From d50f8e6718ec7972b6cd5e99254e13204f6aae1d Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Mon, 15 Sep 2025 08:56:32 +0200 Subject: [PATCH] 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> --- .../block-grid-entry.context.ts | 10 +++--- .../block-list-entry.element.ts | 15 ++++---- .../picker/clipboard-entry-picker.element.ts | 36 +++++++++++++------ .../ufm-virtual-render.controller.ts | 31 +++++++++------- 4 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.context.ts index aca05d1ea2..9528926842 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.context.ts @@ -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}` diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts index 783c0ab10f..5e3a68e444 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts @@ -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}` diff --git a/src/Umbraco.Web.UI.Client/src/packages/clipboard/clipboard-entry/picker/clipboard-entry-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/clipboard/clipboard-entry/picker/clipboard-entry-picker.element.ts index c5c98d5f1a..9f67b19f5c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/clipboard/clipboard-entry/picker/clipboard-entry-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/clipboard/clipboard-entry/picker/clipboard-entry-picker.element.ts @@ -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`

There are no items in the clipboard.

`, + ); } #renderItem(item: UmbClipboardEntryDetailModel) { + const label = item.name ?? item.unique; return html` 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``; + return html``; } #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 { diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/controllers/ufm-virtual-render.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/controllers/ufm-virtual-render.controller.ts index 840be8b92d..1c25519887 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/controllers/ufm-virtual-render.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/controllers/ufm-virtual-render.controller.ts @@ -12,21 +12,26 @@ export class UmbUfmVirtualRenderController extends UmbControllerBase { const items: Array = []; - 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) {