From 326585f49f10f63b3c801f53bbd87bab703286c4 Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Tue, 24 Oct 2023 14:12:39 +0200
Subject: [PATCH 1/4] init
---
.../input-markdown.element.ts | 90 ++++++++++++-------
1 file changed, 57 insertions(+), 33 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
index 0b66230a0c..805dd1e32e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
@@ -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, (something) => {
+ this.serverUrl = something.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.
});
}
@@ -232,13 +235,16 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
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')})` },
+ {
+ range: selection,
+ text: ``,
+ },
]);
if (!alt?.length) {
this.#editor?.select({
startColumn: selection.startColumn + 1,
- endColumn: selection.startColumn + 9,
+ endColumn: selection.startColumn + 10,
endLineNumber: selection.startLineNumber,
startLineNumber: selection.startLineNumber,
});
@@ -413,7 +419,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
@@ -421,7 +427,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Bold"
- title="Bold"
+ title="Bold, <Ctrl+B>"
@click=${() => this.#editor?.monacoEditor?.getAction('b')?.run()}>
B
@@ -429,7 +435,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Italic"
- title="Italic"
+ title="Italic, <Ctrl+I>"
@click=${() => this.#editor?.monacoEditor?.getAction('i')?.run()}>
I
@@ -439,7 +445,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Quote"
- title="Quote"
+ title="Quote, <Ctrl+Shift+.>"
@click=${() => this.#editor?.monacoEditor?.getAction('q')?.run()}>
@@ -447,7 +453,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Ordered List"
- title="Ordered List"
+ title="Ordered List, <Ctrl+Shift+7>"
@click=${() => this.#editor?.monacoEditor?.getAction('ol')?.run()}>
@@ -455,7 +461,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
compact
look="secondary"
label="Unordered List"
- title="Unordered List"
+ title="Unordered List, <Ctrl+Shift+8>"
@click=${() => this.#editor?.monacoEditor?.getAction('ul')?.run()}>
@@ -464,9 +470,9 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
this.#editor?.monacoEditor?.getAction('fenced-code')?.run()}>
+ label="Code"
+ title="Code, <Ctrl+E>"
+ @click=${() => this.#editor?.monacoEditor?.getAction('code')?.run()}>
this.#editor?.monacoEditor?.getAction('link')?.run()}>
@@ -533,7 +539,6 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
}
render() {
- //TODO: Why is the theme dark in Backoffice, but light in Storybook?
return html`
${this._renderBasicActions()}
- ${unsafeHTML(sanitizeHtml(marked.parse(this.value as string)))}
+ ${unsafeHTML(
+ sanitizeHtml(marked.parse((this.value as string) || ''), {
+ allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
+ allowedAttributes: { img: ['src'] },
+ allowedSchemes: ['http', 'https'],
+ }),
+ )}
`;
}
@@ -592,6 +603,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);
+ }
`,
];
}
From 93b6cd668d756d39395a98568200c47cf3ee2246 Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Tue, 24 Oct 2023 15:38:14 +0200
Subject: [PATCH 2/4] fix preview and select of img alt
---
.../input-markdown.element.ts | 23 ++++++++-----------
1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
index 805dd1e32e..8187f1a484 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
@@ -224,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, {});
@@ -234,21 +234,18 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
.then((data) => {
const imgUrl = data.selection[0];
this.#editor?.monacoEditor?.executeEdits('', [
- //TODO: media url
+ //TODO: Get the correct media URL
{
range: selection,
- text: ``,
+ text: ``,
},
]);
-
- if (!alt?.length) {
- this.#editor?.select({
- startColumn: selection.startColumn + 1,
- endColumn: selection.startColumn + 10,
- 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());
@@ -550,7 +547,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
}
renderPreview() {
- if (this.preview) return;
+ if (!this.preview) return;
return html`
${unsafeHTML(
sanitizeHtml(marked.parse((this.value as string) || ''), {
From 7cdaaca1318a41c785c8f15d162296ba28090e01 Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Tue, 24 Oct 2023 15:45:37 +0200
Subject: [PATCH 3/4] rename
---
.../input-markdown-editor/input-markdown.element.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
index 8187f1a484..62ebcae773 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
@@ -49,8 +49,8 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => {
this._modalContext = instance;
});
- this.consumeContext(UMB_APP, (something) => {
- this.serverUrl = something.getServerUrl();
+ this.consumeContext(UMB_APP, (instance) => {
+ this.serverUrl = instance.getServerUrl();
});
}
From eae92c2e029cf1e23b892824fc084ab968a0ddb2 Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Wed, 8 Nov 2023 12:32:11 +0100
Subject: [PATCH 4/4] rename sanitizehtml to dompurify
---
.../src/external/{sanitize-html => dompurify}/index.ts | 4 +---
.../input-markdown-editor/input-markdown.element.ts | 4 ++--
src/Umbraco.Web.UI.Client/tsconfig.json | 2 +-
src/Umbraco.Web.UI.Client/web-test-runner.config.mjs | 2 +-
4 files changed, 5 insertions(+), 7 deletions(-)
rename src/Umbraco.Web.UI.Client/src/external/{sanitize-html => dompurify}/index.ts (59%)
diff --git a/src/Umbraco.Web.UI.Client/src/external/sanitize-html/index.ts b/src/Umbraco.Web.UI.Client/src/external/dompurify/index.ts
similarity index 59%
rename from src/Umbraco.Web.UI.Client/src/external/sanitize-html/index.ts
rename to src/Umbraco.Web.UI.Client/src/external/dompurify/index.ts
index c05512948b..2a816a45c3 100644
--- a/src/Umbraco.Web.UI.Client/src/external/sanitize-html/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/dompurify/index.ts
@@ -1,6 +1,4 @@
/* eslint local-rules/enforce-umbraco-external-imports: 0 */
import DOMPurify from 'dompurify';
-const sanitizeHtml = DOMPurify.sanitize;
-
-export { sanitizeHtml };
+export { DOMPurify };
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
index 4a176dcffa..cb50430223 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-markdown-editor/input-markdown.element.ts
@@ -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';
@@ -548,7 +548,7 @@ export class UmbInputMarkdownElement extends FormControlMixin(UmbLitElement) {
renderPreview(markdown: string) {
const markdownAsHtml = marked.parse(markdown);
- const sanitizedHtml = markdownAsHtml ? sanitizeHtml(markdownAsHtml) : '';
+ const sanitizedHtml = markdownAsHtml ? DOMPurify.sanitize(markdownAsHtml) : '';
return html` ${unsafeHTML(sanitizedHtml)} `;
}
diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json
index 2dc002fed3..2d036fc825 100644
--- a/src/Umbraco.Web.UI.Client/tsconfig.json
+++ b/src/Umbraco.Web.UI.Client/tsconfig.json
@@ -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"],
diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs
index 6495958a88..53d2347db4 100644
--- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs
+++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs
@@ -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',