Merge remote-tracking branch 'origin/main' into feature/extension-api-controller

This commit is contained in:
Niels Lyngsø
2023-11-08 16:03:11 +01:00
4 changed files with 62 additions and 51 deletions

View File

@@ -1,6 +1,4 @@
/* eslint local-rules/enforce-umbraco-external-imports: 0 */
import DOMPurify from 'dompurify';
const sanitizeHtml = DOMPurify.sanitize;
export { sanitizeHtml };
export { DOMPurify };

View File

@@ -1,4 +1,4 @@
import { sanitizeHtml } from '@umbraco-cms/backoffice/external/sanitize-html';
import { DOMPurify } from '@umbraco-cms/backoffice/external/dompurify';
import { marked } from '@umbraco-cms/backoffice/external/marked';
import { monaco } from '@umbraco-cms/backoffice/external/monaco-editor';
import { UmbCodeEditorController, UmbCodeEditorElement, loadCodeEditor } from '@umbraco-cms/backoffice/code-editor';
@@ -12,6 +12,7 @@ import {
UMB_MODAL_MANAGER_CONTEXT_TOKEN,
UmbModalManagerContext,
} from '@umbraco-cms/backoffice/modal';
import { UMB_APP } from '@umbraco-cms/backoffice/app';
/**
* @element umb-input-markdown
@@ -23,6 +24,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
protected getFormElement() {
return this._codeEditor;
}
// TODO: Make actions be able to handle multiple selection
@property({ type: Boolean })
@@ -39,12 +41,17 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
private _modalContext?: UmbModalManagerContext;
private serverUrl?: string;
constructor() {
super();
this.#loadCodeEditor();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => {
this._modalContext = instance;
});
this.consumeContext(UMB_APP, (instance) => {
this.serverUrl = instance.getServerUrl();
});
}
async #loadCodeEditor() {
@@ -67,42 +74,41 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
}
async #loadActions() {
// TODO: Find a way to have "double" keybindings (ctrl+m+ctrl+c for `code`, rather than simple ctrl+c as its taken by OS to copy things)
// Going with the keybindings of a Markdown Shortcut plugin https://marketplace.visualstudio.com/items?itemName=robole.markdown-shortcuts#shortcuts or perhaps there are keybindings that would make more sense.
//Note: UI Buttons have the keybindings hardcoded in its title. If you change the keybindings here, please update the render as well.
this.#editor?.monacoEditor?.addAction({
label: 'Add Heading H1',
id: 'h1',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Digit1],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Digit1],
run: () => this._insertAtCurrentLine('# '),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Heading H2',
id: 'h2',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Digit2],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Digit2],
run: () => this._insertAtCurrentLine('## '),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Heading H3',
id: 'h3',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Digit3],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Digit3],
run: () => this._insertAtCurrentLine('### '),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Heading H4',
id: 'h4',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Digit4],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Digit4],
run: () => this._insertAtCurrentLine('#### '),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Heading H5',
id: 'h5',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Digit5],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Digit5],
run: () => this._insertAtCurrentLine('##### '),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Heading H6',
id: 'h6',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Digit6],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Digit6],
run: () => this._insertAtCurrentLine('###### '),
});
this.#editor?.monacoEditor?.addAction({
@@ -120,52 +126,49 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
this.#editor?.monacoEditor?.addAction({
label: 'Add Quote',
id: 'q',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyQ],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Period],
run: () => this._insertQuote(),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Ordered List',
id: 'ol',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyO],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Digit7],
run: () => this._insertAtCurrentLine('1. '),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Unordered List',
id: 'ul',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyU],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Digit8],
run: () => this._insertAtCurrentLine('- '),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Code',
id: 'code',
//keybindings: [KeyMod.CtrlCmd | KeyCode.KeyM | KeyMod.CtrlCmd | KeyCode.KeyC],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyE],
run: () => this._insertBetweenSelection('`', '`', 'Code'),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Fenced Code',
id: 'fenced-code',
//keybindings: [KeyMod.CtrlCmd | KeyCode.KeyM | KeyMod.CtrlCmd | KeyCode.KeyF],
run: () => this._insertBetweenSelection('```', '```', 'Code'),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Line',
id: 'line',
//keybindings: [KeyMod.CtrlCmd | KeyCode.KeyM | KeyMod.CtrlCmd | KeyCode.KeyC],
run: () => this._insertLine(),
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Link',
id: 'link',
//keybindings: [KeyMod.CtrlCmd | KeyCode.KeyM | KeyMod.CtrlCmd | KeyCode.KeyC],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK],
run: () => this._insertLink(),
// TODO: Open in modal
});
this.#editor?.monacoEditor?.addAction({
label: 'Add Image',
id: 'image',
//keybindings: [KeyMod.CtrlCmd | KeyCode.KeyM | KeyMod.CtrlCmd | KeyCode.KeyC],
//keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyJ], // What keybinding would be good for image?
run: () => this._insertMedia(),
// TODO: Open in modal
// TODO: Update when media picker is complete.
});
}
@@ -221,7 +224,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
const selection = this.#editor?.getSelections()[0];
if (!selection) return;
const alt = this.#editor?.getValueInRange(selection);
const alt = this.#editor?.getValueInRange(selection) || 'alt text';
this._focusEditor(); // Focus before opening modal, otherwise cannot regain focus back after modal
const modalContext = this._modalContext?.open(UMB_MEDIA_TREE_PICKER_MODAL, {});
@@ -231,18 +234,18 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
.then((data) => {
const imgUrl = data.selection[0];
this.#editor?.monacoEditor?.executeEdits('', [
//TODO: media url
{ range: selection, text: `[${alt || 'alt text'}](TODO: id-${imgUrl || this.localize.term('general_url')})` },
//TODO: Get the correct media URL
{
range: selection,
text: `![${alt}](${imgUrl ? `${this.serverUrl}'/media/'${imgUrl}` : 'URL'})`,
},
]);
if (!alt?.length) {
this.#editor?.select({
startColumn: selection.startColumn + 1,
endColumn: selection.startColumn + 9,
endLineNumber: selection.startLineNumber,
startLineNumber: selection.startLineNumber,
});
}
this.#editor?.select({
startColumn: selection.startColumn + 2,
endColumn: selection.startColumn + alt.length + 2, // +2 because of ![
endLineNumber: selection.startLineNumber,
startLineNumber: selection.startLineNumber,
});
})
.catch(() => undefined)
.finally(() => this._focusEditor());
@@ -413,7 +416,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Heading"
title="Heading"
title="Heading, <Ctrl+Shift+1>"
@click=${() => this.#editor?.monacoEditor?.getAction('h1')?.run()}>
H
</uui-button>
@@ -421,7 +424,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Bold"
title="Bold"
title="Bold, &lt;Ctrl+B&gt;"
@click=${() => this.#editor?.monacoEditor?.getAction('b')?.run()}>
B
</uui-button>
@@ -429,7 +432,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Italic"
title="Italic"
title="Italic, &lt;Ctrl+I&gt;"
@click=${() => this.#editor?.monacoEditor?.getAction('i')?.run()}>
I
</uui-button>
@@ -439,7 +442,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Quote"
title="Quote"
title="Quote, &lt;Ctrl+Shift+.&gt;"
@click=${() => this.#editor?.monacoEditor?.getAction('q')?.run()}>
<uui-icon name="icon-quote"></uui-icon>
</uui-button>
@@ -447,7 +450,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Ordered List"
title="Ordered List"
title="Ordered List, &lt;Ctrl+Shift+7&gt;"
@click=${() => this.#editor?.monacoEditor?.getAction('ol')?.run()}>
<uui-icon name="icon-ordered-list"></uui-icon>
</uui-button>
@@ -455,7 +458,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Unordered List"
title="Unordered List"
title="Unordered List, &lt;Ctrl+Shift+8&gt;"
@click=${() => this.#editor?.monacoEditor?.getAction('ul')?.run()}>
<uui-icon name="icon-bulleted-list"></uui-icon>
</uui-button>
@@ -464,9 +467,9 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
<uui-button
compact
look="secondary"
label="Fenced Code"
title="Fenced Code"
@click=${() => this.#editor?.monacoEditor?.getAction('fenced-code')?.run()}>
label="Code"
title="Code, &lt;Ctrl+E&gt;"
@click=${() => this.#editor?.monacoEditor?.getAction('code')?.run()}>
<uui-icon name="icon-code"></uui-icon>
</uui-button>
<uui-button
@@ -481,7 +484,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Link"
title="Link"
title="Link, &lt;Ctrl+K&gt;"
@click=${() => this.#editor?.monacoEditor?.getAction('link')?.run()}>
<uui-icon name="icon-link"></uui-icon>
</uui-button>
@@ -533,7 +536,6 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
}
render() {
//TODO: Why is the theme dark in Backoffice, but light in Storybook?
return html` <div id="actions">${this._renderBasicActions()}</div>
<umb-code-editor
language="markdown"
@@ -546,10 +548,8 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
renderPreview(markdown: string) {
const markdownAsHtml = marked.parse(markdown);
const sanitizedHtml = markdownAsHtml ? sanitizeHtml(markdownAsHtml) : '';
return html`<uui-scroll-container id="preview">
${unsafeHTML(sanitizedHtml)}
</uui-scroll-container>`;
const sanitizedHtml = markdownAsHtml ? DOMPurify.sanitize(markdownAsHtml) : '';
return html`<uui-scroll-container id="preview"> ${unsafeHTML(sanitizedHtml)} </uui-scroll-container>`;
}
static styles = [
@@ -593,6 +593,19 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
margin-inline: 0;
padding-inline: var(--uui-size-3);
}
p > code,
pre {
border: 1px solid var(--uui-color-divider-emphasis);
border-radius: var(--uui-border-radius);
padding: 0 var(--uui-size-1);
background-color: var(--uui-color-background);
}
hr {
border: none;
border-bottom: 1px solid var(--uui-palette-cocoa-black);
}
`,
];
}

View File

@@ -32,7 +32,7 @@
"@umbraco-cms/backoffice/external/tinymce": ["src/external/tinymce"],
"@umbraco-cms/backoffice/external/uui": ["src/external/uui"],
"@umbraco-cms/backoffice/external/uuid": ["src/external/uuid"],
"@umbraco-cms/backoffice/external/sanitize-html": ["src/external/sanitize-html"],
"@umbraco-cms/backoffice/external/dompurify": ["src/external/dompurify"],
"@umbraco-cms/backoffice/external/marked": ["src/external/marked"],
"@umbraco-cms/backoffice/backend-api": ["src/external/backend-api"],

View File

@@ -35,7 +35,7 @@ export default {
'@umbraco-cms/backoffice/external/tinymce': './src/external/tinymce/index.ts',
'@umbraco-cms/backoffice/external/uui': './src/external/uui/index.ts',
'@umbraco-cms/backoffice/external/uuid': './src/external/uuid/index.ts',
'@umbraco-cms/backoffice/external/sanitize-html': './src/external/sanitize-html/index.ts',
'@umbraco-cms/backoffice/external/dompurify': './src/external/dompurify/index.ts',
'@umbraco-cms/backoffice/external/marked': './src/external/marked/index.ts',
'@umbraco-cms/backoffice/backend-api': './src/external/backend-api/index.ts',