From 007ee1adb60a3cdef430ef3c9393ff9b6931bf57 Mon Sep 17 00:00:00 2001 From: b-kelly Date: Mon, 21 Mar 2022 09:36:57 -0400 Subject: [PATCH 1/7] feat: add language dropdown to code_block nodes --- src/rich-text/editor.ts | 8 +- src/rich-text/node-views/code-block.ts | 88 +++++++++++++++++---- src/shared/highlighting/highlight-plugin.ts | 84 +++++++++++--------- 3 files changed, 125 insertions(+), 55 deletions(-) diff --git a/src/rich-text/editor.ts b/src/rich-text/editor.ts index eb580699..3ccd79e0 100644 --- a/src/rich-text/editor.ts +++ b/src/rich-text/editor.ts @@ -103,8 +103,12 @@ export class RichTextEditor extends BaseView { ], }), nodeViews: { - code_block(node: ProseMirrorNode) { - return new CodeBlockView(node); + code_block( + node: ProseMirrorNode, + view: EditorView, + getPos: () => number + ) { + return new CodeBlockView(node, view, getPos); }, image( node: ProseMirrorNode, diff --git a/src/rich-text/node-views/code-block.ts b/src/rich-text/node-views/code-block.ts index cee27b8d..ec77b3f0 100644 --- a/src/rich-text/node-views/code-block.ts +++ b/src/rich-text/node-views/code-block.ts @@ -1,8 +1,13 @@ import { Node as ProsemirrorNode } from "prosemirror-model"; -import { NodeView } from "prosemirror-view"; -import { getBlockLanguage } from "../../shared/highlighting/highlight-plugin"; +import { EditorView, NodeView } from "prosemirror-view"; +import { + getBlockLanguage, + getLoadedLanguages, +} from "../../shared/highlighting/highlight-plugin"; import { escapeHTML } from "../../shared/utils"; +type getPosParam = boolean | (() => number); + /** * View with wrapping/decorations for code_block nodes */ @@ -10,9 +15,9 @@ export class CodeBlockView implements NodeView { dom?: HTMLElement | null; contentDOM?: HTMLElement | null; - private language: string = null; + private language: ReturnType = null; - constructor(node: ProsemirrorNode) { + constructor(node: ProsemirrorNode, view: EditorView, getPos: getPosParam) { this.dom = document.createElement("div"); this.dom.classList.add("ps-relative", "p0", "ws-normal", "ow-normal"); @@ -20,11 +25,14 @@ export class CodeBlockView implements NodeView { this.language = rawLanguage; this.dom.innerHTML = escapeHTML` -
${rawLanguage}
+ `; this.contentDOM = this.dom.querySelector(".content-dom"); + + this.initializeLanguageSelect(view, getPos); + this.updateDisplayedLanguage(); } update(node: ProsemirrorNode): boolean { @@ -35,24 +43,74 @@ export class CodeBlockView implements NodeView { const rawLanguage = this.getLanguageFromBlock(node); - if (this.language !== rawLanguage) { - this.dom.querySelector(".js-language-indicator").textContent = - rawLanguage; + if (this.language.raw !== rawLanguage.raw) { this.language = rawLanguage; + this.updateDisplayedLanguage(); } return true; } - private getLanguageFromBlock(node: ProsemirrorNode) { - let autodetectedLanguage = node.attrs - .detectedHighlightLanguage as string; + private initializeLanguageSelect(view: EditorView, getPos: getPosParam) { + const $sel = + this.dom.querySelector(".js-lang-select"); + + // add an "auto" dropdown that we can target via JS + const autoOpt = document.createElement("option"); + autoOpt.textContent = "auto"; + autoOpt.value = "auto"; + autoOpt.className = "js-auto-option"; + $sel.appendChild(autoOpt); - if (autodetectedLanguage) { - // TODO localization - autodetectedLanguage += " (auto)"; + getLoadedLanguages().forEach((lang) => { + const opt = document.createElement("option"); + opt.value = lang; + opt.textContent = lang; + opt.defaultSelected = lang === this.language.raw; + $sel.appendChild(opt); + }); + + if (typeof getPos !== "function") { + return; } - return autodetectedLanguage || getBlockLanguage(node); + // when the dropdown is changed, update the language on the node + $sel.addEventListener("change", (e) => { + e.stopPropagation(); + + const newLang = $sel.value; + + view.dispatch( + view.state.tr.setNodeMarkup(getPos(), null, { + params: newLang === "auto" ? null : newLang, + detectedHighlightLanguage: null, + }) + ); + }); + } + + private updateDisplayedLanguage() { + const lang = this.language.raw; + const $sel = + this.dom.querySelector(".js-lang-select"); + const $auto = $sel.querySelector(".js-auto-option"); + + if (this.language.autodetected) { + $sel.value = "auto"; + $auto.textContent = lang + " (auto)"; + } else { + $sel.value = lang; + $auto.textContent = "auto"; + } + } + + private getLanguageFromBlock(node: ProsemirrorNode) { + const autodetectedLanguage = node.attrs + .detectedHighlightLanguage as string; + + return { + raw: autodetectedLanguage || getBlockLanguage(node, "auto"), + autodetected: !!autodetectedLanguage, + }; } } diff --git a/src/shared/highlighting/highlight-plugin.ts b/src/shared/highlighting/highlight-plugin.ts index 68bb70a5..5c931bca 100644 --- a/src/shared/highlighting/highlight-plugin.ts +++ b/src/shared/highlighting/highlight-plugin.ts @@ -7,44 +7,47 @@ import { getHljsInstance } from "./hljs-instance"; * Register the languages we're going to use here so we can strongly type our inputs */ //TODO missing: regex -type Language = - | "markdown" - | "bash" - | "cpp" - | "csharp" - | "coffeescript" - | "xml" - | "java" - | "json" - | "perl" - | "python" - | "ruby" - | "clojure" - | "css" - | "dart" - | "erlang" - | "go" - | "haskell" - | "javascript" - | "kotlin" - | "tex" - | "lisp" - | "scheme" - | "lua" - | "matlab" - | "mathematica" - | "ocaml" - | "pascal" - | "protobuf" - | "r" - | "rust" - | "scala" - | "sql" - | "swift" - | "vhdl" - | "vbscript" - | "yml" - | "none"; +const SUPPORTED_LANGS = [ + "plaintext", + "markdown", + "bash", + "cpp", + "csharp", + "coffeescript", + "xml", + "java", + "json", + "perl", + "python", + "ruby", + "clojure", + "css", + "dart", + "erlang", + "go", + "haskell", + "javascript", + "kotlin", + "tex", + "lisp", + "scheme", + "lua", + "matlab", + "mathematica", + "ocaml", + "pascal", + "protobuf", + "r", + "rust", + "scala", + "sql", + "swift", + "vhdl", + "vbscript", + "yml", +] as const; + +type Language = typeof SUPPORTED_LANGS[number]; // Aliases are neatly grouped onto the same line, so tell prettier not to format // prettier-ignore @@ -103,6 +106,11 @@ export function getBlockLanguage( return dealiasLangauge(rawLanguage); } +/** TODO DOCUMENT */ +export function getLoadedLanguages() { + return SUPPORTED_LANGS; +} + /** * Plugin that highlights all code within all code_blocks in the parent */ From 1da8fbea52fc2e085ec205b3b46610da28f6f02b Mon Sep 17 00:00:00 2001 From: b-kelly Date: Tue, 22 Mar 2022 13:11:18 -0400 Subject: [PATCH 2/7] docs: comment cleanup --- src/rich-text/node-views/code-block.ts | 1 + src/shared/highlighting/highlight-plugin.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rich-text/node-views/code-block.ts b/src/rich-text/node-views/code-block.ts index ec77b3f0..15e5a889 100644 --- a/src/rich-text/node-views/code-block.ts +++ b/src/rich-text/node-views/code-block.ts @@ -97,6 +97,7 @@ export class CodeBlockView implements NodeView { if (this.language.autodetected) { $sel.value = "auto"; + // TODO localization $auto.textContent = lang + " (auto)"; } else { $sel.value = lang; diff --git a/src/shared/highlighting/highlight-plugin.ts b/src/shared/highlighting/highlight-plugin.ts index 5c931bca..d908ef0c 100644 --- a/src/shared/highlighting/highlight-plugin.ts +++ b/src/shared/highlighting/highlight-plugin.ts @@ -106,7 +106,7 @@ export function getBlockLanguage( return dealiasLangauge(rawLanguage); } -/** TODO DOCUMENT */ +/** Returns all supported language codes */ export function getLoadedLanguages() { return SUPPORTED_LANGS; } From 2bfe13a5c8a0231925502229a1b5664ec1d41d5b Mon Sep 17 00:00:00 2001 From: Aaron Shekey Date: Tue, 22 Mar 2022 16:33:49 -0500 Subject: [PATCH 3/7] fix: use a stacks `select` --- src/rich-text/node-views/code-block.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rich-text/node-views/code-block.ts b/src/rich-text/node-views/code-block.ts index 15e5a889..d94ffd1f 100644 --- a/src/rich-text/node-views/code-block.ts +++ b/src/rich-text/node-views/code-block.ts @@ -26,7 +26,7 @@ export class CodeBlockView implements NodeView { this.dom.innerHTML = escapeHTML`
- +
`; this.contentDOM = this.dom.querySelector(".content-dom"); From f5654f6872076ffbb280716f31ec698ad7e50876 Mon Sep 17 00:00:00 2001 From: Aaron Shekey Date: Tue, 22 Mar 2022 16:34:05 -0500 Subject: [PATCH 4/7] chore: update package-json.lock --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6ee6b87..06ef1a4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@types/prosemirror-state": "^1.2.8", "@types/prosemirror-view": "^1.23.1", "markdown-it": "^12.3.2", - "prosemirror-commands": "^1.2.1", + "prosemirror-commands": "^1.2.2", "prosemirror-highlightjs": "^0.7.2", "prosemirror-history": "^1.2.0", "prosemirror-inputrules": "^1.1.3", @@ -11231,9 +11231,9 @@ } }, "node_modules/prosemirror-commands": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.1.tgz", - "integrity": "sha512-S/IkpXfpuLFsRynC2HQ5iYROUPiZskKS1+ClcWycGJvj4HMb/mVfeEkQrixYxgTl96EAh+RZQNWPC06GZXk5tQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.2.tgz", + "integrity": "sha512-TX+KpWudMon06frryfpO/u7hsQv2hu8L4VSVbCpi3/7wXHBgl+35mV85qfa3RpT8xD2f3MdeoTqH0vy5JdbXPg==", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", @@ -22913,9 +22913,9 @@ } }, "prosemirror-commands": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.1.tgz", - "integrity": "sha512-S/IkpXfpuLFsRynC2HQ5iYROUPiZskKS1+ClcWycGJvj4HMb/mVfeEkQrixYxgTl96EAh+RZQNWPC06GZXk5tQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.2.tgz", + "integrity": "sha512-TX+KpWudMon06frryfpO/u7hsQv2hu8L4VSVbCpi3/7wXHBgl+35mV85qfa3RpT8xD2f3MdeoTqH0vy5JdbXPg==", "requires": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", From 93fc5bec1081467eab2d7717b1c398dc0b015177 Mon Sep 17 00:00:00 2001 From: b-kelly Date: Wed, 23 Mar 2022 11:48:38 -0400 Subject: [PATCH 5/7] revert: changes to package-lock.json --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 06ef1a4e..a6ee6b87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@types/prosemirror-state": "^1.2.8", "@types/prosemirror-view": "^1.23.1", "markdown-it": "^12.3.2", - "prosemirror-commands": "^1.2.2", + "prosemirror-commands": "^1.2.1", "prosemirror-highlightjs": "^0.7.2", "prosemirror-history": "^1.2.0", "prosemirror-inputrules": "^1.1.3", @@ -11231,9 +11231,9 @@ } }, "node_modules/prosemirror-commands": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.2.tgz", - "integrity": "sha512-TX+KpWudMon06frryfpO/u7hsQv2hu8L4VSVbCpi3/7wXHBgl+35mV85qfa3RpT8xD2f3MdeoTqH0vy5JdbXPg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.1.tgz", + "integrity": "sha512-S/IkpXfpuLFsRynC2HQ5iYROUPiZskKS1+ClcWycGJvj4HMb/mVfeEkQrixYxgTl96EAh+RZQNWPC06GZXk5tQ==", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", @@ -22913,9 +22913,9 @@ } }, "prosemirror-commands": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.2.tgz", - "integrity": "sha512-TX+KpWudMon06frryfpO/u7hsQv2hu8L4VSVbCpi3/7wXHBgl+35mV85qfa3RpT8xD2f3MdeoTqH0vy5JdbXPg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.1.tgz", + "integrity": "sha512-S/IkpXfpuLFsRynC2HQ5iYROUPiZskKS1+ClcWycGJvj4HMb/mVfeEkQrixYxgTl96EAh+RZQNWPC06GZXk5tQ==", "requires": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", From fcc59ecbaa49d2381ce008d4bbd30984223eff9a Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Tue, 10 May 2022 14:23:18 -0400 Subject: [PATCH 6/7] fix: add translation for added string --- src/rich-text/node-views/code-block.ts | 3 +-- src/shared/localization.ts | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rich-text/node-views/code-block.ts b/src/rich-text/node-views/code-block.ts index fc2e11b1..21ba5669 100644 --- a/src/rich-text/node-views/code-block.ts +++ b/src/rich-text/node-views/code-block.ts @@ -103,8 +103,7 @@ export class CodeBlockView implements NodeView { }); } else { $sel.value = lang; - // TODO localization - $auto.textContent = "auto"; + $auto.textContent = _t("nodes.codeblock_auto"); } } diff --git a/src/shared/localization.ts b/src/shared/localization.ts index 1fbfb865..98980f95 100644 --- a/src/shared/localization.ts +++ b/src/shared/localization.ts @@ -20,6 +20,7 @@ export const defaultStrings = { mode_toggle_title: "Toggle Markdown mode" as string, }, nodes: { + codeblock_auto: "auto" as string, codeblock_lang_auto: ({ lang }: { lang: string }) => `${lang} (auto)`, spoiler_reveal_text: "Reveal spoiler" as string, }, From a852d7a3c8312c3cbd4aa09026de342089b71cbe Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Fri, 16 Sep 2022 11:55:22 -0400 Subject: [PATCH 7/7] fix (intentionally) bad merge --- src/rich-text/node-views/code-block.ts | 138 ++++++++++++------------- 1 file changed, 67 insertions(+), 71 deletions(-) diff --git a/src/rich-text/node-views/code-block.ts b/src/rich-text/node-views/code-block.ts index 59d0f32d..ee56a193 100644 --- a/src/rich-text/node-views/code-block.ts +++ b/src/rich-text/node-views/code-block.ts @@ -27,15 +27,7 @@ export class CodeBlockView implements NodeView { ) { this.dom = document.createElement("div"); this.dom.classList.add("ps-relative", "p0", "ws-normal", "ow-normal"); - - const rawLanguage = this.getLanguageFromBlock(node); - this.language = rawLanguage; - - this.dom.innerHTML = escapeHTML` -
${rawLanguage}
-
- `; - + this.render(view, getPos); this.contentDOM = this.dom.querySelector(".content-dom"); this.update(node); } @@ -49,7 +41,7 @@ export class CodeBlockView implements NodeView { const rawLanguage = this.getLanguageFromBlock(node); const processorApplies = this.getValidProcessorResult( - rawLanguage, + rawLanguage.raw, node ); @@ -68,7 +60,6 @@ export class CodeBlockView implements NodeView { const randomId = generateRandomId(); this.dom.innerHTML = escapeHTML` -
-
`; +
+
`; this.contentDOM = this.dom.querySelector(".content-dom"); @@ -102,6 +94,8 @@ export class CodeBlockView implements NodeView { }) ); }); + + this.initializeLanguageSelect(view, getPos); } /** Switches the view between editor mode and processor mode */ @@ -116,61 +110,6 @@ export class CodeBlockView implements NodeView { } /** Gets the codeblock language from the node */ - private getLanguageFromBlock(node: ProsemirrorNode) { - let autodetectedLanguage = node.attrs - .detectedHighlightLanguage as string; - - // add an "auto" dropdown that we can target via JS - const autoOpt = document.createElement("option"); - autoOpt.textContent = "auto"; - autoOpt.value = "auto"; - autoOpt.className = "js-auto-option"; - $sel.appendChild(autoOpt); - - getLoadedLanguages().forEach((lang) => { - const opt = document.createElement("option"); - opt.value = lang; - opt.textContent = lang; - opt.defaultSelected = lang === this.language.raw; - $sel.appendChild(opt); - }); - - if (typeof getPos !== "function") { - return; - } - - // when the dropdown is changed, update the language on the node - $sel.addEventListener("change", (e) => { - e.stopPropagation(); - - const newLang = $sel.value; - - view.dispatch( - view.state.tr.setNodeMarkup(getPos(), null, { - params: newLang === "auto" ? null : newLang, - detectedHighlightLanguage: null, - }) - ); - }); - } - - private updateDisplayedLanguage() { - const lang = this.language.raw; - const $sel = - this.dom.querySelector(".js-lang-select"); - const $auto = $sel.querySelector(".js-auto-option"); - - if (this.language.autodetected) { - $sel.value = "auto"; - $auto.textContent = _t("nodes.codeblock_lang_auto", { - lang, - }); - } else { - $sel.value = lang; - $auto.textContent = _t("nodes.codeblock_auto"); - } - } - private getLanguageFromBlock(node: ProsemirrorNode) { const autodetectedLanguage = node.attrs .detectedHighlightLanguage as string; @@ -182,11 +121,13 @@ export class CodeBlockView implements NodeView { } /** Updates the edit/code view */ - private updateCodeBlock(rawLanguage: string) { - if (this.language !== rawLanguage) { - this.dom.querySelector(".js-language-indicator").textContent = - rawLanguage; + private updateCodeBlock(rawLanguage: CodeBlockView["language"]) { + if (this.language?.raw !== rawLanguage.raw) { + this.dom.querySelector( + ".js-language-indicator" + ).value = rawLanguage.raw; this.language = rawLanguage; + this.updateDisplayedLanguage(); } } @@ -245,4 +186,59 @@ export class CodeBlockView implements NodeView { return processors; } + + private initializeLanguageSelect(view: EditorView, getPos: getPosParam) { + const $sel = + this.dom.querySelector(".js-lang-select"); + + // add an "auto" dropdown that we can target via JS + const autoOpt = document.createElement("option"); + autoOpt.textContent = "auto"; + autoOpt.value = "auto"; + autoOpt.className = "js-auto-option"; + $sel.appendChild(autoOpt); + + getLoadedLanguages().forEach((lang) => { + const opt = document.createElement("option"); + opt.value = lang; + opt.textContent = lang; + opt.defaultSelected = lang === this.language?.raw; + $sel.appendChild(opt); + }); + + if (typeof getPos !== "function") { + return; + } + + // when the dropdown is changed, update the language on the node + $sel.addEventListener("change", (e) => { + e.stopPropagation(); + + const newLang = $sel.value; + + view.dispatch( + view.state.tr.setNodeMarkup(getPos(), null, { + params: newLang === "auto" ? null : newLang, + detectedHighlightLanguage: null, + }) + ); + }); + } + + private updateDisplayedLanguage() { + const lang = this.language?.raw; + const $sel = + this.dom.querySelector(".js-lang-select"); + const $auto = $sel.querySelector(".js-auto-option"); + + if (this.language?.autodetected) { + $sel.value = "auto"; + $auto.textContent = _t("nodes.codeblock_lang_auto", { + lang, + }); + } else { + $sel.value = lang; + $auto.textContent = _t("nodes.codeblock_auto"); + } + } }