Feature: Umbraco Flavored Markdown

This commit is contained in:
leekelleher
2024-05-23 09:12:38 +01:00
parent 24e3af0079
commit a70b8f91aa
31 changed files with 490 additions and 45 deletions

View File

@@ -86,6 +86,7 @@
"./themes": "./dist-cms/packages/core/themes/index.js",
"./tiny-mce": "./dist-cms/packages/tiny-mce/index.js",
"./tree": "./dist-cms/packages/core/tree/index.js",
"./ufm": "./dist-cms/packages/ufm/index.js",
"./user-group": "./dist-cms/packages/user/user-group/index.js",
"./user-permission": "./dist-cms/packages/user/user-permission/index.js",
"./user": "./dist-cms/packages/user/user/index.js",

View File

@@ -33,6 +33,7 @@ const CORE_PACKAGES = [
import('../../packages/tags/umbraco-package.js'),
import('../../packages/templating/umbraco-package.js'),
import('../../packages/tiny-mce/umbraco-package.js'),
import('../../packages/ufm/umbraco-package.js'),
import('../../packages/umbraco-news/umbraco-package.js'),
import('../../packages/user/umbraco-package.js'),
import('../../packages/webhook/umbraco-package.js'),

View File

@@ -39,7 +39,7 @@ describe('UmbContextConsumer', () => {
describe('events', () => {
it('dispatches context request event when constructed', async () => {
const listener = oneEvent(window, UMB_CONTENT_REQUEST_EVENT_TYPE, false);
const listener = oneEvent(window, UMB_CONTENT_REQUEST_EVENT_TYPE);
consumer.hostConnected();

View File

@@ -16,12 +16,14 @@ UmbDomPurify.addHook('afterSanitizeAttributes', function (node) {
/**
* @description - Controller for formatting text.
* @deprecated - Use the `<umb-ufm-render>` component instead. This method will be removed in Umbraco 15.
*/
export class UmbFormattingController extends UmbControllerBase {
#localize = new UmbLocalizationController(this._host);
/**
* A method to localize the string input then transform any markdown to santized HTML.
* @deprecated - Use the `<umb-ufm-render>` component instead. This method will be removed in Umbraco 15.
*/
public transform(input?: string): string {
if (!input) return '';

View File

@@ -1,6 +1,9 @@
import { UmbFormattingController } from './formatting.controller.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
/**
* @deprecated - Use the `<umb-ufm-render>` component instead. This method will be removed in Umbraco 15.
*/
export function localizeAndTransform(host: UmbControllerHost, input: string): string {
return new UmbFormattingController(host).transform(input);
}

View File

@@ -1,8 +1,11 @@
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { css, customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UMB_BLOCK_GRID_ENTRY_CONTEXT } from '../../context/block-grid-entry.context-token.js';
import type { UmbBlockDataType, UmbBlockViewUrlsPropType } from '@umbraco-cms/backoffice/block';
import '@umbraco-cms/backoffice/ufm';
import '../block-grid-areas-container/index.js';
import '../ref-grid-block/index.js';
import type { UmbBlockViewUrlsPropType } from '@umbraco-cms/backoffice/block';
/**
* @element umb-block-grid-block
@@ -16,8 +19,26 @@ export class UmbBlockGridBlockElement extends UmbLitElement {
@property({ attribute: false })
urls?: UmbBlockViewUrlsPropType;
@state()
_content?: UmbBlockDataType;
constructor() {
super();
this.consumeContext(UMB_BLOCK_GRID_ENTRY_CONTEXT, (context) => {
this.observe(
context.content,
(content) => {
this._content = content;
},
'observeContent',
);
});
}
override render() {
return html`<umb-ref-grid-block standalone .name=${this.label ?? ''} href=${this.urls?.editContent ?? ''}>
return html`<umb-ref-grid-block standalone href=${this.urls?.editContent ?? ''}>
<umb-ufm-render inline .markdown=${this.label} .value=${this._content}></umb-ufm-render>
<umb-block-grid-areas-container slot="areas"></umb-block-grid-areas-container>
</umb-ref-grid-block>`;
}

View File

@@ -1,6 +1,9 @@
import { UMB_BLOCK_ENTRY_CONTEXT } from '@umbraco-cms/backoffice/block';
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_BLOCK_ENTRY_CONTEXT } from '@umbraco-cms/backoffice/block';
import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block';
import '@umbraco-cms/backoffice/ufm';
/**
* @element umb-ref-list-block
@@ -11,6 +14,9 @@ export class UmbRefListBlockElement extends UmbLitElement {
@property({ type: String })
label?: string;
@state()
_content?: UmbBlockDataType;
@state()
_workspaceEditPath?: string;
@@ -19,6 +25,14 @@ export class UmbRefListBlockElement extends UmbLitElement {
// UMB_BLOCK_LIST_ENTRY_CONTEXT
this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT, (context) => {
this.observe(
context.content,
(content) => {
this._content = content;
},
'observeContent',
);
this.observe(
context.workspaceEditContentPath,
(workspaceEditPath) => {
@@ -30,10 +44,11 @@ export class UmbRefListBlockElement extends UmbLitElement {
}
override render() {
return html`<uui-ref-node
standalone
.name=${this.label ?? ''}
href=${this._workspaceEditPath ?? '#'}></uui-ref-node>`;
return html`
<uui-ref-node standalone href=${this._workspaceEditPath ?? '#'}>
<umb-ufm-render inline .markdown=${this.label} .value=${this._content}></umb-ufm-render>
</uui-ref-node>
`;
}
static override styles = [

View File

@@ -1,14 +1,15 @@
import type { UmbUfmRenderElement } from '../../../ufm/components/ufm-render/index.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import {
css,
html,
LitElement,
ifDefined,
when,
customElement,
html,
ifDefined,
property,
state,
repeat,
state,
when,
LitElement,
} from '@umbraco-cms/backoffice/external/lit';
// TODO: move to UI Library - entity actions should NOT be moved to UI Library but stay in an UmbTable element
@@ -31,6 +32,7 @@ export interface UmbTableColumn {
width?: string;
allowSorting?: boolean;
align?: 'left' | 'center' | 'right';
labelTemplate?: string;
}
export interface UmbTableColumnLayoutElement extends HTMLElement {
@@ -263,6 +265,15 @@ export class UmbTableElement extends LitElement {
return element;
}
if (column.labelTemplate) {
import('@umbraco-cms/backoffice/ufm');
const element = document.createElement('umb-ufm-render') as UmbUfmRenderElement;
element.inline = true;
element.markdown = column.labelTemplate;
element.value = value;
return element;
}
return value;
}

View File

@@ -45,6 +45,7 @@ import type { ManifestTheme } from './theme.model.js';
import type { ManifestTinyMcePlugin } from './tinymce-plugin.model.js';
import type { ManifestTree } from './tree.model.js';
import type { ManifestTreeItem } from './tree-item.model.js';
import type { ManifestUfmComponent } from './ufm-component.model.js';
import type { ManifestUserProfileApp } from './user-profile-app.model.js';
import type { ManifestWorkspace, ManifestWorkspaceRoutableKind } from './workspace.model.js';
import type { ManifestWorkspaceAction, ManifestWorkspaceActionDefaultKind } from './workspace-action.model.js';
@@ -113,6 +114,7 @@ export type * from './theme.model.js';
export type * from './tinymce-plugin.model.js';
export type * from './tree-item.model.js';
export type * from './tree.model.js';
export type * from './ufm-component.model.js';
export type * from './user-granular-permission.model.js';
export type * from './user-profile-app.model.js';
export type * from './workspace-action-menu-item.model.js';
@@ -204,6 +206,7 @@ export type ManifestTypes =
| ManifestTree
| ManifestTreeItem
| ManifestTreeStore
| ManifestUfmComponent
| ManifestUserProfileApp
| ManifestWorkspaceActionMenuItem
| ManifestWorkspaceActions

View File

@@ -0,0 +1,15 @@
import type { ManifestApi, UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { Tokens } from '@umbraco-cms/backoffice/external/marked';
export interface UmbUfmComponentApi extends UmbApi {
render(token: Tokens.Generic): string | undefined;
}
export interface MetaUfmComponent {
marker: string;
}
export interface ManifestUfmComponent extends ManifestApi<UmbUfmComponentApi> {
type: 'ufmComponent';
meta: MetaUfmComponent;
}

View File

@@ -22,16 +22,14 @@ export class UmbLocalizeElement extends UmbLitElement {
* @example args="[1,2,3]"
* @type {any[] | undefined}
*/
@property({
type: Array,
})
@property({ type: Array })
args?: unknown[];
/**
* If true, the key will be rendered instead of the localized value if the key is not found.
* @attr
*/
@property()
@property({ type: Boolean })
debug = false;
@state()

View File

@@ -133,7 +133,7 @@ describe('UmbBasicVariantElement', () => {
});
it('fires change event', async () => {
const listener = oneEvent(datasetElement, UmbChangeEvent.TYPE, false);
const listener = oneEvent(datasetElement, UmbChangeEvent.TYPE);
expect(propertyEditor.alias).to.eq('testAlias');
propertyEditor.setValue('testValue3');
@@ -153,7 +153,7 @@ describe('UmbBasicVariantElement', () => {
adapterPropertyEditor.alias = 'testAdapterAlias';
datasetElement.appendChild(adapterPropertyEditor);
const listener = oneEvent(datasetElement, UmbChangeEvent.TYPE, false);
const listener = oneEvent(datasetElement, UmbChangeEvent.TYPE);
// The alias of the original property editor must be 'testAlias' for the adapter to set the value of it.
expect(propertyEditor.alias).to.eq('testAlias');

View File

@@ -1,8 +1,9 @@
import { localizeAndTransform } from '@umbraco-cms/backoffice/formatting-api';
import { css, customElement, html, property, unsafeHTML, when } from '@umbraco-cms/backoffice/external/lit';
import { css, customElement, html, property, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import '@umbraco-cms/backoffice/ufm';
/**
* @element umb-property-layout
* @description - Element for displaying a property in an workspace.
@@ -59,6 +60,7 @@ export class UmbPropertyLayoutElement extends UmbLitElement {
public invalid?: boolean;
override render() {
const ufmValue = { alias: this.alias, label: this.label, description: this.description };
// TODO: Only show alias on label if user has access to DocumentType within settings:
return html`
<div id="headerColumn">
@@ -68,7 +70,7 @@ export class UmbPropertyLayoutElement extends UmbLitElement {
</uui-label>
<slot name="action-menu"></slot>
<uui-scroll-container id="description">
${unsafeHTML(localizeAndTransform(this, this.description))}
<umb-ufm-render .markdown=${this.description} .value=${ufmValue}></umb-ufm-render>
</uui-scroll-container>
<slot name="description"></slot>
</div>
@@ -126,18 +128,6 @@ export class UmbPropertyLayoutElement extends UmbLitElement {
right: -30px;
}
#description {
color: var(--uui-color-text-alt);
}
#description * {
max-width: 100%;
}
#description pre {
overflow: auto;
}
#editorColumn {
margin-top: var(--uui-size-space-3);
}

View File

@@ -141,7 +141,7 @@ describe('UmbPaginationManager', () => {
});
it('dispatches a change event', async () => {
const listener = oneEvent(manager, UmbChangeEvent.TYPE, false);
const listener = oneEvent(manager, UmbChangeEvent.TYPE);
manager.setCurrentPageNumber(2);
const event = (await listener) as unknown as UmbChangeEvent;
const target = event.target as UmbPaginationManager;

View File

@@ -1,15 +1,17 @@
import { getPropertyValueByAlias } from '../index.js';
import { UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN } from '../../../paths.js';
import type { UmbDocumentCollectionFilterModel, UmbDocumentCollectionItemModel } from '../../types.js';
import { UMB_DOCUMENT_COLLECTION_CONTEXT } from '../../document-collection.context-token.js';
import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
import { fromCamelCase } from '@umbraco-cms/backoffice/utils';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/modal';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
import type { UmbDefaultCollectionContext, UmbCollectionColumnConfiguration } from '@umbraco-cms/backoffice/collection';
import type { UUIInterfaceColor } from '@umbraco-cms/backoffice/external/uui';
import { UMB_DOCUMENT_COLLECTION_CONTEXT } from '../../document-collection.context-token.js';
import type { UmbDocumentCollectionFilterModel, UmbDocumentCollectionItemModel } from '../../types.js';
import { UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN } from '../../../paths.js';
import { getPropertyValueByAlias } from '../index.js';
import '@umbraco-cms/backoffice/ufm';
@customElement('umb-document-grid-collection-view')
export class UmbDocumentGridCollectionViewElement extends UmbLitElement {
@@ -170,7 +172,21 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement {
${repeat(
this._userDefinedProperties,
(column) => column.alias,
(column) => html`<li><span>${column.header}:</span> ${getPropertyValueByAlias(item, column.alias)}</li>`,
(column) => html`
<li>
<span>${column.header}:</span>
${when(
column.nameTemplate,
() => html`
<umb-ufm-render
inline
.markdown=${column.nameTemplate}
.value=${getPropertyValueByAlias(item, column.alias)}></umb-ufm-render>
`,
() => html`${getPropertyValueByAlias(item, column.alias)}`,
)}
</li>
`,
)}
</ul>
`;

View File

@@ -136,6 +136,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement {
name: item.header,
alias: item.alias,
elementName: item.elementName,
labelTemplate: item.nameTemplate,
allowSorting: true,
};
});

View File

@@ -82,6 +82,15 @@ export class UmbPropertyEditorUICollectionColumnConfigurationElement
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
#onChangeNameTemplate(e: UUIInputEvent, configuration: UmbCollectionColumnConfiguration) {
this.value = this.value?.map(
(config): UmbCollectionColumnConfiguration =>
config.alias === configuration.alias ? { ...config, nameTemplate: e.target.value as string } : config,
);
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
#onRemove(unique: string) {
const newValue: Array<UmbCollectionColumnConfiguration> = [];
@@ -135,10 +144,10 @@ export class UmbPropertyEditorUICollectionColumnConfigurationElement
</div>
<uui-input
disabled
label="template"
placeholder="Enter a name template..."
.value=${column.nameTemplate ?? ''}></uui-input>
placeholder="Enter a label template..."
.value=${column.nameTemplate ?? ''}
@change=${(e: UUIInputEvent) => this.#onChangeNameTemplate(e, column)}></uui-input>
<div class="actions">
<uui-button

View File

@@ -0,0 +1,2 @@
export * from './ufm-render.element.js';
export { UMB_UFM_RENDER_CONTEXT } from './ufm-render.context.js';

View File

@@ -0,0 +1,25 @@
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
export class UmbUfmRenderContext extends UmbContextBase<UmbUfmRenderContext> {
#value = new UmbObjectState<unknown>(undefined);
readonly value = this.#value.asObservable();
constructor(host: UmbControllerHost) {
super(host, UMB_UFM_RENDER_CONTEXT);
}
getValue() {
return this.#value.getValue();
}
setValue(value: unknown | undefined) {
this.#value.setValue(value);
}
}
export default UmbUfmRenderContext;
export const UMB_UFM_RENDER_CONTEXT = new UmbContextToken<UmbUfmRenderContext>('UmbUfmRenderContext');

View File

@@ -0,0 +1,119 @@
import type { UfmPlugin } from '../../plugins/marked-ufm.plugin.js';
import { ufm } from '../../plugins/marked-ufm.plugin.js';
import { UmbUfmRenderContext } from './ufm-render.context.js';
import { css, customElement, nothing, property, unsafeHTML, until } from '@umbraco-cms/backoffice/external/lit';
import { DOMPurify } from '@umbraco-cms/backoffice/external/dompurify';
import { Marked } from '@umbraco-cms/backoffice/external/marked';
import type { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import type { ManifestUfmComponent } from '@umbraco-cms/backoffice/extension-registry';
const UmbDomPurify = DOMPurify(window);
const UmbDomPurifyConfig: DOMPurify.Config = {
USE_PROFILES: { html: true },
CUSTOM_ELEMENT_HANDLING: {
tagNameCheck: /^(?:ufm|umb|uui)-.*$/,
attributeNameCheck: /.+/,
allowCustomizedBuiltInElements: false,
},
};
UmbDomPurify.addHook('afterSanitizeAttributes', function (node) {
// set all elements owning target to target=_blank
if ('target' in node) {
node.setAttribute('target', '_blank');
}
});
const UmbMarked = new Marked({
async: true,
gfm: true,
breaks: true,
hooks: {
postprocess: (markup) => {
return UmbDomPurify.sanitize(markup, UmbDomPurifyConfig) as string;
},
},
});
const elementName = 'umb-ufm-render';
@customElement(elementName)
export class UmbUfmRenderElement extends UmbLitElement {
#context = new UmbUfmRenderContext(this);
@property({ type: Boolean })
inline = false;
@property()
markdown?: string;
@property({ attribute: false })
public set value(value: string | unknown | undefined) {
this.#context.setValue(value);
}
public get value(): string | unknown | undefined {
return this.#context.getValue();
}
constructor() {
super();
new UmbExtensionsApiInitializer(this, umbExtensionsRegistry, 'ufmComponent', [], undefined, (controllers) => {
UmbMarked.use(
ufm(
controllers
.map((controller) => {
const ctrl = controller as unknown as UmbExtensionApiInitializer<ManifestUfmComponent>;
if (!ctrl.manifest || !ctrl.api) return;
return {
alias: ctrl.manifest.alias,
marker: ctrl.manifest.meta.marker,
render: ctrl.api.render,
};
})
.filter((x) => x) as Array<UfmPlugin>,
),
);
this.requestUpdate('markdown');
});
}
override render() {
return until(this.#renderMarkdown());
}
async #renderMarkdown() {
if (!this.markdown) return null;
const markup = !this.inline ? await UmbMarked.parse(this.markdown) : await UmbMarked.parseInline(this.markdown);
return markup ? unsafeHTML(markup) : nothing;
}
static override styles = [
UmbTextStyles,
css`
:host {
color: var(--uui-color-text-alt);
}
* {
max-width: 100%;
}
pre {
overflow: auto;
}
`,
];
}
export { UmbUfmRenderElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbUfmRenderElement;
}
}

View File

@@ -0,0 +1,2 @@
export * from './components/ufm-render/index.js';
export * from './plugins/marked-ufm.plugin.js';

View File

@@ -0,0 +1,22 @@
import type { ManifestUfmComponent } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestUfmComponent> = [
{
type: 'ufmComponent',
alias: 'Umb.Markdown.LabelValue',
name: 'Label Value Markdown Component',
api: () => import('./ufm-components/label-value.component.js'),
meta: {
marker: '=?',
},
},
{
type: 'ufmComponent',
alias: 'Umb.Markdown.Localize',
name: 'Localize Markdown Component',
api: () => import('./ufm-components/localize.component.js'),
meta: {
marker: '#',
},
},
];

View File

@@ -0,0 +1,41 @@
import type { MarkedExtension, Tokens } from '@umbraco-cms/backoffice/external/marked';
export interface UfmPlugin {
alias: string;
marker: string;
render?: (token: Tokens.Generic) => string | undefined;
}
export function ufm(plugins: Array<UfmPlugin> = []): MarkedExtension {
return {
extensions: plugins.map(({ alias, marker, render }) => {
return {
name: alias,
level: 'inline',
start: (src: string) => {
const regex = new RegExp(`\\{${marker}`);
const match = src.match(regex);
return match ? match.index : -1;
},
tokenizer(src: string): Tokens.Generic | undefined {
const pattern = `^(?<!\\\\){{?${marker}((?:[a-zA-Z][\\w-]*|[\\{].*?[\\}]+|[\\[].*?[\\]])+)(?<!\\\\)}}?`;
const regex = new RegExp(pattern);
const match = src.match(regex);
if (match) {
const [raw, content = ''] = match;
return {
type: alias,
raw: raw,
tokens: [],
text: content.trim(),
};
}
return undefined;
},
renderer: render,
};
}),
};
}

View File

@@ -0,0 +1,12 @@
// import { UmbUfmComponentBase } from './ufm-component-base.js';
// import type { Tokens } from '@umbraco-cms/backoffice/external/marked';
// import './document-name.element.js';
// export class UmbUfmDocumentNameComponent extends UmbUfmComponentBase {
// render(token: Tokens.Generic) {
// return `<ufm-document-name alias="${token.text}" debug></ufm-document-name>`;
// }
// }
// export { UmbUfmDocumentNameComponent as api };

View File

@@ -0,0 +1,54 @@
// import { UMB_UFM_RENDER_CONTEXT } from '@umbraco-cms/backoffice/components';
// import { UmbDocumentItemRepository } from '@umbraco-cms/backoffice/document';
// import { customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
// import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
// const elementName = 'ufm-document-name';
// @customElement(elementName)
// export class UmbUfmDocumentNameElement extends UmbLitElement {
// @property()
// alias?: string;
// @state()
// private _value?: unknown;
// #documentRepository = new UmbDocumentItemRepository(this);
// constructor() {
// super();
// this.consumeContext(UMB_UFM_RENDER_CONTEXT, (context) => {
// this.observe(
// context.value,
// async (value) => {
// if (!value) return;
// const unique =
// this.alias && typeof value === 'object'
// ? ((value as Record<string, unknown>)[this.alias] as string)
// : (value as string);
// if (!unique) return;
// const { data } = await this.#documentRepository.requestItems([unique]);
// this._value = data?.[0]?.name;
// },
// 'observeValue',
// );
// });
// }
// override render() {
// return this._value ?? this.alias;
// }
// }
// export { UmbUfmDocumentNameElement as element };
// declare global {
// interface HTMLElementTagNameMap {
// [elementName]: UmbUfmDocumentNameElement;
// }
// }

View File

@@ -0,0 +1,12 @@
import { UmbUfmComponentBase } from './ufm-component-base.js';
import type { Tokens } from '@umbraco-cms/backoffice/external/marked';
import './label-value.element.js';
export class UmbUfmLabelValueComponent extends UmbUfmComponentBase {
render(token: Tokens.Generic) {
return `<ufm-label-value alias="${token.text}"></ufm-label-value>`;
}
}
export { UmbUfmLabelValueComponent as api };

View File

@@ -0,0 +1,44 @@
import { UMB_UFM_RENDER_CONTEXT } from '../components/ufm-render/index.js';
import { customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
const elementName = 'ufm-label-value';
@customElement(elementName)
export class UmbUfmLabelValueElement extends UmbLitElement {
@property()
alias?: string;
@state()
private _value?: unknown;
constructor() {
super();
this.consumeContext(UMB_UFM_RENDER_CONTEXT, (context) => {
this.observe(
context.value,
(value) => {
if (this.alias !== undefined && value !== undefined && typeof value === 'object') {
this._value = (value as Record<string, unknown>)[this.alias];
} else {
this._value = value;
}
},
'observeValue',
);
});
}
override render() {
return this._value ?? `{${this.alias}}`;
}
}
export { UmbUfmLabelValueElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbUfmLabelValueElement;
}
}

View File

@@ -0,0 +1,10 @@
import { UmbUfmComponentBase } from './ufm-component-base.js';
import type { Tokens } from '@umbraco-cms/backoffice/external/marked';
export class UmbUfmLocalizeComponent extends UmbUfmComponentBase {
render(token: Tokens.Generic) {
return `<umb-localize key="${token.text}"></umb-localize>`;
}
}
export { UmbUfmLocalizeComponent as api };

View File

@@ -0,0 +1,7 @@
import type { Tokens } from '@umbraco-cms/backoffice/external/marked';
import type { UmbUfmComponentApi } from '@umbraco-cms/backoffice/extension-registry';
export abstract class UmbUfmComponentBase implements UmbUfmComponentApi {
abstract render(token: Tokens.Generic): string | undefined;
destroy() {}
}

View File

@@ -0,0 +1,8 @@
export const extensions = [
{
name: 'Umbraco Flavored Markdown Bundle',
alias: 'Umb.Bundle.UmbracoFlavoredMarkdown',
type: 'bundle',
js: () => import('./manifests.js'),
},
];

View File

@@ -112,6 +112,7 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js
"@umbraco-cms/backoffice/themes": ["./src/packages/core/themes/index.ts"],
"@umbraco-cms/backoffice/tiny-mce": ["./src/packages/tiny-mce/index.ts"],
"@umbraco-cms/backoffice/tree": ["./src/packages/core/tree/index.ts"],
"@umbraco-cms/backoffice/ufm": ["./src/packages/ufm/index.ts"],
"@umbraco-cms/backoffice/user-group": ["./src/packages/user/user-group/index.ts"],
"@umbraco-cms/backoffice/user-permission": ["./src/packages/user/user-permission/index.ts"],
"@umbraco-cms/backoffice/user": ["./src/packages/user/user/index.ts"],