From 4bce4a94894e8f48850a1a22d64d39526f62d5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 19:11:52 +0100 Subject: [PATCH] simplify fold export & restore legacy higlights --- client/components/codeEditor/codeEditor.jsx | 27 ++-- client/components/codeEditor/customFolding.js | 76 +++++---- .../components/codeEditor/customHighlight.js | 8 +- client/components/codeEditor/customKeyMap.js | 2 +- .../codeEditor/legacyCustomHighlight.js | 35 +++- .../homebrew/editor/snippetbar/snippetbar.jsx | 2 +- themes/codeMirror/customThemes/default.js | 149 +++++++++--------- 7 files changed, 160 insertions(+), 139 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 64717e546..2067d370b 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -24,16 +24,16 @@ import { autocompleteEmoji } from './autocompleteEmoji.js'; import { searchKeymap, search } from '@codemirror/search'; import * as themesImport from '@uiw/codemirror-themes-all'; -import { defaultCM5Theme } from '@themes/codeMirror/customThemes/default.js'; +import defaultCM5Theme from '@themes/codeMirror/customThemes/default.js'; const themes = { default: defaultCM5Theme, ...themesImport }; const themeCompartment = new Compartment(); const highlightCompartment = new Compartment(); -import { customKeymap } from './customKeyMap.js'; -import { homebreweryFold, hbFolding } from './customFolding.js'; +import customKeymap from './customKeyMap.js'; +import pageFoldExtension from './customFolding.js'; import { customHighlightStyle, tokenizeCustomMarkdown, tokenizeCustomCSS } from './customHighlight.js'; -import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.js'; //only makes highlight for +import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.js'; const createHighlightPlugin = (renderer, tab)=>{ let tokenize; @@ -131,7 +131,7 @@ const CodeEditor = forwardRef( const prevTabRef = useRef(tab); const createExtensions = ({ onChange, language, editorTheme })=>{ - const updateListener = EditorView.updateListener.of((update)=>{ + const setEventListeners = EditorView.updateListener.of((update)=>{ if(update.docChanged) { onChange(update.state.doc.toString()); } @@ -155,26 +155,17 @@ const CodeEditor = forwardRef( const customHighlightPlugin = createHighlightPlugin(renderer, tab); - const combinedHighlight = [ - customHighlightPlugin, - highlightExtension, - ]; - const languageExtension = language === 'css' ? [css(), cssLanguage] : markdown({ base: markdownLanguage, codeLanguages: languages }); - const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; return [ - history(), - - updateListener, + history(), //allows for undo and redo + setEventListeners, EditorView.lineWrapping, scrollPastEnd(), languageExtension, - lineNumbers(), - homebreweryFold, - hbFolding, + pageFoldExtension, foldGutter({ openText : '▾', @@ -183,7 +174,7 @@ const CodeEditor = forwardRef( highlightActiveLine(), highlightActiveLineGutter(), - highlightCompartment.of(combinedHighlight), + highlightCompartment.of([customHighlightPlugin,highlightExtension]), themeCompartment.of(themeExtension), ...(tab !== 'brewStyles' ? [autocompleteEmoji] : []), search(), diff --git a/client/components/codeEditor/customFolding.js b/client/components/codeEditor/customFolding.js index cf6bea5c3..f758fbb44 100644 --- a/client/components/codeEditor/customFolding.js +++ b/client/components/codeEditor/customFolding.js @@ -1,50 +1,46 @@ -import { foldService } from '@codemirror/language'; -import { codeFolding } from '@codemirror/language'; +import { foldService, 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; +const pageFoldExtension = [ + foldService.of((state, lineStart)=>{ + const doc = state.doc; + const matcher = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; - if(doc.line(start).text.trim()) return ` ↤ Lines ${start}-${end} ↦`; + const startLine = doc.lineAt(lineStart); + const prevLineText = startLine.number > 1 ? doc.line(startLine.number - 1).text : ''; - const preview = Array.from({ length: end - start }, (_, i)=>doc.line(start + 1 + i).text.trim()) - .find(Boolean) || `Lines ${start}-${end}`; + if(startLine.number > 1 && !matcher.test(prevLineText)) return null; - return ` ↤ ${preview.replace('{', '').slice(0, 50).trim()}${preview.length > 50 ? '...' : ''} ↦`; -} + let endLine = startLine.number; + while (endLine < doc.lines && !matcher.test(doc.line(endLine + 1).text)) { + endLine++; + } -export const homebreweryFold = foldService.of((state, lineStart)=>{ - const doc = state.doc; - const matcher = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; + if(endLine === startLine.number) return null; - const startLine = doc.lineAt(lineStart); - const prevLineText = startLine.number > 1 ? doc.line(startLine.number - 1).text : ''; + return { from: startLine.from, to: doc.line(endLine).to }; + }), + codeFolding({ + preparePlaceholder : (state, range)=>{ + const doc = state.doc; + const start = doc.lineAt(range.from).number; + const end = doc.lineAt(range.to).number; - if(startLine.number > 1 && !matcher.test(prevLineText)) return null; + if(doc.line(start).text.trim()) return ` ↤ Lines ${start}-${end} ↦`; - let endLine = startLine.number; - while (endLine < doc.lines && !matcher.test(doc.line(endLine + 1).text)) { - endLine++; - } + const preview = Array.from({ length: end - start }, (_, i)=>doc.line(start + 1 + i).text.trim() + ).find(Boolean) || `Lines ${start}-${end}`; - if(endLine === startLine.number) return null; + return ` ↤ ${preview.replace('{', '').slice(0, 50).trim()}${preview.length > 50 ? '...' : ''} ↦`; + }, + 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; + }, + }), +]; - const widgetObject = { from: startLine.from, to: doc.line(endLine).to }; - - 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; - }, -}); +export default pageFoldExtension; \ No newline at end of file diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index 3f62d6c9c..4b63de802 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -19,7 +19,7 @@ const customTags = { //CSS - variable : 'variable', + variable : 'variable', }; export function tokenizeCustomMarkdown(text) { @@ -262,13 +262,15 @@ export const customHighlightStyle = HighlightStyle.define([ { tag: tags.heading4, class: 'cm-header cm-header-4' }, { tag: tags.heading5, class: 'cm-header cm-header-5' }, { tag: tags.heading6, class: 'cm-header cm-header-6' }, - { tag: tags.link, class: 'cm-link' }, { tag: tags.string, class: 'cm-string' }, { tag: tags.url, class: 'cm-string cm-url' }, { tag: tags.list, class: 'cm-list' }, { tag: tags.strong, class: 'cm-strong' }, { tag: tags.emphasis, class: 'cm-em' }, + { tag: tags.quote, class: 'cm-quote' }, + + //css tags { tag: tags.tagName, class: 'cm-tag' }, { tag: tags.className, class: 'cm-class' }, @@ -284,6 +286,8 @@ export const customHighlightStyle = HighlightStyle.define([ { tag: tags.invalid, class: 'cm-error' }, { tag: tags.comment, class: 'cm-comment' }, + //custom tags + { tag: customTags.pageLine, color: '#f0a' }, { tag: customTags.snippetLine, class: 'cm-snippetLine', color: '#0af' }, { tag: customTags.inlineBlock, class: 'cm-inline-block' }, diff --git a/client/components/codeEditor/customKeyMap.js b/client/components/codeEditor/customKeyMap.js index 1c7560144..81cb88e25 100644 --- a/client/components/codeEditor/customKeyMap.js +++ b/client/components/codeEditor/customKeyMap.js @@ -207,7 +207,7 @@ const newPage = (view)=>{ return true; }; -export const customKeymap = keymap.of([ +export default keymap.of([ { key: 'Tab', run: insertTabAtCursor }, { key: 'Shift-Tab', run: indentMore }, { key: 'Mod-Shift-Tab', run: indentLess }, diff --git a/client/components/codeEditor/legacyCustomHighlight.js b/client/components/codeEditor/legacyCustomHighlight.js index e011e15f5..84b0fcb24 100644 --- a/client/components/codeEditor/legacyCustomHighlight.js +++ b/client/components/codeEditor/legacyCustomHighlight.js @@ -20,8 +20,39 @@ export function legacyTokenizeCustomMarkdown(text) { } export const legacyCustomHighlightStyle = HighlightStyle.define([ - { tag: tags.heading1, color: '#000', fontWeight: '700' }, - { tag: tags.keyword, color: '#07a' }, // example for your markdown headings + { tag: tags.heading, class: 'cm-header' }, + { tag: tags.heading1, class: 'cm-header cm-header-1' }, + { tag: tags.heading2, class: 'cm-header cm-header-2' }, + { tag: tags.heading3, class: 'cm-header cm-header-3' }, + { tag: tags.heading4, class: 'cm-header cm-header-4' }, + { tag: tags.heading5, class: 'cm-header cm-header-5' }, + { tag: tags.heading6, class: 'cm-header cm-header-6' }, + { tag: tags.link, class: 'cm-link' }, + { tag: tags.string, class: 'cm-string' }, + { tag: tags.url, class: 'cm-string cm-url' }, + { tag: tags.list, class: 'cm-list' }, + { tag: tags.strong, class: 'cm-strong' }, + { tag: tags.emphasis, class: 'cm-em' }, + { tag: tags.quote, class: 'cm-quote' }, + + //css tags + + { tag: tags.tagName, class: 'cm-tag' }, + { tag: tags.className, class: 'cm-class' }, + { tag: tags.propertyName, class: 'cm-property' }, + { tag: tags.attributeValue, class: 'cm-value' }, + { tag: tags.keyword, class: 'cm-keyword' }, + { tag: tags.atom, class: 'cm-atom' }, + { tag: tags.integer, class: 'cm-integer' }, + { tag: tags.unit, class: 'cm-unit' }, + { tag: tags.color, class: 'cm-color' }, + { tag: tags.paren, class: 'cm-paren' }, + { tag: tags.variableName, class: 'cm-variable' }, + { tag: tags.invalid, class: 'cm-error' }, + { tag: tags.comment, class: 'cm-comment' }, + + //custom tags + { tag: customTags.pageLine, color: '#f0a' }, { tag: customTags.snippetLine, class: 'cm-snippetLine', color: '#0af' }, ]); diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index d7af14f49..12185f504 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -25,7 +25,7 @@ const ThemeSnippets = { //import EditorThemes from '../../../../build/homebrew/codeMirror/editorThemes.json'; import * as themesImport from '@uiw/codemirror-themes-all'; -import { defaultCM5Theme } from '@themes/codeMirror/customThemes/default.js'; +import defaultCM5Theme from '@themes/codeMirror/customThemes/default.js'; const themes = { default: defaultCM5Theme, ...themesImport }; diff --git a/themes/codeMirror/customThemes/default.js b/themes/codeMirror/customThemes/default.js index 49e663a58..305271405 100644 --- a/themes/codeMirror/customThemes/default.js +++ b/themes/codeMirror/customThemes/default.js @@ -1,81 +1,80 @@ // themes/codeMirror/customThemes/default.js import { EditorView } from '@codemirror/view'; -import { Compartment } from '@codemirror/state'; -export const themeCompartment = new Compartment(); +//This theme is made of the base css for the codemirror 5 editor -export const defaultCM5Theme = EditorView.theme({ - "&": { - backgroundColor: "white", - color: "black", - }, - ".cm-content": { - padding: "4px 0", - fontFamily: "monospace", - fontSize: "13px", - lineHeight: "1", - }, - ".cm-line": { - padding: "0 4px", - }, - ".cm-gutters": { - borderRight: "1px solid #ddd", - backgroundColor: "#f7f7f7", - whiteSpace: "nowrap", - }, - ".cm-linenumber": { - padding: "0 3px 0 5px", - minWidth: "20px", - textAlign: "right", - color: "#999", - whiteSpace: "nowrap", - }, - ".cm-cursor": { - borderLeft: "1px solid black", - }, - ".cm-fat-cursor": { - width: "auto", - backgroundColor: "#7e7", - caretColor: "transparent", - }, - ".cm-activeline-background": { - backgroundColor: "#e8f2ff", - }, - ".cm-selected": { - backgroundColor: "#d7d4f0", - }, - ".cm-foldmarker": { - color: "blue", - fontFamily: "arial", - lineHeight: "0.3", - cursor: "pointer", - }, +export default EditorView.theme({ + '&' : { + backgroundColor : 'white', + color : 'black', + }, + '.cm-content' : { + padding : '4px 0', + fontFamily : 'monospace', + fontSize : '13px', + lineHeight : '1', + }, + '.cm-line' : { + padding : '0 4px', + }, + '.cm-gutters' : { + borderRight : '1px solid #ddd', + backgroundColor : '#f7f7f7', + whiteSpace : 'nowrap', + }, + '.cm-linenumber' : { + padding : '0 3px 0 5px', + minWidth : '20px', + textAlign : 'right', + color : '#999', + whiteSpace : 'nowrap', + }, + '.cm-cursor' : { + borderLeft : '1px solid black', + }, + '.cm-fat-cursor' : { + width : 'auto', + backgroundColor : '#7e7', + caretColor : 'transparent', + }, + '.cm-activeline-background' : { + backgroundColor : '#e8f2ff', + }, + '.cm-selected' : { + backgroundColor : '#d7d4f0', + }, + '.cm-foldmarker' : { + color : 'blue', + fontFamily : 'arial', + lineHeight : '0.3', + cursor : 'pointer', + }, - // Semantic classes - ".cm-header": { color: "blue", fontWeight: "bold" }, - ".cm-strong": { fontWeight: "bold" }, - ".cm-em": { fontStyle: "italic" }, - ".cm-quote": { color: "#090" }, - ".cm-keyword": { color: "#708" }, - ".cm-atom, cm-value, cm-color": { color: "#219" }, - ".cm-number": { color: "#164" }, - ".cm-def": { color: "#00f" }, - ".cm-list": { color: "#05a" }, - ".cm-variable, .cm-type": { color: "#085" }, - ".cm-comment": { color: "#a50" }, - ".cm-link": { color: "#00c", textDecoration: "underline" }, - ".cm-string": { color: "#a11", textDecoration: "none" }, - ".cm-string-2": { color: "#f50", textDecoration: "none" }, - ".cm-meta, .cm-qualifier, .cm-class": { color: "#555" }, - ".cm-builtin": { color: "#30a" }, - ".cm-bracket": { color: "#997" }, - ".cm-tag": { color: "#170" }, - ".cm-attribute": { color: "#00c" }, - ".cm-hr": { color: "#999" }, - ".cm-negative": { color: "#d44" }, - ".cm-positive": { color: "#292" }, - ".cm-error, .cm-invalidchar": { color: "#f00" }, - ".cm-matchingbracket": { color: "#0b0" }, - ".cm-nonmatchingbracket": { color: "#a22" }, - ".cm-matchingtag": { backgroundColor: "rgba(255, 150, 0, 0.3)" }, + // Semantic classes + '.cm-header' : { color: 'blue', fontWeight: 'bold' }, + '.cm-strong' : { fontWeight: 'bold' }, + '.cm-em' : { fontStyle: 'italic' }, + '.cm-keyword' : { color: '#708' }, + '.cm-atom, cm-value, cm-color' : { color: '#219' }, + '.cm-number' : { color: '#164' }, + '.cm-def' : { color: '#00f' }, + '.cm-list' : { color: '#05a' }, + '.cm-variable, .cm-type' : { color: '#085' }, + '.cm-comment' : { color: '#a50' }, + '.cm-link' : { color: '#00c', textDecoration: 'underline' }, + '.cm-string' : { color: '#a11', textDecoration: 'none' }, + '.cm-string-2' : { color: '#f50', textDecoration: 'none' }, + '.cm-meta, .cm-qualifier, .cm-class' : { color: '#555' }, + '.cm-builtin' : { color: '#30a' }, + '.cm-bracket' : { color: '#997' }, + '.cm-tag' : { color: '#170' }, + '.cm-attribute' : { color: '#00c' }, + '.cm-hr' : { color: '#999' }, + '.cm-negative' : { color: '#d44' }, + '.cm-positive' : { color: '#292' }, + '.cm-error, .cm-invalidchar' : { color: '#f00' }, + '.cm-matchingbracket' : { color: '#0b0' }, + '.cm-nonmatchingbracket' : { color: '#a22' }, + '.cm-matchingtag' : { backgroundColor: 'rgba(255, 150, 0, 0.3)' }, + '.cm-quote' : { color: '#090' }, }, { dark: false }); \ No newline at end of file