From 0c5f5c877e308033622f82137e1ff366e95f9873 Mon Sep 17 00:00:00 2001 From: Lila Rest Date: Sun, 29 Jan 2023 15:29:47 +0100 Subject: [PATCH] feat: entire rewrite of the Live Preview support Use a proper CodeMirror stateField to observe changes on the doc and render the CustomClasses behaviors through Decorators --- package-lock.json | 97 +++++++++-- package.json | 1 + src/live-preview-mode-old.ts | 212 +++++++++++++++++++++++ src/live-preview-mode.ts | 316 +++++++++++++++++------------------ src/main.ts | 4 +- styles.css | 16 ++ tsconfig.json | 3 +- 7 files changed, 468 insertions(+), 181 deletions(-) create mode 100644 src/live-preview-mode-old.ts diff --git a/package-lock.json b/package-lock.json index 4860316..05c188e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.10.0", "license": "MIT", "devDependencies": { + "@codemirror/language": "^6.4.0", "@types/node": "^16.11.6", "builtin-modules": "3.3.0", "cz-conventional-changelog": "^3.3.0", @@ -54,19 +55,31 @@ "node": ">=6.9.0" } }, + "node_modules/@codemirror/language": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz", + "integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==", + "dev": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, "node_modules/@codemirror/state": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.4.tgz", "integrity": "sha512-g+3OJuRylV5qsXuuhrc6Cvs1NQluNioepYMM2fhnpYkNk7NgX+j0AFuevKSVKzTDmDyt9+Puju+zPdHNECzCNQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@codemirror/view": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.5.1.tgz", "integrity": "sha512-xBKP8N3AXOs06VcKvIuvIQoUlGs7Hb78ftJWahLaRX909jKPMgGxR5XjvrawzTTZMSTU3DzdjDNPwG6fPM/ypQ==", "dev": true, - "peer": true, "dependencies": { "@codemirror/state": "^6.1.4", "style-mod": "^4.0.0", @@ -356,6 +369,30 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@lezer/common": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz", + "integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==", + "dev": true + }, + "node_modules/@lezer/highlight": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz", + "integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==", + "dev": true, + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.1.tgz", + "integrity": "sha512-+GymJB/+3gThkk2zHwseaJTI5oa4AuOuj1I2LCslAVq1dFZLSX8SAe4ZlJq1TjezteDXtF/+d4qeWz9JvnrG9Q==", + "dev": true, + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -3915,8 +3952,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/supports-color": { "version": "5.5.0", @@ -4139,8 +4175,7 @@ "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/wcwidth": { "version": "1.0.1", @@ -4335,19 +4370,31 @@ "js-tokens": "^4.0.0" } }, + "@codemirror/language": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz", + "integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==", + "dev": true, + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, "@codemirror/state": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.4.tgz", "integrity": "sha512-g+3OJuRylV5qsXuuhrc6Cvs1NQluNioepYMM2fhnpYkNk7NgX+j0AFuevKSVKzTDmDyt9+Puju+zPdHNECzCNQ==", - "dev": true, - "peer": true + "dev": true }, "@codemirror/view": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.5.1.tgz", "integrity": "sha512-xBKP8N3AXOs06VcKvIuvIQoUlGs7Hb78ftJWahLaRX909jKPMgGxR5XjvrawzTTZMSTU3DzdjDNPwG6fPM/ypQ==", "dev": true, - "peer": true, "requires": { "@codemirror/state": "^6.1.4", "style-mod": "^4.0.0", @@ -4575,6 +4622,30 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@lezer/common": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz", + "integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==", + "dev": true + }, + "@lezer/highlight": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz", + "integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==", + "dev": true, + "requires": { + "@lezer/common": "^1.0.0" + } + }, + "@lezer/lr": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.1.tgz", + "integrity": "sha512-+GymJB/+3gThkk2zHwseaJTI5oa4AuOuj1I2LCslAVq1dFZLSX8SAe4ZlJq1TjezteDXtF/+d4qeWz9JvnrG9Q==", + "dev": true, + "requires": { + "@lezer/common": "^1.0.0" + } + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -7188,8 +7259,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", - "dev": true, - "peer": true + "dev": true }, "supports-color": { "version": "5.5.0", @@ -7347,8 +7417,7 @@ "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", - "dev": true, - "peer": true + "dev": true }, "wcwidth": { "version": "1.0.1", diff --git a/package.json b/package.json index b675470..0a6cf74 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "author": "Lila Rest ", "license": "MIT", "devDependencies": { + "@codemirror/language": "^6.4.0", "@types/node": "^16.11.6", "builtin-modules": "3.3.0", "cz-conventional-changelog": "^3.3.0", diff --git a/src/live-preview-mode-old.ts b/src/live-preview-mode-old.ts new file mode 100644 index 0000000..0a192a7 --- /dev/null +++ b/src/live-preview-mode-old.ts @@ -0,0 +1,212 @@ +import { MarkdownRenderer } from "obsidian"; +import { plugin } from "./main"; +import { syntaxTree } from "@codemirror/language"; +import { RangeSetBuilder } from "@codemirror/state"; +import { + Decoration, + DecorationSet, + EditorView, + PluginSpec, + PluginValue, + ViewPlugin, + ViewUpdate, + WidgetType, +} from "@codemirror/view"; + +class DefaultRenderWidget extends WidgetType { + + constructor (customClass: string) { + super(); + } + + toDOM (view: EditorView): HTMLElement { + const div = document.createElement("div"); + + div.innerText = "👉"; + + return div; + } +} + +class HideWidget extends WidgetType { + toDOM () { + return document.createElement("div"); + } +} + +class CompatibilityModeRenderWidget extends WidgetType { + customClass: string; + livePreviewBlocks: Array; + + constructor (customClass: string, livePreviewBlocks: Array) { + super(); + this.customClass = customClass; + this.livePreviewBlocks = livePreviewBlocks; + console.log(livePreviewBlocks); + } + + toDOM (view: EditorView): HTMLElement { + // Create the Read mode render element + const readModeRender = document.createElement("div"); + readModeRender.classList.add( + "custom-classes-renderer", + this.customClass, + ); + + // Loop through every next block elements + let markdown = ""; + for (const element of this.livePreviewBlocks) { + + // Hide the element from the render + element.style.display = "none"; + + + // Build the element markdown + //@ts-ignore + console.log(update); + markdown += view.state.doc.line(view.contentDOM.indexOf(element) + 1).text + "\n"; + } + + // Render markdown into the custom class block + MarkdownRenderer.renderMarkdown( + markdown, + readModeRender, + "", + //@ts-ignore + null); + + return readModeRender; + } +} + +export class _CustomClassesPlugin implements PluginValue { + decorations: DecorationSet; + + constructor (view: EditorView) { + this.decorations = this.buildDecorations(view); + } + + update (update: ViewUpdate) { + if (update.selectionSet || update.docChanged || update.viewportChanged) { + this.decorations = this.buildDecorations(update.view); + } + } + + /** + * Returns an array of the blocks that are in the scope of a given custom class block + * @param classBlock HTMLElement + * @returns scopeBlocks Array + */ + getScopeBlocks (classBlock: HTMLElement) { + + // Create the array that will host all the blocs of the scope + const scopeBlocks: Array = []; + + // Retrieve the first block of the scope + const firstBlock = classBlock.nextElementSibling as HTMLElement; + + // If the first block is neither null nor a line break + if (firstBlock && firstBlock.innerHTML !== "
") { + + // Append it to the scope blocks list + scopeBlocks.push(firstBlock); + + // If the first block is a list item and the 'groupListItemInLivePreview' option is set + if (firstBlock.classList.contains("HyperMD-list-line") && plugin?.settings.get("compatibilityMode")) { + + // Retrieve the first block list type + const firstBlockListType = firstBlock.querySelector(".cm-formatting-list-ul") ? "ul" : "ol"; + + // And loop to append all others items of the list to scopeBlocks array + while (true) { + + // Retrieve nextBlock + const nextBlock = scopeBlocks[scopeBlocks.length - 1].nextElementSibling as HTMLElement; + + // If the next block is neither null nor a line break + if (nextBlock && firstBlock.innerHTML !== "
") { + + // Retrieve next block list type + const nextBlockListType = nextBlock.querySelector(".cm-formatting-list-ul") ? "ul" : "ol"; + + // Break if the nextBlock is null, or not a list item or not of the same list type + if (nextBlockListType !== firstBlockListType) break; + + // Else append the element + scopeBlocks.push(nextBlock); + } + else { + break; + } + } + } + } + + return scopeBlocks; + } + + buildDecorations (view: EditorView): DecorationSet { + const builder = new RangeSetBuilder(); + const blocksContainer = view.contentDOM; + + // Iterate over rendered blocks of the view + for (const block of blocksContainer.children) { + + // If the block is a custom class block + const codeBlock: HTMLElement | null = block.querySelector('span.cm-inline-code'); + if (codeBlock && codeBlock.innerText.trim().startsWith(plugin?.settings.get("customClassAnchor"))) { + + + // Retrieve the list of elements that composes the next block + const nextBlockElements = this.getScopeBlocks(block as HTMLElement); + + // If the custom class block target some elements + if (nextBlockElements.length > 0) { + + // Retrieve whether the codeBlock or the next block are active or not + const active = [block, ...nextBlockElements].find(el => el.classList.contains("cm-active")) ? true : false; + + // If the code block is not active render it + if (!active) { + + // Retrieve the custom class name + const customClass = codeBlock.innerText.trim().replace(plugin?.settings.get("customClassAnchor"), "").trim(); + + // If compatibility mode is enabled simulate reading mode render + if (plugin?.settings.get("compatibilityMode")) { + builder.add( + view.state.doc.line(blocksContainer.indexOf(block) + 1).from, + view.state.doc.line(blocksContainer.indexOf(nextBlockElements[nextBlockElements.length - 1]) + 1).to, + Decoration.replace({ + widget: new CompatibilityModeRenderWidget(customClass, nextBlockElements), + }) + ); + } + + // Else simply add the custom class to the next element sibling + else { + + // Hide the class block element + builder.add( + view.state.doc.line(blocksContainer.indexOf(block) + 1).from, + view.state.doc.line(blocksContainer.indexOf(block) + 1).to, + Decoration.replace(HideWidget) + ); + + // Add class to the next element + nextBlockElements[0].classList.add(customClass); + } + } + } + } + } + + return builder.finish(); + } +} + +const pluginSpec: PluginSpec<_CustomClassesPlugin> = { + decorations: (value: _CustomClassesPlugin) => value.decorations, +}; + +export const CustomClassesPlugin = ViewPlugin.fromClass(_CustomClassesPlugin, pluginSpec); \ No newline at end of file diff --git a/src/live-preview-mode.ts b/src/live-preview-mode.ts index 88554ea..f48cf7a 100644 --- a/src/live-preview-mode.ts +++ b/src/live-preview-mode.ts @@ -1,208 +1,196 @@ -import { - ViewUpdate, - PluginValue, - ViewPlugin, -} from "@codemirror/view"; import { MarkdownRenderer } from "obsidian"; import { plugin } from "./main"; +import { + Extension, + RangeSetBuilder, + StateField, + Transaction, +} from "@codemirror/state"; +import { + Decoration, + DecorationSet, + EditorView, + WidgetType, +} from "@codemirror/view"; -class _LivePreviewModeRenderer implements PluginValue { - prevEditingMode: string; - - - /** - * Returns an array of the blocks that are in the scope of a given custom class block - * @param classBlock HTMLElement - * @returns scopeBlocks Array - */ - getScopeBlocks (classBlock: HTMLElement) { - - // Create the array that will host all the blocs of the scope - const scopeBlocks: Array = []; - - // Retrieve the first block of the scope - const firstBlock = classBlock.nextElementSibling as HTMLElement; - - // If the first block is neither null nor a line break - if (firstBlock && firstBlock.innerHTML !== "
") { - - // Append it to the scope blocks list - scopeBlocks.push(firstBlock); - - // If the first block is a list item and the 'groupListItemInLivePreview' option is set - if (firstBlock.classList.contains("HyperMD-list-line") && plugin?.settings.get("compatibilityMode")) { - - // Retrieve the first block list type - const firstBlockListType = firstBlock.querySelector(".cm-formatting-list-ul") ? "ul" : "ol"; - - // And loop to append all others items of the list to scopeBlocks array - while (true) { - // Retrieve nextBlock - const nextBlock = scopeBlocks[scopeBlocks.length - 1].nextElementSibling as HTMLElement; +class CompatibilityModeRenderWidget extends WidgetType { + customClass: string; + lineNumber: number; + targettedLines: number; - // If the next block is neither null nor a line break - if (nextBlock && firstBlock.innerHTML !== "
") { + constructor (customClass: string, lineNumber: number, targettedLines: number) { + super(); + this.customClass = customClass; + this.lineNumber = lineNumber; + this.targettedLines = targettedLines; + } - // Retrieve next block list type - const nextBlockListType = nextBlock.querySelector(".cm-formatting-list-ul") ? "ul" : "ol"; + toDOM (view: EditorView): HTMLElement { + + // Create the Read mode render element + const readModeRender = document.createElement("div"); + readModeRender.classList.add( + "custom-classes-renderer", + this.customClass, + ); + + // Loop through every next block elements + let markdown = view.state.doc.slice( + view.state.doc.line(this.lineNumber + 1).from, + view.state.doc.line(this.lineNumber + this.targettedLines).to + //@ts-ignore + ).text.join("\n"); + + // Render markdown into the custom class block + MarkdownRenderer.renderMarkdown( + markdown, + readModeRender, + "", + //@ts-ignore + null); + + return readModeRender; + } - // Break if the nextBlock is null, or not a list item or not of the same list type - if (nextBlockListType !== firstBlockListType) break; + ignoreEvent (e: Event | MouseEvent) { - // Else append the element - scopeBlocks.push(nextBlock); - } - else { - break; - } - } + // Support clicks on links + if (e.type === "mousedown") { + e = e as MouseEvent; + //@ts-ignore + if (e.target?.nodeName === "A") { + e.preventDefault(); + return true; } } - - return scopeBlocks; + return false; } +} +function getTargettedLinesNumber (doc: any, lineNumber: number): number { + let numberOfLines = 0; + let lastLineWasList = false; + let lastListType = null; - update (update: ViewUpdate) { + for (let offset = 1; lineNumber + offset <= doc.lines; offset++) { - // Unhide hidden blocks if the user has switched from Live Preview mode to Source mode - const currentEditingMode = app?.workspace?.activeLeaf?.getViewState().state.source ? "source" : "preview"; - if (this.prevEditingMode === "preview") { - if (currentEditingMode === "source") { - app.workspace.iterateRootLeaves((leaf) => { - // Ensure that every block of the container is displayed - //@ts-ignore - for (const element of leaf.view.containerEl.querySelector(".cm-content")?.children) { - //@ts-ignore - element.style.removeProperty("display"); - } - }); - } - } - this.prevEditingMode = currentEditingMode; + // Break if one line is already targetted but wasn't a list item + if (numberOfLines > 0 && !lastLineWasList) break; - // If the editor content has changed unhide all hidden elements - if (update.docChanged) { + // Retrieve line + const line = doc.line(lineNumber + offset); - // Retrieve the blocks' container element - const blocksContainer = update.view.contentDOM; + // Break if the line is empty + if (line.text.trim() === "") break; - for (const block of blocksContainer.children) { - //@ts-ignore - block.style.removeProperty("display"); - } - } + // Figure whether the line is a list item and if yes, its type + let listType = null; + if (/^(\s*)(\-)(\s+)(.*)/.test(line.text)) listType = "ul"; + else if (/^(\s*)(\d+[\.\)])(\s+)(.*)/.test(line.text)) listType = "ol"; + const isList = listType ? true : false; - // Proceed to render only if the update has changed the cursor position on the document (performance reasons) - if (update.selectionSet) { + // Break if the + if (lastLineWasList && listType !== lastListType) break; - // Retrieve the blocks' container element - const blocksContainer = update.view.contentDOM; + // Increment the number of lines + numberOfLines++; - // Iterate over each block of the view - for (const block of blocksContainer.children) { + // Update last list was line and last list type + lastLineWasList = isList; + lastListType = listType; + } - // If the block is a custom class block - const codeBlock: HTMLElement | null = block.querySelector('span.cm-inline-code'); - if (codeBlock && codeBlock.innerText.trim().startsWith(plugin?.settings.get("customClassAnchor"))) { + return numberOfLines; +} - // Retrieve the list of elements that composes the next block - const nextBlockElements = this.getScopeBlocks(block as HTMLElement); - // If the custom class block target some elements - if (nextBlockElements.length > 0) { +export const customClassField = StateField.define({ - // Retrieve whether the codeBlock or the next block are active or not - const active = [block, ...nextBlockElements].find(el => el.classList.contains("cm-active")) ? true : false; + create (state): DecorationSet { + return Decoration.none; + }, - // If the code block scope is active - if (active) { + update (oldState: DecorationSet, tx: Transaction): DecorationSet { + const builder = new RangeSetBuilder(); - // If compatibility mode is enabled display the next block elements again - if (plugin?.settings.get("compatibilityMode")) { - console.log("unhide next block elements"); - console.log(nextBlockElements); - for (const element of nextBlockElements) { - element.style.removeProperty("display"); - } - } + // If live preview mode + const sourceViewEl = document.querySelector("div.markdown-source-view"); + if (sourceViewEl && sourceViewEl.classList.contains("is-live-preview")) { - // Else display the class code block again - else { - //@ts-ignore - block.style.removeProperty("display"); - } - } + for (let i = 1; i <= tx.state.doc.lines; i++) { + const line = tx.state.doc.line(i); - // Else if the code block is not active - else { + // If the line is an inline code-block + if (line.text.startsWith("`") && line.text.endsWith("`")) { - // Retrieve the custom class name - const customClass = codeBlock.innerText.trim().replace(plugin?.settings.get("customClassAnchor"), "").trim(); - console.log(customClass); - console.log(nextBlockElements); + // If the code block is a Custom Classes code block + if (line.text.replace("`", "").trim().startsWith(plugin?.settings.get("customClassAnchor"))) { - // If compatibility mode is enabled simulate reading mode render - if (plugin?.settings.get("compatibilityMode")) { + // Retrieve the list of elements that composes the next block + const targettedLinesNumber = getTargettedLinesNumber(tx.state.doc, line.number); - // Reset the block HTML - block.innerHTML = ""; + // If the custom class block target some lines + if (targettedLinesNumber > 0) { - // Retrieve the Markdown file lines - let markdownLines: Array = []; - if (update.state.doc.children) { - for (const childDoc of update.state.doc.children) { - //@ts-ignore - markdownLines = [...markdownLines, ...childDoc.text]; + // Retrieve whether the custom class line or the lines it targets are active + let active = false; + if (tx.selection) { + const to = tx.state.doc.line(line.number + targettedLinesNumber).to; + for (const range of tx.selection?.ranges) { + if (range.from >= line.from && range.to <= to) { + active = true; } } - else { - //@ts-ignore - markdownLines = update.state.doc.text; - } - - // Loop through every next block elements - let markdown = ""; - for (const element of nextBlockElements) { - - // Hide the element from the render - element.style.display = "none"; - - - // Build the element markdown - //@ts-ignore - markdown += markdownLines[blocksContainer.indexOf(element)] + "\n"; - } - - // Render markdown into the custom class block - MarkdownRenderer.renderMarkdown( - markdown, - block as HTMLElement, - "", - //@ts-ignore - null); - - // Append the class to the custom class block - block.className = customClass; } - // Else simply add the custom class to the next element sibling - else { - - // Hide the class block - //@ts-ignore - block.style.display = "none"; + // If the code block is not active render it + if (!active) { + + // Build the custom class name + const customClass = line.text + .replaceAll("`", "") + .trim() + .replace(plugin?.settings.get("customClassAnchor"), "") + .trim(); + + // If compatibility mode is enabled simulate reading mode render + if (plugin?.settings.get("compatibilityMode")) { + builder.add( + line.from, + tx.state.doc.line(line.number + targettedLinesNumber).to, + Decoration.replace({ + widget: new CompatibilityModeRenderWidget( + customClass, + line.number, + targettedLinesNumber + ), + }) + ); + } - // Add class to the next element - nextBlockElements[0].classList.add(customClass); + // Else simply add the custom class to the next element sibling + else { + builder.add( + line.from, + line.to, + Decoration.replace({}) + ); + + // TODO addclass widget + // Add class to the next element + // nextBlockElements[0].classList.add(customClass); + } } } } } } } - } -} + return builder.finish(); + }, -export const LivePreviewModeRenderer = ViewPlugin.fromClass(_LivePreviewModeRenderer); \ No newline at end of file + provide (field: StateField): Extension { + return EditorView.decorations.from(field); + } +}); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 6156dd3..55d9c4f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import { CustomClassesSettings, CustomClassesSettingsTab } from './settings'; -import { LivePreviewModeRenderer } from './live-preview-mode'; +import { customClassField } from './live-preview-mode'; import { readingModeRenderer } from './reading-mode'; export let plugin: CustomClasses | null = null; @@ -26,7 +26,7 @@ export default class CustomClasses extends Plugin { this.addSettingTab(new CustomClassesSettingsTab(this.app, this)); // Start the Live Preview mode renderer - this.registerEditorExtension([LivePreviewModeRenderer]); + this.registerEditorExtension([customClassField]); // Start the Reading mode renderer this.registerMarkdownPostProcessor(readingModeRenderer); diff --git a/styles.css b/styles.css index 66182ce..de0cf43 100644 --- a/styles.css +++ b/styles.css @@ -3,4 +3,20 @@ h2.settings-header { margin-left: -10px; font-weight: 900; margin-top: 60px; +} + +.custom-classes-renderer { + white-space: nowrap; + display: inline-block; +} + +.custom-classes-renderer > * { + margin: 0; + padding: 0; + white-space: normal; +} + +.custom-classes-renderer > ul, +.custom-classes-renderer > ol { + padding-inline-start: 24px; } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index b362d6c..c00beab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,8 @@ "dom.iterable", "es5", "es6", - "es7" + "es7", + "ES2021" ] }, "include": [