|
|
|
|
@@ -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: ``,
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
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, <Ctrl+B>"
|
|
|
|
|
@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, <Ctrl+I>"
|
|
|
|
|
@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, <Ctrl+Shift+.>"
|
|
|
|
|
@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, <Ctrl+Shift+7>"
|
|
|
|
|
@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, <Ctrl+Shift+8>"
|
|
|
|
|
@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, <Ctrl+E>"
|
|
|
|
|
@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, <Ctrl+K>"
|
|
|
|
|
@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);
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|