From f50c25b906bb4ea68b5b0f2931ab00991e54e677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 26 Mar 2026 12:54:07 +0100 Subject: [PATCH] organizing --- client/components/codeEditor/codeEditor.jsx | 167 +---------- client/components/codeEditor/customFolding.js | 51 ++++ .../components/codeEditor/customHighlight.js | 283 ++++++++++++++++++ .../codeEditor/customMarkdownGrammar.js | 227 -------------- client/components/codeEditor/fold-pages.js | 26 -- 5 files changed, 340 insertions(+), 414 deletions(-) create mode 100644 client/components/codeEditor/customFolding.js create mode 100644 client/components/codeEditor/customHighlight.js delete mode 100644 client/components/codeEditor/customMarkdownGrammar.js delete mode 100644 client/components/codeEditor/fold-pages.js diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 5d119e629..7b718a1f4 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -8,169 +8,19 @@ import { highlightActiveLineGutter, highlightActiveLine, scrollPastEnd, - Decoration, - ViewPlugin, - WidgetType, } from '@codemirror/view'; import { EditorState, Compartment } from '@codemirror/state'; -import { foldGutter, foldKeymap, syntaxHighlighting, HighlightStyle } from '@codemirror/language'; +import { foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language'; import { defaultKeymap, history, historyField, undo, redo } from '@codemirror/commands'; import { languages } from '@codemirror/language-data'; import { css } from '@codemirror/lang-css'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; -import { tags } from '@lezer/highlight'; - -// ######################### THEMES ############################# - import * as themes from '@uiw/codemirror-themes-all'; - const themeCompartment = new Compartment(); -// ######################### CUSTOM HIGHLIGHTS ############################# - -const highlightStyle = HighlightStyle.define([ - { - tag : tags.heading1, - color : 'black', - fontSize : '1.75em', - fontWeight : '700', - class : 'cm-header cm-header-1', - }, - { - tag : tags.processingInstruction, - color : 'blue', - }, - // … -]); - -import { tokenizeCustomMarkdown, customTags } from './customMarkdownGrammar.js'; - -const customHighlightStyle = HighlightStyle.define([ - { tag: tags.heading1, color: '#000', fontWeight: '700' }, - { tag: tags.keyword, color: '#07a' }, // example for your markdown headings - { tag: customTags.pageLine, color: '#f0a' }, - { tag: customTags.snippetBreak, class: 'cm-snippet-break', color: '#0af' }, - { tag: customTags.inlineBlock, class: 'cm-inline-block', backgroundColor: '#fffae6' }, - { tag: customTags.emoji, class: 'cm-emoji', color: '#fa0' }, - { tag: customTags.superscript, class: 'cm-superscript', verticalAlign: 'super', fontSize: '0.8em' }, - { tag: customTags.subscript, class: 'cm-subscript', verticalAlign: 'sub', fontSize: '0.8em' }, - { tag: customTags.definitionTerm, class: 'cm-dt', fontWeight: 'bold', color: '#0a0' }, - { tag: customTags.definitionDesc, class: 'cm-dd', color: '#070' }, -]); - -const customHighlightPlugin = ViewPlugin.fromClass( - class { - constructor(view) { - this.decorations = this.buildDecorations(view); - } - update(update) { - if(update.docChanged) { - this.decorations = this.buildDecorations(update.view); - } - } - buildDecorations(view) { - const decos = []; - const tokens = tokenizeCustomMarkdown(view.state.doc.toString()); - - tokens.forEach((tok)=>{ - const line = view.state.doc.line(tok.line + 1); - - 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 { - // 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); - } - }, - { - decorations : (v)=>v.decorations, - }, -); - -// ######################### FOLDING ############################### - -import { foldService } from '@codemirror/language'; - -const homebreweryFold = foldService.of((state, lineStart)=>{ - const doc = state.doc; - const matcher = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; - - const startLine = doc.lineAt(lineStart); - const prevLineText = startLine.number > 1 ? doc.line(startLine.number - 1).text : ''; - - if(startLine.number > 1 && !matcher.test(prevLineText)) return null; - - let endLine = startLine.number; - while (endLine < doc.lines && !matcher.test(doc.line(endLine + 1).text)) { - endLine++; - } - - if(endLine === startLine.number) return null; - - const widgetObject = { from: startLine.from, to: doc.line(endLine).to }; - console.log(widgetObject); - - return widgetObject; -}); - -import { codeFolding } from '@codemirror/language'; - -function getFoldPreview(state, from, to) { - const doc = state.doc; - const startLine = doc.lineAt(from).number; - const endLine = doc.lineAt(to).number; - - // If the current line has text, do not generate a preview - if (doc.line(startLine).text.trim().length > 0) { - return `↤ Lines ${startLine}-${endLine} ↦`; - } - - let preview = ''; - - for (let i = startLine + 1; i <= endLine; i++) { - const text = doc.line(i).text.trim(); - if (text.length > 0) { - preview = text; - break; - } - } - - if (!preview) preview = `Lines ${startLine}-${endLine}`; - - preview = preview.replace('{', '').trim(); - if (preview.length > 50) preview = `${preview.slice(0, 50)}...`; - - return `↤ ${preview} ↦`; -} - -const hbFolding = codeFolding({ - preparePlaceholder : (state, range)=>{ - return getFoldPreview(state, range.from, range.to); - }, - - placeholderDOM(view, onclick, prepared) { - const span = document.createElement('span'); - span.className = 'cm-fold-placeholder'; - span.textContent = prepared; - span.onclick = onclick; - span.style.color = '#989898'; - return span; - } -}); - - -// ######################### COMPONENT ############################# +import { customHighlightPlugin, customHighlightStyle } from './customHighlight.js'; +import { homebreweryFold, hbFolding } from './customFolding.js'; const CodeEditor = forwardRef( ( @@ -242,8 +92,6 @@ const CodeEditor = forwardRef( EditorView.lineWrapping, scrollPastEnd(), languageExtension, - highlightActiveLine(), - highlightActiveLineGutter(), lineNumbers(), homebreweryFold, @@ -254,9 +102,10 @@ const CodeEditor = forwardRef( openText : '▾', closedText : '▸' }), - themeCompartment.of(themeExtension), // 👈 key line + themeCompartment.of(themeExtension), - syntaxHighlighting(highlightStyle), + highlightActiveLine(), + highlightActiveLineGutter(), customHighlightPlugin, syntaxHighlighting(customHighlightStyle), ]; @@ -265,7 +114,6 @@ const CodeEditor = forwardRef( useEffect(()=>{ if(!editorRef.current) return; - // create initial editor state const state = EditorState.create({ doc : value, extensions : createExtensions({ onChange, language, editorTheme }), @@ -276,7 +124,6 @@ const CodeEditor = forwardRef( parent : editorRef.current, }); - // save initial state for current tab docsRef.current[tab] = state; return ()=>viewRef.current?.destroy(); @@ -289,10 +136,8 @@ const CodeEditor = forwardRef( const prevTab = prevTabRef.current; if(prevTab !== tab) { - // save current state docsRef.current[prevTab] = view.state; - // restore or create let nextState = docsRef.current[tab]; if(!nextState) { diff --git a/client/components/codeEditor/customFolding.js b/client/components/codeEditor/customFolding.js new file mode 100644 index 000000000..778901371 --- /dev/null +++ b/client/components/codeEditor/customFolding.js @@ -0,0 +1,51 @@ +import { foldService } from '@codemirror/language'; +import { codeFolding } from '@codemirror/language'; + +export function getFoldPreview(state, from, to) { + const doc = state.doc; + const start = doc.lineAt(from).number; + const end = doc.lineAt(to).number; + + if(doc.line(start).text.trim()) return ` ↤ Lines ${start}-${end} ↦`; + + const preview = Array.from({ length: end - start }, (_, i)=>doc.line(start + 1 + i).text.trim()) + .find(Boolean) || `Lines ${start}-${end}`; + + return ` ↤ ${preview.replace('{', '').slice(0, 50).trim()}${preview.length > 50 ? '...' : ''} ↦`; +} + +export const homebreweryFold = foldService.of((state, lineStart)=>{ + const doc = state.doc; + const matcher = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; + + const startLine = doc.lineAt(lineStart); + const prevLineText = startLine.number > 1 ? doc.line(startLine.number - 1).text : ''; + + if(startLine.number > 1 && !matcher.test(prevLineText)) return null; + + let endLine = startLine.number; + while (endLine < doc.lines && !matcher.test(doc.line(endLine + 1).text)) { + endLine++; + } + + if(endLine === startLine.number) return null; + + const widgetObject = { from: startLine.from, to: doc.line(endLine).to }; + console.log(widgetObject); + + return widgetObject; +}); + +export const hbFolding = codeFolding({ + preparePlaceholder : (state, range)=>{ + return getFoldPreview(state, range.from, range.to); + }, + placeholderDOM(view, onclick, prepared) { + const span = document.createElement('span'); + span.className = 'cm-fold-placeholder'; + span.textContent = prepared; + span.onclick = onclick; + span.style.color = '#989898'; + return span; + }, +}); diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js new file mode 100644 index 000000000..29df5576d --- /dev/null +++ b/client/components/codeEditor/customHighlight.js @@ -0,0 +1,283 @@ +import { HighlightStyle } from '@codemirror/language'; +import { tags } from '@lezer/highlight'; +import { + Decoration, + ViewPlugin, +} from '@codemirror/view'; + +// Making the tokens +const customTags = { + pageLine : 'pageLine', // .cm-pageLine + snippetLine : 'snippetLine', // .cm-snippetLine + columnSplit : 'columnSplit', // .cm-columnSplit + snippetBreak : 'snippetBreak', // .cm-snippetBreak + block : 'block', // .cm-block + inlineBlock : 'inline-block', // .cm-inline-block + injection : 'injection', // .cm-injection + emoji : 'emoji', // .cm-emoji + superscript : 'superscript', // .cm-superscript + subscript : 'subscript', // .cm-subscript + definitionList : 'definitionList', // .cm-definitionList + definitionTerm : 'definitionTerm', // .cm-definitionTerm + definitionDesc : 'definitionDesc', // .cm-definitionDesc + definitionColon : 'definitionColon', // .cm-definitionColon +}; + +function tokenizeCustomMarkdown(text) { + const tokens = []; + const lines = text.split('\n'); + + // Track multi-line blocks + const inBlock = false; + const blockStart = 0; + + lines.forEach((lineText, lineNumber)=>{ + // --- Page / snippet lines --- + if(/^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m.test(lineText)) tokens.push({ line: lineNumber, type: customTags.pageLine }); + if(/^\\snippet\ .*$/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetLine }); + if(/^\\column(?:break)?$/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.columnSplit }); + if(/\\snippet/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetBreak }); + + // --- Emoji --- + if(/:\w+?:/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.emoji }); + + // --- Superscript / Subscript --- + if(/\^/.test(lineText)) { + let startIndex = lineText.indexOf('^'); + const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy; + const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy; + + while (startIndex >= 0) { + superRegex.lastIndex = subRegex.lastIndex = startIndex; + + let match = subRegex.exec(lineText); + let type = customTags.subscript; + + if(!match) { + match = superRegex.exec(lineText); + type = customTags.superscript; + } + + if(match) { + tokens.push({ + line : lineNumber, + type, + from : match.index, + to : match.index + match[0].length, + }); + } + + startIndex = lineText.indexOf( + '^', + Math.max(startIndex + 1, superRegex.lastIndex || 0, subRegex.lastIndex || 0), + ); + } + } + + // --- inline definition lists --- + if(/::/.test(lineText)) { + if(/^:*$/.test(lineText) == true) { + return; //if line only has colons, stops + } + + const singleLineRegex = /^([^:\n]*\S)(::)([^\n]*)$/dmy; + + const 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 def list + if(!/^::/.test(lines[lineNumber]) && lineNumber + 1 < lines.length && /^::/.test(lines[lineNumber + 1])) { + const term = lineText; + const startLine = lineNumber; + const 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; + } + + 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, + }); + }); + } + } + + if(lineText.includes('{') && lineText.includes('}')) { + const injectionRegex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm; + let match; + while ((match = injectionRegex.exec(lineText)) !== null) { + tokens.push({ + line : lineNumber, + from : match.index +1, + to : match.index + match[1].length +1, + type : customTags.injection, + }); + } + } + if(lineText.includes('{{') && lineText.includes('}}')) { + // Inline blocks: single-line {{…}} + const spanRegex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g; + let match; + let blockCount = 0; + while ((match = spanRegex.exec(lineText)) !== null) { + if(match[0].startsWith('{{')) { + blockCount += 1; + } else { + blockCount -= 1; + } + if(blockCount < 0) { + blockCount = 0; + continue; + } + tokens.push({ + line : lineNumber, + from : match.index, + to : match.index + match[0].length, + type : customTags.inlineBlock, + }); + } + } else if(lineText.trimLeft().startsWith('{{') || lineText.trimLeft().startsWith('}}')) { + // Highlight block divs {{\n Content \n}} + let endCh = lineText.length + 1; + + const match = lineText.match( + /^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/, + ); + if(match) endCh = match.index + match[0].length; + tokens.push({ line: lineNumber, type: customTags.block }); + } + + + }); + + return tokens; +} + +export const customHighlightStyle = HighlightStyle.define([ + { tag: tags.heading1, color: '#000', fontWeight: '700' }, + { tag: tags.keyword, color: '#07a' }, // example for your markdown headings + { tag: customTags.pageLine, color: '#f0a' }, + { tag: customTags.snippetBreak, class: 'cm-snippet-break', color: '#0af' }, + { tag: customTags.inlineBlock, class: 'cm-inline-block', backgroundColor: '#fffae6' }, + { tag: customTags.emoji, class: 'cm-emoji', color: '#fa0' }, + { tag: customTags.superscript, class: 'cm-superscript', verticalAlign: 'super', fontSize: '0.8em' }, + { tag: customTags.subscript, class: 'cm-subscript', verticalAlign: 'sub', fontSize: '0.8em' }, + { tag: customTags.definitionTerm, class: 'cm-dt', fontWeight: 'bold', color: '#0a0' }, + { tag: customTags.definitionDesc, class: 'cm-dd', color: '#070' }, +]); + +export const customHighlightPlugin = ViewPlugin.fromClass( + class { + constructor(view) { + this.decorations = this.buildDecorations(view); + } + update(update) { + if(update.docChanged) { + this.decorations = this.buildDecorations(update.view); + } + } + buildDecorations(view) { + const decos = []; + const tokens = tokenizeCustomMarkdown(view.state.doc.toString()); + + tokens.forEach((tok)=>{ + const line = view.state.doc.line(tok.line + 1); + + 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 { + // 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); + } + }, + { + decorations : (v)=>v.decorations, + }, +); diff --git a/client/components/codeEditor/customMarkdownGrammar.js b/client/components/codeEditor/customMarkdownGrammar.js deleted file mode 100644 index d39bc4cdd..000000000 --- a/client/components/codeEditor/customMarkdownGrammar.js +++ /dev/null @@ -1,227 +0,0 @@ -// customMarkdownGrammar.js - -// --- Custom tags with CM6-compatible class names --- -export const customTags = { - pageLine: "pageLine", // .cm-pageLine - snippetLine: "snippetLine", // .cm-snippetLine - columnSplit: "columnSplit", // .cm-columnSplit - snippetBreak: "snippetBreak", // .cm-snippetBreak - block: "block", // .cm-block - inlineBlock: "inline-block", // .cm-inline-block - injection: "injection", // .cm-injection - emoji: "emoji", // .cm-emoji - superscript: "superscript", // .cm-superscript - subscript: "subscript", // .cm-subscript - definitionList: "definitionList", // .cm-definitionList - definitionTerm: "definitionTerm", // .cm-definitionTerm - definitionDesc: "definitionDesc", // .cm-definitionDesc - definitionColon: "definitionColon", // .cm-definitionColon -}; - -// --- Tokenizer function --- -export function tokenizeCustomMarkdown(text) { - const tokens = []; - const lines = text.split("\n"); - - // Track multi-line blocks - let inBlock = false; - let blockStart = 0; - - lines.forEach((lineText, lineNumber) => { - // --- Page / snippet lines --- - if (/^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m.test(lineText)) tokens.push({ line: lineNumber, type: customTags.pageLine }); - if (/^\\snippet\ .*$/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetLine }); - if (/^\\column(?:break)?$/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.columnSplit }); - if (/\\snippet/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetBreak }); - - // --- Emoji --- - if (/:\w+?:/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.emoji }); - - // --- Superscript / Subscript --- - if (/\^/.test(lineText)) { - let startIndex = lineText.indexOf("^"); - const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy; - const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy; - - while (startIndex >= 0) { - superRegex.lastIndex = subRegex.lastIndex = startIndex; - - let match = subRegex.exec(lineText); - let type = customTags.subscript; - - if (!match) { - match = superRegex.exec(lineText); - type = customTags.superscript; - } - - if (match) { - tokens.push({ - line: lineNumber, - type, - from: match.index, - to: match.index + match[0].length, - }); - } - - startIndex = lineText.indexOf( - "^", - Math.max(startIndex + 1, superRegex.lastIndex || 0, subRegex.lastIndex || 0), - ); - } - } - - // --- inline definition lists --- - if (/::/.test(lineText)) { - 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 def list - if (!/^::/.test(lines[lineNumber]) && lineNumber + 1 < lines.length && /^::/.test(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; - } - - 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, - }); - }); - } - } - - if (lineText.includes("{") && lineText.includes("}")) { - const injectionRegex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm; - let match; - while ((match = injectionRegex.exec(lineText)) !== null) { - tokens.push({ - line: lineNumber, - from: match.index +1, - to: match.index + match[1].length +1, - type: customTags.injection, - }); - } - } - if (lineText.includes("{{") && lineText.includes("}}")) { - // Inline blocks: single-line {{…}} - const spanRegex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g; - let match; - let blockCount = 0; - while ((match = spanRegex.exec(lineText)) !== null) { - if (match[0].startsWith("{{")) { - blockCount += 1; - } else { - blockCount -= 1; - } - if (blockCount < 0) { - blockCount = 0; - continue; - } - tokens.push({ - line: lineNumber, - from: match.index, - to: match.index + match[0].length, - type: customTags.inlineBlock, - }); - } - } else if (lineText.trimLeft().startsWith("{{") || lineText.trimLeft().startsWith("}}")) { - // Highlight block divs {{\n Content \n}} - let endCh = lineText.length + 1; - - const match = lineText.match( - /^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/, - ); - if (match) endCh = match.index + match[0].length; - tokens.push({ line: lineNumber, type: customTags.block }); - } - - - }); - - return tokens; -} diff --git a/client/components/codeEditor/fold-pages.js b/client/components/codeEditor/fold-pages.js deleted file mode 100644 index 1d8d19f6b..000000000 --- a/client/components/codeEditor/fold-pages.js +++ /dev/null @@ -1,26 +0,0 @@ -export default { - registerHomebreweryHelper : function(CodeMirror) { - CodeMirror.registerHelper('fold', 'homebrewery', function(cm, start) { - const matcher = /^\\page.*/; - const prevLine = cm.getLine(start.line - 1); - - if(start.line === cm.firstLine() || prevLine.match(matcher)) { - const lastLineNo = cm.lastLine(); - let end = start.line; - - while (end < lastLineNo) { - if(cm.getLine(end + 1).match(matcher)) - break; - ++end; - } - - return { - from : CodeMirror.Pos(start.line, 0), - to : CodeMirror.Pos(end, cm.getLine(end).length) - }; - } - - return null; - }); - } -};