Block editors: resolves clipboard UFM label (#20102)

* Block item copy rendered UFM label to clipboard

* Fixes clipboard entry's icon

* Clipboard entry: sets title

and fallback on the unique (instead of empty string)

* Clipboard entry: CSS for flat menu structure

* Clipboard entry: replace condition with `when`

+ imports tidy-up

* Imports tidy-up

* Fixed UFM Virtual Render's nested text retrieval

Previously, it'd placed nested text after the parent's text,
now it remains nested.

* Update src/Umbraco.Web.UI.Client/src/packages/ufm/controllers/ufm-virtual-render.controller.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Lee Kelleher
2025-09-15 08:56:32 +02:00
committed by GitHub
parent 5ddcf44256
commit d50f8e6718
4 changed files with 55 additions and 37 deletions

View File

@@ -6,10 +6,8 @@ import {
} from '../../constants.js';
import { UMB_BLOCK_GRID_MANAGER_CONTEXT } from '../../block-grid-manager/block-grid-manager.context-token.js';
import { UMB_BLOCK_GRID_ENTRIES_CONTEXT } from '../block-grid-entries/block-grid-entries.context-token.js';
import {
type UmbBlockGridScalableContext,
UmbBlockGridScaleManager,
} from '../../context/block-grid-scale-manager/block-grid-scale-manager.controller.js';
import { UmbBlockGridScaleManager } from '../../context/block-grid-scale-manager/block-grid-scale-manager.controller.js';
import type { UmbBlockGridScalableContext } from '../../context/block-grid-scale-manager/block-grid-scale-manager.controller.js';
import {
UmbArrayState,
UmbBooleanState,
@@ -18,10 +16,10 @@ import {
mergeObservables,
observeMultiple,
} from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbBlockEntryContext } from '@umbraco-cms/backoffice/block';
import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
import { UMB_CLIPBOARD_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/clipboard';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbBlockGridEntryContext
extends UmbBlockEntryContext<
@@ -301,7 +299,7 @@ export class UmbBlockGridEntryContext
const workspaceName = propertyDatasetContext?.getName();
const propertyLabel = propertyContext?.getLabel();
const blockLabel = this.getLabel();
const blockLabel = this.getName();
const entryName = workspaceName
? `${workspaceName} - ${propertyLabel} - ${blockLabel}`

View File

@@ -5,24 +5,25 @@ import {
UMB_BLOCK_LIST_PROPERTY_EDITOR_SCHEMA_ALIAS,
UMB_BLOCK_LIST_PROPERTY_EDITOR_UI_ALIAS,
} from '../../constants.js';
import { css, customElement, html, nothing, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element';
import { html, css, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor';
import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils';
import { UmbObserveValidationStateController } from '@umbraco-cms/backoffice/validation';
import { UmbDataPathBlockElementDataQuery } from '@umbraco-cms/backoffice/block';
import { UmbObserveValidationStateController } from '@umbraco-cms/backoffice/validation';
import { UUIBlinkAnimationValue, UUIBlinkKeyframes } from '@umbraco-cms/backoffice/external/uui';
import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
import { UMB_CLIPBOARD_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/clipboard';
import type {
ManifestBlockEditorCustomView,
UmbBlockEditorCustomViewProperties,
} from '@umbraco-cms/backoffice/block-custom-view';
import type { UmbExtensionElementInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UUIBlinkAnimationValue, UUIBlinkKeyframes } from '@umbraco-cms/backoffice/external/uui';
import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
import { UMB_CLIPBOARD_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/clipboard';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor';
import '../ref-list-block/index.js';
import '../inline-list-block/index.js';
import '../unsupported-list-block/index.js';
/**
* @element umb-block-list-entry
*/
@@ -309,7 +310,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
const workspaceName = propertyDatasetContext?.getName();
const propertyLabel = propertyContext?.getLabel();
const blockLabel = this._label;
const blockLabel = this.#context.getName();
const entryName = workspaceName
? `${workspaceName} - ${propertyLabel} - ${blockLabel}`

View File

@@ -1,14 +1,15 @@
import { UmbClipboardCollectionRepository } from '../../collection/index.js';
import type { UmbClipboardEntryDetailModel } from '../types.js';
import { html, customElement, state, repeat, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
import { UmbEntityContext, type UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { css, customElement, html, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbEntityContext } from '@umbraco-cms/backoffice/entity';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import {
UmbRequestReloadChildrenOfEntityEvent,
UmbRequestReloadStructureForEntityEvent,
} from '@umbraco-cms/backoffice/entity-action';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
// TODO: make this into an extension point (Picker) with two kinds of pickers: tree-item-picker and collection-item-picker;
@customElement('umb-clipboard-entry-picker')
@@ -117,19 +118,24 @@ export class UmbClipboardEntryPickerElement extends UmbLitElement {
};
override render() {
return html`${this._items.length > 0
? repeat(
return when(
this._items.length > 0,
() =>
repeat(
this._items,
(item) => item.unique,
(item) => this.#renderItem(item),
)
: html`There are no items in the clipboard`}`;
),
() => html`<p>There are no items in the clipboard.</p>`,
);
}
#renderItem(item: UmbClipboardEntryDetailModel) {
const label = item.name ?? item.unique;
return html`
<uui-menu-item
label=${item.name ?? ''}
label=${label}
title=${label}
selectable
@selected=${() => this.#selectionManager.select(item.unique)}
@deselected=${() => this.#selectionManager.deselect(item.unique)}
@@ -141,7 +147,7 @@ export class UmbClipboardEntryPickerElement extends UmbLitElement {
#renderItemIcon(item: UmbClipboardEntryDetailModel) {
const iconName = item.icon ?? 'icon-clipboard-entry';
return html`<uui-icon slot="icon" name=${iconName}></uui-icon>`;
return html`<umb-icon slot="icon" name=${iconName}></umb-icon>`;
}
#renderItemActions(item: UmbClipboardEntryDetailModel) {
@@ -168,6 +174,14 @@ export class UmbClipboardEntryPickerElement extends UmbLitElement {
super.destroy();
}
static override styles = [
css`
:host {
--uui-menu-item-flat-structure: 1;
}
`,
];
}
declare global {

View File

@@ -12,21 +12,26 @@ export class UmbUfmVirtualRenderController extends UmbControllerBase {
const items: Array<string> = [];
items.push(element.shadowRoot?.textContent ?? element.textContent ?? '');
if (element.shadowRoot !== null) {
Array.from(element.shadowRoot.children).forEach((element) => {
items.push(this.#getTextFromDescendants(element));
});
// Try get the text content from the shadow root first, otherwise get it from the light DOM. [LK]
if (element.shadowRoot) {
for (const node of element.shadowRoot.childNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
items.push(this.#getTextFromDescendants(node as Element));
} else if (node.nodeType === Node.TEXT_NODE) {
items.push(node.textContent ?? '');
}
}
} else {
for (const node of element.childNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
items.push(this.#getTextFromDescendants(node as Element));
} else if (node.nodeType === Node.TEXT_NODE) {
items.push(node.textContent ?? '');
}
}
}
if (element.children !== null) {
Array.from(element.children).forEach((element) => {
items.push(this.#getTextFromDescendants(element));
});
}
return items.filter((x) => x).join(' ');
return items.filter((x) => x).join('');
}
set markdown(markdown: string | undefined) {