diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts index 61561ea731..a0d880a33e 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts @@ -13,6 +13,7 @@ interface UmbControllerHostBaseDeclaration extends Omit(superClass: T) => { class UmbControllerHostBaseClass extends superClass implements UmbControllerHostBaseDeclaration { @@ -26,7 +27,8 @@ export const UmbControllerHostMixin = (superClass: T /** * Tests if a controller is assigned to this element. - * @param {UmbController} ctrl + * @param {UmbController} ctrl - The controller to check for. + * @returns {boolean} - true if the controller is assigned */ hasUmbController(ctrl: UmbController): boolean { return this.#controllers.indexOf(ctrl) !== -1; @@ -34,15 +36,16 @@ export const UmbControllerHostMixin = (superClass: T /** * Retrieve controllers matching a filter of this element. - * @param {method} filterMethod + * @param {Function} filterMethod - filter method + * @returns {Array} - currently assigned controllers passing the filter method. */ - getUmbControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[] { + getUmbControllers(filterMethod: (ctrl: UmbController) => boolean): Array { return this.#controllers.filter(filterMethod); } /** * Append a controller to this element. - * @param {UmbController} ctrl + * @param {UmbController} ctrl - the controller to append to this host. */ addUmbController(ctrl: UmbController): void { // If this specific class is already added, then skip out. @@ -68,7 +71,7 @@ export const UmbControllerHostMixin = (superClass: T /** * Remove a controller from this element. * Notice this will also destroy the controller. - * @param {UmbController} ctrl + * @param {UmbController} ctrl - The controller to remove and destroy from this host. */ removeUmbController(ctrl: UmbController): void { const index = this.#controllers.indexOf(ctrl); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts index 03d92d381b..cb057981ac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts @@ -23,6 +23,7 @@ import type { import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbUfmVirtualRenderController } from '@umbraco-cms/backoffice/ufm'; export abstract class UmbBlockEntryContext< BlockManagerContextTokenType extends UmbContextToken, @@ -94,6 +95,8 @@ export abstract class UmbBlockEntryContext< #label = new UmbStringState(''); public readonly label = this.#label.asObservable(); + #labelRender = new UmbUfmVirtualRenderController(this); + #generateWorkspaceEditContentPath = (path?: string, contentKey?: string) => path && contentKey ? path + 'edit/' + encodeFilePath(contentKey) + '/view/content' : ''; @@ -244,6 +247,11 @@ export abstract class UmbBlockEntryContext< ) { super(host, 'UmbBlockEntryContext'); + this.observe(this.label, (label) => { + this.#labelRender.markdown = label; + }); + this.#watchContentForLabelRender(); + // Consume block manager: this.consumeContext(blockManagerContextToken, (manager) => { this._manager = manager; @@ -340,6 +348,12 @@ export abstract class UmbBlockEntryContext< ); } + async #watchContentForLabelRender() { + this.observe(await this.contentValues(), (content) => { + this.#labelRender.value = content; + }); + } + getContentKey() { return this._layout.value?.contentKey; } @@ -361,7 +375,7 @@ export abstract class UmbBlockEntryContext< * @returns {string} - the value of the label. */ getLabel() { - return this.#label.value; + return this.#labelRender.toString(); } #updateCreatePaths() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/ufm-render/ufm-render.element.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/ufm-render/ufm-render.element.ts index 6bf24160e5..76de2ba178 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/ufm-render/ufm-render.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/ufm-render/ufm-render.element.ts @@ -34,6 +34,10 @@ export class UmbUfmRenderElement extends UmbLitElement { }); } + override toString(): string { + return this.shadowRoot?.textContent ?? ''; + } + override render() { return until(this.#renderMarkdown()); } 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 new file mode 100644 index 0000000000..c8ead67af2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/controllers/ufm-virtual-render.controller.ts @@ -0,0 +1,68 @@ +import { UmbUfmRenderElement } from '../components/index.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +/** + * Renders a UFM + */ +export class UmbUfmVirtualRenderController extends UmbControllerBase { + #element: UmbUfmRenderElement; + + #getTextFromDescendants(element?: Element | null): string { + if (!element) return ''; + + 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)); + }); + } + + if (element.children !== null) { + Array.from(element.children).forEach((element) => { + items.push(this.#getTextFromDescendants(element)); + }); + } + + return items.filter((x) => x).join(' '); + } + + set markdown(markdown: string | undefined) { + this.#element.markdown = markdown; + } + get markdown(): string | undefined { + return this.#element.markdown; + } + + set value(value: unknown | undefined) { + this.#element.value = value; + } + get value(): unknown | undefined { + return this.#element.value; + } + + constructor(host: UmbControllerHost) { + super(host); + + const element = new UmbUfmRenderElement(); + element.inline = true; + element.style.visibility = 'hidden'; + this.getHostElement().appendChild(element); + this.#element = element; + } + + override hostConnected(): void {} + + override toString(): string { + return this.#getTextFromDescendants(this.#element); + } + + override destroy(): void { + super.destroy(); + this.#element?.destroy(); + (this.#element as any) = undefined; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts index 481d5818a8..6735e76614 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts @@ -1,6 +1,7 @@ -export * from './types.js'; export * from './components/index.js'; export * from './contexts/index.js'; +export * from './controllers/ufm-virtual-render.controller.js'; export * from './plugins/index.js'; +export * from './types.js'; export * from './ufm-component.extension.js'; export * from './ufm-filter.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/types.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/types.ts index 066099c1db..e97116b6c1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/types.ts @@ -1,5 +1,6 @@ import type { UmbUfmFilterApi } from './ufm-filter.extension.js'; +// TODO: This is not a type? So it should ideally be move to a different file. [NL] export abstract class UmbUfmFilterBase implements UmbUfmFilterApi { abstract filter(...args: Array): string | undefined | null; destroy() {}