From 953ef8c534e5efeaf5b0cb6a7f982a200e8910c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Tue, 24 Mar 2026 16:56:40 +0100 Subject: [PATCH] definition lists --- client/components/codeEditor/codeEditor.jsx | 22 ++-- .../codeEditor/customMarkdownGrammar.js | 119 +++++++++++++++++- client/homebrew/editor/editor.less | 8 +- 3 files changed, 126 insertions(+), 23 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 14fd4914a..f6c2014c6 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -70,24 +70,21 @@ const customHighlightPlugin = ViewPlugin.fromClass( tokens.forEach((tok) => { const line = view.state.doc.line(tok.line + 1); - if (tok.from != null && tok.to != null) { - decos.push({ - from: line.from + tok.from, - to: line.from + tok.to, - deco: Decoration.mark({ class: `cm-${tok.type}` }), - }); + if (tok.from != null && tok.to != null && tok.from < tok.to) { + // inline decoration + decos.push( + Decoration.mark({ class: `cm-${tok.type}` }).range(line.from + tok.from, line.from + tok.to), + ); } else { - decos.push({ - from: line.from, - to: line.from, - deco: Decoration.line({ class: `cm-${tok.type}` }), - }); + // full-line decoration + decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); } }); + // sort by absolute start position decos.sort((a, b) => a.from - b.from || a.to - b.to); - return Decoration.set(decos.map((d) => d.deco.range(d.from, d.to))); + return Decoration.set(decos); } }, { @@ -114,7 +111,6 @@ const CodeEditor = forwardRef( const docsRef = useRef({}); const prevTabRef = useRef(tab); - console.log(props); // --- init editor --- const createExtensions = ({ onChange, language, editorTheme }) => { diff --git a/client/components/codeEditor/customMarkdownGrammar.js b/client/components/codeEditor/customMarkdownGrammar.js index 6290982e2..9ebafd92e 100644 --- a/client/components/codeEditor/customMarkdownGrammar.js +++ b/client/components/codeEditor/customMarkdownGrammar.js @@ -11,8 +11,10 @@ export const customTags = { emoji: "emoji", // .cm-emoji superscript: "superscript", // .cm-superscript subscript: "subscript", // .cm-subscript - definitionTerm: "dt-highlight", // .cm-dt-highlight - definitionDesc: "dd-highlight", // .cm-dd-highlight + definitionList: "definitionList", // .cm-definitionList + definitionTerm: "definitionTerm", // .cm-definitionTerm + definitionDesc: "definitionDesc", // .cm-definitionDesc + definitionColon: "definitionColon", // .cm-definitionColon injection: "injection", // .cm-injection }; @@ -66,11 +68,116 @@ export function tokenizeCustomMarkdown(text) { Math.max(startIndex + 1, superRegex.lastIndex || 0, subRegex.lastIndex || 0), ); } - }; - // --- Definition lists --- + } + + // --- inline definition lists --- if (/::/.test(lineText)) { - tokens.push({ line: lineNumber, type: customTags.definitionDesc }); - tokens.push({ line: lineNumber, type: customTags.definitionTerm }); + if (/^:*$/.test(lineText) == true) { + return; //if line only has colons, stops + } + + const singleLineRegex = /^([^:\n]*\S)(::)([^\n]*)$/dmy; + + let match = singleLineRegex.exec(lineText); + + if (match) { + const [full, term, colons, desc] = match; + let offset = 0; + + // Entire line as definitionList + tokens.push({ + line: lineNumber, + type: customTags.definitionList, + }); + + // Term + tokens.push({ + line: lineNumber, + type: customTags.definitionTerm, + from: offset, + to: offset + term.length, + }); + offset += term.length; + + // :: + tokens.push({ + line: lineNumber, + type: customTags.definitionColon, + from: offset, + to: offset + colons.length, + }); + offset += colons.length; + + // Definition + tokens.push({ + line: lineNumber, + type: customTags.definitionDesc, + from: offset, + to: offset + desc.length, + }); + + return; + } + } + + // --- Multiline definition list: term:\n::def1\n::def2 --- + // Only treat this line as a term if next line starts with :: + if (!/^::/.test(lines[lineNumber]) && lineNumber + 1 < lines.length && /^::/.test(lines[lineNumber + 1])) { + console.log(`testing line ${lineNumber + 1}, with content: ${lineText}`); + console.log(`next line is ${lineNumber + 1 + 1}, with content: ${lines[lineNumber + 1]}`); + + const term = lineText; + const startLine = lineNumber; + let defs = []; + let endLine = startLine; + + // collect all following :: definitions + for (let i = lineNumber + 1; i < lines.length; i++) { + const nextLine = lines[i]; + const onlyColonsMatch = /^:*$/.test(nextLine); + const defMatch = /^(::)(.*\S.*)?\s*$/.exec(nextLine); + if (!onlyColonsMatch && defMatch) { + defs.push({ colons: defMatch[1], desc: defMatch[2], line: i }); + endLine = i; + } else break; + } + + console.log(defs); + if (defs.length > 0) { + tokens.push({ + line: startLine, + type: customTags.definitionList, + }); + + // term + tokens.push({ + line: startLine, + type: customTags.definitionTerm, + from: 0, + to: lineText.length, + }); + + // definitions + defs.forEach((d) => { + tokens.push({ + line: d.line, + type: customTags.definitionList, + }); + + tokens.push({ + line: d.line, + type: customTags.definitionColon, + from: 0, + to: d.colons.length, + }); + tokens.push({ + line: d.line, + type: customTags.definitionDesc, + from: d.colons.length, + to: d.colons.length + d.desc.length, + }); + }); + } } // --- Injection `{…}` --- diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index db2421b0f..ada924387 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -74,15 +74,15 @@ vertical-align : sub; color : rgb(123, 123, 15); } - .cm-dl-highlight { - &.dl-colon-highlight { + .cm-definitionList { + .cm-definitionTerm { color : rgb(96, 117, 143); } + .cm-definitionColon { font-weight : bold; color : #949494; background : #E5E5E5; border-radius : 3px; } - &.dt-highlight { color : rgb(96, 117, 143); } - &.dd-highlight { color : rgb(97, 57, 178); } + .cm-definitionDesc { color : rgb(97, 57, 178); } } }