diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 8f052df085..a2cdfa9d3c 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -11,6 +11,7 @@ "./controller-api": "./dist-cms/libs/controller-api/index.js", "./element-api": "./dist-cms/libs/element-api/index.js", "./extension-api": "./dist-cms/libs/extension-api/index.js", + "./formatting-api": "./dist-cms/libs/formatting-api/index.js", "./localization-api": "./dist-cms/libs/localization-api/index.js", "./observable-api": "./dist-cms/libs/observable-api/index.js", "./action": "./dist-cms/packages/core/action/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/libs/formatting-api/formatting.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/formatting-api/formatting.controller.ts new file mode 100644 index 0000000000..fc225b6199 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/formatting-api/formatting.controller.ts @@ -0,0 +1,34 @@ +import { createDOMPurify } from '@umbraco-cms/backoffice/external/dompurify'; +import { Marked } from '@umbraco-cms/backoffice/external/marked'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; +import type { DOMPurify } from '@umbraco-cms/backoffice/external/dompurify'; + +const UmbMarked = new Marked({ gfm: true, breaks: true }); +const UmbDomPurify = createDOMPurify(window); +const UmbDomPurifyConfig: DOMPurify.Config = { USE_PROFILES: { html: true } }; + +UmbDomPurify.addHook('afterSanitizeAttributes', function (node) { + // set all elements owning target to target=_blank + if ('target' in node) { + node.setAttribute('target', '_blank'); + } +}); + +/** + * @description - Controller for formatting text. + */ +export class UmbFormattingController extends UmbControllerBase { + #localize = new UmbLocalizationController(this._host); + + /** + * A method to localize the string input then transform any markdown to santized HTML. + */ + public transform(input?: string): string { + if (!input) return ''; + const translated = this.#localize.string(input); + const markup = UmbMarked.parse(translated) as string; + const sanitized = UmbDomPurify.sanitize(markup, UmbDomPurifyConfig) as string; + return sanitized; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/libs/formatting-api/index.ts b/src/Umbraco.Web.UI.Client/src/libs/formatting-api/index.ts new file mode 100644 index 0000000000..a11870de1f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/formatting-api/index.ts @@ -0,0 +1,2 @@ +export * from './formatting.controller.js'; +export * from './localizeAndTransform.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/formatting-api/localizeAndTransform.function.ts b/src/Umbraco.Web.UI.Client/src/libs/formatting-api/localizeAndTransform.function.ts new file mode 100644 index 0000000000..21ff4e07cd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/formatting-api/localizeAndTransform.function.ts @@ -0,0 +1,6 @@ +import { UmbFormattingController } from './formatting.controller.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export function localizeAndTransform(host: UmbControllerHost, input: string): string { + return new UmbFormattingController(host).transform(input); +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/label-template/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/label-template/index.ts deleted file mode 100644 index e1bff34b66..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/label-template/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './label-template.context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/label-template/label-template.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/label-template/label-template.context.ts deleted file mode 100644 index e44f34435f..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/label-template/label-template.context.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { createDOMPurify } from '@umbraco-cms/backoffice/external/dompurify'; -import { Marked } from '@umbraco-cms/backoffice/external/marked'; -import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -import type { DOMPurify } from '@umbraco-cms/backoffice/external/dompurify'; -import type { MarkedOptions } from '@umbraco-cms/backoffice/external/marked'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; - -export class UmbLabelTemplateContext extends UmbContextBase { - #hostElement?: UmbLitElement; - #marked; - #domPurify: DOMPurify.DOMPurifyI; - #domPurifyConfig: DOMPurify.Config; - - constructor(host: UmbControllerHost) { - super(host, UMB_LABEL_TEMPLATE_CONTEXT); - - this.#marked = new Marked({ gfm: true, breaks: true }); - - this.#domPurifyConfig = { USE_PROFILES: { html: true } }; - - this.#domPurify = createDOMPurify(window); - - this.#domPurify.addHook('afterSanitizeAttributes', function (node) { - // set all elements owning target to target=_blank - if ('target' in node) { - node.setAttribute('target', '_blank'); - } - }); - } - - public setHostElement(hostElement: UmbLitElement): void { - this.#hostElement = hostElement; - } - - public transform(input: string): string | null | undefined { - return this.#transform(input, this.#marked.parse); - } - - public transformInline(input: string): string | null | undefined { - return this.#transform(input, this.#marked.parseInline); - } - - #transform( - input: string, - markdownParse?: (src: string, options?: MarkedOptions | undefined | null) => string | Promise, - ): string | null | undefined { - if (!input) return undefined; - - const localized = this.#hostElement ? this.#hostElement.localize.string(input) : input; - const markdowned = markdownParse ? (markdownParse(localized) as string) : localized; - const sanitized = markdowned ? (this.#domPurify.sanitize(markdowned, this.#domPurifyConfig) as string) : localized; - - return sanitized ?? input; - } -} - -export const UMB_LABEL_TEMPLATE_CONTEXT = new UmbContextToken('UmbLabelTemplateContext'); - -// Default export to enable this as a globalContext extension js: -export default UmbLabelTemplateContext; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/label-template/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/label-template/manifests.ts deleted file mode 100644 index 54cd180897..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/label-template/manifests.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ManifestGlobalContext } from '@umbraco-cms/backoffice/extension-registry'; - -export const manifests: Array = [ - { - type: 'globalContext', - alias: 'Umb.GlobalContext.LabelTemplate', - name: 'Label Template Global Context', - api: () => import('./label-template.context.js'), - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts index 1b8480f15a..22d8b9f67f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts @@ -8,7 +8,6 @@ import { manifests as entityActionManifests } from './entity-action/manifests.js import { manifests as extensionManifests } from './extension-registry/manifests.js'; import { manifests as iconRegistryManifests } from './icon-registry/manifests.js'; import { manifests as imagingManifests } from './imaging/manifests.js'; -import { manifests as labelTemplateManifests } from './label-template/manifests.js'; import { manifests as localizationManifests } from './localization/manifests.js'; import { manifests as modalManifests } from './modal/common/manifests.js'; import { manifests as propertyActionManifests } from './property-action/manifests.js'; @@ -28,7 +27,6 @@ export const manifests: Array = [ ...iconRegistryManifests, ...imagingManifests, ...cultureManifests, - ...labelTemplateManifests, ...localizationManifests, ...themeManifests, ...sectionManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-layout/property-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-layout/property-layout.element.ts index bd0802019e..83d3496cf6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-layout/property-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-layout/property-layout.element.ts @@ -1,4 +1,4 @@ -import { UMB_LABEL_TEMPLATE_CONTEXT } from '../../label-template/index.js'; +import { localizeAndTransform } from '@umbraco-cms/backoffice/formatting-api'; import { css, customElement, html, property, unsafeHTML, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @@ -12,17 +12,6 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; */ @customElement('umb-property-layout') export class UmbPropertyLayoutElement extends UmbLitElement { - #labelTemplate?: typeof UMB_LABEL_TEMPLATE_CONTEXT.TYPE; - - constructor() { - super(); - - this.consumeContext(UMB_LABEL_TEMPLATE_CONTEXT, (context) => { - this.#labelTemplate = context; - this.#labelTemplate.setHostElement(this); - }); - } - /** * Alias. The technical name of the property. * @type {string} @@ -70,17 +59,15 @@ export class UmbPropertyLayoutElement extends UmbLitElement { public invalid?: boolean; render() { - const label = this.#labelTemplate?.transformInline(this.label) ?? this.label; - const description = this.#labelTemplate?.transform(this.description) ?? this.description; - // TODO: Only show alias on label if user has access to DocumentType within settings: return html`
- ${label} ${when(this.invalid, () => html`!`)} + ${this.localize.string(this.label)} + ${when(this.invalid, () => html`!`)} -
${unsafeHTML(description)}
+
${unsafeHTML(localizeAndTransform(this, this.description))}
diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 86aa97925d..6e26a50763 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -36,6 +36,7 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "@umbraco-cms/backoffice/controller-api": ["./src/libs/controller-api/index.ts"], "@umbraco-cms/backoffice/element-api": ["./src/libs/element-api/index.ts"], "@umbraco-cms/backoffice/extension-api": ["./src/libs/extension-api/index.ts"], + "@umbraco-cms/backoffice/formatting-api": ["./src/libs/formatting-api/index.ts"], "@umbraco-cms/backoffice/localization-api": ["./src/libs/localization-api/index.ts"], "@umbraco-cms/backoffice/observable-api": ["./src/libs/observable-api/index.ts"], "@umbraco-cms/backoffice/action": ["./src/packages/core/action/index.ts"],