Feature: Umbraco Flavored Markdown
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 '';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>`;
|
||||
}
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -136,6 +136,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement {
|
||||
name: item.header,
|
||||
alias: item.alias,
|
||||
elementName: item.elementName,
|
||||
labelTemplate: item.nameTemplate,
|
||||
allowSorting: true,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './ufm-render.element.js';
|
||||
export { UMB_UFM_RENDER_CONTEXT } from './ufm-render.context.js';
|
||||
@@ -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');
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
2
src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts
Normal file
2
src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './components/ufm-render/index.js';
|
||||
export * from './plugins/marked-ufm.plugin.js';
|
||||
22
src/Umbraco.Web.UI.Client/src/packages/ufm/manifests.ts
Normal file
22
src/Umbraco.Web.UI.Client/src/packages/ufm/manifests.ts
Normal 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: '#',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -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,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
// }
|
||||
// }
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export const extensions = [
|
||||
{
|
||||
name: 'Umbraco Flavored Markdown Bundle',
|
||||
alias: 'Umb.Bundle.UmbracoFlavoredMarkdown',
|
||||
type: 'bundle',
|
||||
js: () => import('./manifests.js'),
|
||||
},
|
||||
];
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user