From 3785d5808fdf4b8877e98abf9083ac04bcaba1f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sat, 21 Mar 2026 21:49:59 +0100 Subject: [PATCH 01/56] basic editor --- client/components/codeEditor/codeEditor.jsx | 599 ++++---------------- package-lock.json | 258 ++++++++- package.json | 9 +- vitePlugins/generateAssetsPlugin.js | 13 +- 4 files changed, 390 insertions(+), 489 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index cd140ad07..c4189e21d 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -1,490 +1,143 @@ -/* eslint-disable max-lines */ -import './codeEditor.less'; -import React from 'react'; -import createReactClass from 'create-react-class'; -import _ from 'lodash'; -import closeTag from './close-tag'; -import autoCompleteEmoji from './autocompleteEmoji'; -let CodeMirror; +import React, { useEffect, useRef, forwardRef, useImperativeHandle } from "react"; -const CodeEditor = createReactClass({ - displayName : 'CodeEditor', - getDefaultProps : function() { - return { - language : '', - tab : 'brewText', - value : '', - wrap : true, - onChange : ()=>{}, - enableFolding : true, - editorTheme : 'default' - }; - }, +import { EditorState } from "@codemirror/state"; +import { defaultKeymap, history, historyField, undo, redo } from "@codemirror/commands"; +import { foldGutter, foldKeymap } from "@codemirror/language"; +import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightActiveLine } from "@codemirror/view"; +import { markdown } from "@codemirror/lang-markdown"; +import { css } from "@codemirror/lang-css"; - getInitialState : function() { - return { - docs : {} - }; - }, +const CodeEditor = forwardRef(({ value = "", onChange = () => {} }, ref) => { + const editorRef = useRef(null); + const viewRef = useRef(null); - editor : React.createRef(null), + // --- init editor --- + useEffect(() => { + if (!editorRef.current) return; - async componentDidMount() { - CodeMirror = (await import('codemirror')).default; - this.CodeMirror = CodeMirror; - - await import('codemirror/mode/gfm/gfm.js'); - await import('codemirror/mode/css/css.js'); - await import('codemirror/mode/javascript/javascript.js'); - - // addons - await import('codemirror/addon/fold/foldcode.js'); - await import('codemirror/addon/fold/foldgutter.js'); - await import('codemirror/addon/fold/xml-fold.js'); - await import('codemirror/addon/search/search.js'); - await import('codemirror/addon/search/searchcursor.js'); - await import('codemirror/addon/search/jump-to-line.js'); - await import('codemirror/addon/search/match-highlighter.js'); - await import('codemirror/addon/search/matchesonscrollbar.js'); - await import('codemirror/addon/dialog/dialog.js'); - await import('codemirror/addon/scroll/scrollpastend.js'); - await import('codemirror/addon/edit/closetag.js'); - await import('codemirror/addon/hint/show-hint.js'); - // import 'codemirror/addon/selection/active-line.js'; - // import 'codemirror/addon/edit/trailingspace.js'; - - - // register helpers dynamically as well - const foldPagesCode = (await import('./fold-pages')).default; - const foldCSSCode = (await import('./fold-css')).default; - foldPagesCode.registerHomebreweryHelper(CodeMirror); - foldCSSCode.registerHomebreweryHelper(CodeMirror); - - this.buildEditor(); - const newDoc = CodeMirror?.Doc(this.props.value, this.props.language); - this.codeMirror?.swapDoc(newDoc); - }, - - - componentDidUpdate : function(prevProps) { - if(prevProps.view !== this.props.view){ //view changed; swap documents - let newDoc; - - if(!this.state.docs[this.props.view]) { - newDoc = CodeMirror?.Doc(this.props.value, this.props.language); - } else { - newDoc = this.state.docs[this.props.view]; + const updateListener = EditorView.updateListener.of((update) => { + if (update.docChanged) { + onChange(update.state.doc.toString()); } - - const oldDoc = { [prevProps.view]: this.codeMirror?.swapDoc(newDoc) }; - - this.setState((prevState)=>({ - docs : _.merge({}, prevState.docs, oldDoc) - })); - - this.props.rerenderParent(); - } else if(this.codeMirror?.getValue() != this.props.value) { //update editor contents if brew.text is changed from outside - this.codeMirror?.setValue(this.props.value); - } - - if(this.props.enableFolding) { - this.codeMirror?.setOption('foldOptions', this.foldOptions(this.codeMirror)); - } else { - this.codeMirror?.setOption('foldOptions', false); - } - - if(prevProps.editorTheme !== this.props.editorTheme){ - this.codeMirror?.setOption('theme', this.props.editorTheme); - } - }, - - buildEditor : function() { - this.codeMirror = CodeMirror(this.editor.current, { - lineNumbers : true, - lineWrapping : this.props.wrap, - indentWithTabs : false, - tabSize : 2, - smartIndent : false, - historyEventDelay : 250, - scrollPastEnd : true, - extraKeys : { - 'Tab' : this.indent, - 'Shift-Tab' : this.dedent, - 'Ctrl-B' : this.makeBold, - 'Cmd-B' : this.makeBold, - 'Shift-Ctrl-=' : this.makeSuper, - 'Shift-Cmd-=' : this.makeSuper, - 'Ctrl-=' : this.makeSub, - 'Cmd-=' : this.makeSub, - 'Ctrl-I' : this.makeItalic, - 'Cmd-I' : this.makeItalic, - 'Ctrl-U' : this.makeUnderline, - 'Cmd-U' : this.makeUnderline, - 'Ctrl-.' : this.makeNbsp, - 'Cmd-.' : this.makeNbsp, - 'Shift-Ctrl-.' : this.makeSpace, - 'Shift-Cmd-.' : this.makeSpace, - 'Shift-Ctrl-,' : this.removeSpace, - 'Shift-Cmd-,' : this.removeSpace, - 'Ctrl-M' : this.makeSpan, - 'Cmd-M' : this.makeSpan, - 'Shift-Ctrl-M' : this.makeDiv, - 'Shift-Cmd-M' : this.makeDiv, - 'Ctrl-/' : this.makeComment, - 'Cmd-/' : this.makeComment, - 'Ctrl-K' : this.makeLink, - 'Cmd-K' : this.makeLink, - 'Ctrl-L' : ()=>this.makeList('UL'), - 'Cmd-L' : ()=>this.makeList('UL'), - 'Shift-Ctrl-L' : ()=>this.makeList('OL'), - 'Shift-Cmd-L' : ()=>this.makeList('OL'), - 'Shift-Ctrl-1' : ()=>this.makeHeader(1), - 'Shift-Ctrl-2' : ()=>this.makeHeader(2), - 'Shift-Ctrl-3' : ()=>this.makeHeader(3), - 'Shift-Ctrl-4' : ()=>this.makeHeader(4), - 'Shift-Ctrl-5' : ()=>this.makeHeader(5), - 'Shift-Ctrl-6' : ()=>this.makeHeader(6), - 'Shift-Cmd-1' : ()=>this.makeHeader(1), - 'Shift-Cmd-2' : ()=>this.makeHeader(2), - 'Shift-Cmd-3' : ()=>this.makeHeader(3), - 'Shift-Cmd-4' : ()=>this.makeHeader(4), - 'Shift-Cmd-5' : ()=>this.makeHeader(5), - 'Shift-Cmd-6' : ()=>this.makeHeader(6), - 'Shift-Ctrl-Enter' : this.newColumn, - 'Shift-Cmd-Enter' : this.newColumn, - 'Ctrl-Enter' : this.newPage, - 'Cmd-Enter' : this.newPage, - 'Ctrl-F' : 'findPersistent', - 'Cmd-F' : 'findPersistent', - 'Shift-Enter' : 'findPersistentPrevious', - 'Ctrl-[' : this.foldAllCode, - 'Cmd-[' : this.foldAllCode, - 'Ctrl-]' : this.unfoldAllCode, - 'Cmd-]' : this.unfoldAllCode - }, - foldGutter : true, - foldOptions : this.foldOptions(this.codeMirror), - gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - autoCloseTags : true, - styleActiveLine : true, - showTrailingSpace : false, - theme : this.props.editorTheme - // specialChars : / /, - // specialCharPlaceholder : function(char) { - // const el = document.createElement('span'); - // el.className = 'cm-space'; - // el.innerHTML = ' '; - // return el; - // } }); - // Add custom behaviors (auto-close curlies and auto-complete emojis) - closeTag.autoCloseCurlyBraces(CodeMirror, this.codeMirror); - autoCompleteEmoji.showAutocompleteEmoji(CodeMirror, this.codeMirror); + const boldCommand = (view) => { + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = `**${selected}**`; - // Note: codeMirror passes a copy of itself in this callback. cm === this.codeMirror?. Either one works. - this.codeMirror?.on('change', (cm)=>{this.props.onChange(cm.getValue());}); - this.updateSize(); - }, + view.dispatch({ + changes: { from, to, insert: text }, + selection: { anchor: from + text.length }, + }); - // Use for GFM tabs that use common hot-keys - isGFM : function() { - if((this.isGFM()) || (this.props.tab === 'brewSnippets')) return true; - return false; - }, - - isBrewText : function() { - if(this.isGFM()) return true; - return false; - }, - - isBrewSnippets : function() { - if(this.props.tab === 'brewSnippets') return true; - return false; - }, - - indent : function () { - const cm = this.codeMirror; - if(cm.somethingSelected()) { - cm.execCommand('indentMore'); - } else { - cm.execCommand('insertSoftTab'); - } - }, - - dedent : function () { - this.codeMirror?.execCommand('indentLess'); - }, - - makeHeader : function (number) { - if(!this.isGFM()) return; - const selection = this.codeMirror?.getSelection(); - const header = Array(number).fill('#').join(''); - this.codeMirror?.replaceSelection(`${header} ${selection}`, 'around'); - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setCursor({ line: cursor.line, ch: cursor.ch + selection.length + number + 1 }); - }, - - makeBold : function() { - if(!this.isGFM()) return; - const selection = this.codeMirror?.getSelection(), t = selection.slice(0, 2) === '**' && selection.slice(-2) === '**'; - this.codeMirror?.replaceSelection(t ? selection.slice(2, -2) : `**${selection}**`, 'around'); - if(selection.length === 0){ - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setCursor({ line: cursor.line, ch: cursor.ch - 2 }); - } - }, - - makeItalic : function() { - if(!this.isGFM()) return; - const selection = this.codeMirror.getSelection(), t = selection.slice(0, 1) === '*' && selection.slice(-1) === '*'; - this.codeMirror?.replaceSelection(t ? selection.slice(1, -1) : `*${selection}*`, 'around'); - if(selection.length === 0){ - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setCursor({ line: cursor.line, ch: cursor.ch - 1 }); - } - }, - - makeSuper : function() { - if(!this.isGFM()) return; - const selection = this.codeMirror.getSelection(), t = selection.slice(0, 1) === '^' && selection.slice(-1) === '^'; - this.codeMirror?.replaceSelection(t ? selection.slice(1, -1) : `^${selection}^`, 'around'); - if(selection.length === 0){ - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setCursor({ line: cursor.line, ch: cursor.ch - 1 }); - } - }, - - makeSub : function() { - if(!this.isGFM()) return; - const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '^^' && selection.slice(-2) === '^^'; - this.codeMirror?.replaceSelection(t ? selection.slice(2, -2) : `^^${selection}^^`, 'around'); - if(selection.length === 0){ - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setCursor({ line: cursor.line, ch: cursor.ch - 2 }); - } - }, - - - makeNbsp : function() { - if(!this.isGFM()) return; - this.codeMirror?.replaceSelection(' ', 'end'); - }, - - makeSpace : function() { - if(!this.isGFM()) return; - const selection = this.codeMirror?.getSelection(); - const t = selection.slice(0, 8) === '{{width:' && selection.slice(0 -4) === '% }}'; - if(t){ - const percent = parseInt(selection.slice(8, -4)) + 10; - this.codeMirror?.replaceSelection(percent < 90 ? `{{width:${percent}% }}` : '{{width:100% }}', 'around'); - } else { - this.codeMirror?.replaceSelection(`{{width:10% }}`, 'around'); - } - }, - - removeSpace : function() { - if(!this.isGFM()) return; - const selection = this.codeMirror?.getSelection(); - const t = selection.slice(0, 8) === '{{width:' && selection.slice(0 -4) === '% }}'; - if(t){ - const percent = parseInt(selection.slice(8, -4)) - 10; - this.codeMirror?.replaceSelection(percent > 10 ? `{{width:${percent}% }}` : '', 'around'); - } - }, - - newColumn : function() { - if(!this.isGFM()) return; - this.codeMirror?.replaceSelection('\n\\column\n\n', 'end'); - }, - - newPage : function() { - if(!this.isGFM()) return; - this.codeMirror?.replaceSelection('\n\\page\n\n', 'end'); - }, - - injectText : function(injectText, overwrite=true) { - const cm = this.codeMirror; - if(!overwrite) { - cm.setCursor(cm.getCursor('from')); - } - cm.replaceSelection(injectText, 'end'); - cm.focus(); - }, - - makeUnderline : function() { - if(!this.isGFM()) return; - const selection = this.codeMirror.getSelection(), t = selection.slice(0, 3) === '' && selection.slice(-4) === ''; - this.codeMirror?.replaceSelection(t ? selection.slice(3, -4) : `${selection}`, 'around'); - if(selection.length === 0){ - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setCursor({ line: cursor.line, ch: cursor.ch - 4 }); - } - }, - - makeSpan : function() { - if(!this.isGFM()) return; - const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '{{' && selection.slice(-2) === '}}'; - this.codeMirror?.replaceSelection(t ? selection.slice(2, -2) : `{{ ${selection}}}`, 'around'); - if(selection.length === 0){ - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setCursor({ line: cursor.line, ch: cursor.ch - 2 }); - } - }, - - makeDiv : function() { - if(!this.isGFM()) return; - const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '{{' && selection.slice(-2) === '}}'; - this.codeMirror?.replaceSelection(t ? selection.slice(2, -2) : `{{\n${selection}\n}}`, 'around'); - if(selection.length === 0){ - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setCursor({ line: cursor.line - 1, ch: cursor.ch }); // set to -2? if wanting to enter classes etc. if so, get rid of first \n when replacing selection - } - }, - - makeComment : function() { - let regex; - let cursorPos; - let newComment; - const selection = this.codeMirror?.getSelection(); - if(this.isGFM()){ - regex = /^\s*()\s*$/gs; - cursorPos = 4; - newComment = ``; - } else { - regex = /^\s*(\/\*\s?)(.*?)(\s?\*\/)\s*$/gs; - cursorPos = 3; - newComment = `/* ${selection} */`; - } - this.codeMirror?.replaceSelection(regex.test(selection) == true ? selection.replace(regex, '$2') : newComment, 'around'); - if(selection.length === 0){ - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setCursor({ line: cursor.line, ch: cursor.ch - cursorPos }); + return true; }; - }, - makeLink : function() { - if(!this.isGFM()) return; - const isLink = /^\[(.*)\]\((.*)\)$/; - const selection = this.codeMirror?.getSelection().trim(); - let match; - if(match = isLink.exec(selection)){ - const altText = match[1]; - const url = match[2]; - this.codeMirror?.replaceSelection(`${altText} ${url}`); - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setSelection({ line: cursor.line, ch: cursor.ch - url.length }, { line: cursor.line, ch: cursor.ch }); - } else { - this.codeMirror?.replaceSelection(`[${selection || 'alt text'}](url)`); - const cursor = this.codeMirror?.getCursor(); - this.codeMirror?.setSelection({ line: cursor.line, ch: cursor.ch - 4 }, { line: cursor.line, ch: cursor.ch - 1 }); - } - }, + const italicCommand = (view) => { + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = `*${selected}*`; - makeList : function(listType) { - if(!this.isGFM()) return; - const selectionStart = this.codeMirror.getCursor('from'), selectionEnd = this.codeMirror.getCursor('to'); - this.codeMirror?.setSelection( - { line: selectionStart.line, ch: 0 }, - { line: selectionEnd.line, ch: this.codeMirror?.getLine(selectionEnd.line).length } - ); - const newSelection = this.codeMirror?.getSelection(); + view.dispatch({ + changes: { from, to, insert: text }, + selection: { anchor: from + text.length }, + }); - const regex = /^\d+\.\s|^-\s/gm; - if(newSelection.match(regex) != null){ // if selection IS A LIST - this.codeMirror?.replaceSelection(newSelection.replace(regex, ''), 'around'); - } else { // if selection IS NOT A LIST - listType == 'UL' ? this.codeMirror?.replaceSelection(newSelection.replace(/^/gm, `- `), 'around') : - this.codeMirror?.replaceSelection(newSelection.replace(/^/gm, (()=>{ - let n = 1; - return ()=>{ - return `${n++}. `; - }; - })()), 'around'); - } - }, - - foldAllCode : function() { - this.codeMirror?.execCommand('foldAll'); - }, - - unfoldAllCode : function() { - this.codeMirror?.execCommand('unfoldAll'); - }, - - //=-- Externally used -==// - setCursorPosition : function(line, char){ - setTimeout(()=>{ - this.codeMirror?.focus(); - this.codeMirror?.doc.setCursor(line, char); - }, 10); - }, - getCursorPosition : function(){ - return this.codeMirror?.getCursor(); - }, - getTopVisibleLine : function(){ - const rect = this.codeMirror?.getWrapperElement().getBoundingClientRect(); - const topVisibleLine = this.codeMirror?.lineAtHeight(rect.top, 'window'); - return topVisibleLine; - }, - updateSize : function(){ - this.codeMirror?.refresh(); - }, - redo : function(){ - return this.codeMirror?.redo(); - }, - undo : function(){ - return this.codeMirror?.undo(); - }, - historySize : function(){ - return this.codeMirror?.doc.historySize(); - }, - - foldOptions : function(cm){ - return { - scanUp : true, - rangeFinder : this.props.language === 'css' ? CodeMirror.fold.homebrewerycss : CodeMirror.fold.homebrewery, - widget : (from, to)=>{ - let text = ''; - let currentLine = from.line; - let maxLength = 50; - - let foldPreviewText = ''; - while (currentLine <= to.line && text.length <= maxLength) { - const currentText = this.codeMirror?.getLine(currentLine); - currentLine++; - if(currentText[0] == '#'){ - foldPreviewText = currentText; - break; - } - if(!foldPreviewText && currentText != '\n') { - foldPreviewText = currentText; - } - } - text = foldPreviewText || `Lines ${from.line+1}-${to.line+1}`; - text = text.replace('{', '').trim(); - - // Truncate data URLs at `data:` - const startOfData = text.indexOf('data:'); - if(startOfData > 0) - maxLength = Math.min(startOfData + 5, maxLength); - - if(text.length > maxLength) - text = `${text.slice(0, maxLength)}...`; - - return `\u21A4 ${text} \u21A6`; - } + return true; }; - }, - //----------------------// - render : function(){ - return <> - -
- ; - } + const customKeymap = keymap.of([ + { key: "Mod-b", run: boldCommand }, + { key: "Mod-i", run: italicCommand }, + ]); + + const state = EditorState.create({ + doc: value, + extensions: [ + history(), + keymap.of(defaultKeymap), + customKeymap, + updateListener, + markdown(), + css(), + highlightActiveLine(), + highlightActiveLineGutter(), + keymap.of(foldKeymap), + foldGutter(), + lineNumbers(), + + ], + }); + + viewRef.current = new EditorView({ + state, + parent: editorRef.current, + }); + + return () => viewRef.current?.destroy(); + }, []); + + // --- sync external value --- + useEffect(() => { + const view = viewRef.current; + if (!view) return; + + const current = view.state.doc.toString(); + if (value !== current) { + view.dispatch({ + changes: { from: 0, to: current.length, insert: value }, + }); + } + }, [value]); + + // --- exposed API --- + useImperativeHandle(ref, () => ({ + getValue: () => viewRef.current.state.doc.toString(), + + setValue: (text) => { + const view = viewRef.current; + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: text }, + }); + }, + + injectText: (text) => { + const view = viewRef.current; + const { from, to } = view.state.selection.main; + + view.dispatch({ + changes: { from, to, insert: text }, + selection: { anchor: from + text.length }, + }); + + view.focus(); + }, + + getCursorPosition: () => viewRef.current.state.selection.main.head, + + setCursorPosition: (pos) => { + viewRef.current.dispatch({ selection: { anchor: pos } }); + viewRef.current.focus(); + }, + + undo: () => undo(viewRef.current), + redo: () => redo(viewRef.current), + + historySize: () => { + const view = viewRef.current; + if (!view) return { done: 0, undone: 0 }; + + const h = view.state.field(historyField, false); + if (!h) return { done: 0, undone: 0 }; + + return { done: h.done.length, undone: h.undone.length }; + }, + + focus: () => viewRef.current.focus(), + })); + + return
; }); -export default CodeEditor; - +export default CodeEditor; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c3236f5d4..141c8a879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,13 +15,20 @@ "@babel/preset-env": "^7.29.0", "@babel/preset-react": "^7.28.5", "@babel/runtime": "^7.28.6", + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/language": "^6.12.2", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", "@googleapis/drive": "^20.1.0", "@sanity/diff-match-patch": "^3.2.0", "@vitejs/plugin-react": "^5.1.2", "body-parser": "^2.2.0", "classnames": "^2.5.1", - "codemirror": "^5.65.6", + "codemirror": "^6.0.2", "cookie-parser": "^1.4.7", "core-js": "^3.47.0", "cors": "^2.8.5", @@ -2037,6 +2044,147 @@ "@keyv/serialize": "^1.1.1" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz", + "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz", + "integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz", + "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.40.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.40.0.tgz", + "integrity": "sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.6.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@csstools/color-helpers": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", @@ -3358,6 +3506,79 @@ "dev": true, "license": "MIT" }, + "node_modules/@lezer/common": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", + "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.2.tgz", + "integrity": "sha512-tRZAPl1j0pkmPL/pGG85GAbyQYnYv0j6UEdEt5e4ZYvp+OxDu2zVp2c/YddiuO8ZTa2CHsVELqRd/fGWjlISrg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/markdown": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz", + "integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@mongodb-js/saslprep": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", @@ -5383,10 +5604,19 @@ } }, "node_modules/codemirror": { - "version": "5.65.21", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.21.tgz", - "integrity": "sha512-6teYk0bA0nR3QP0ihGMoxuKzpl5W80FpnHpBJpgy66NK3cZv5b/d/HY8PnRvfSsCG1MTfr92u2WUl+wT0E40mQ==", - "license": "MIT" + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } }, "node_modules/collect-v8-coverage": { "version": "1.0.3", @@ -5600,6 +5830,12 @@ "object-assign": "^4.1.1" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -12031,6 +12267,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", @@ -13127,6 +13369,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index f6ff1ba03..c68c7feab 100644 --- a/package.json +++ b/package.json @@ -91,13 +91,20 @@ "@babel/preset-env": "^7.29.0", "@babel/preset-react": "^7.28.5", "@babel/runtime": "^7.28.6", + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/language": "^6.12.2", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", "@googleapis/drive": "^20.1.0", "@sanity/diff-match-patch": "^3.2.0", "@vitejs/plugin-react": "^5.1.2", "body-parser": "^2.2.0", "classnames": "^2.5.1", - "codemirror": "^5.65.6", + "codemirror": "^6.0.2", "cookie-parser": "^1.4.7", "core-js": "^3.47.0", "cors": "^2.8.5", diff --git a/vitePlugins/generateAssetsPlugin.js b/vitePlugins/generateAssetsPlugin.js index caea2c1e8..a5ebfc916 100644 --- a/vitePlugins/generateAssetsPlugin.js +++ b/vitePlugins/generateAssetsPlugin.js @@ -63,17 +63,10 @@ export function generateAssetsPlugin(isDev = false) { await fs.copy('./client/icons', `${buildDir}/icons`); // Compile CodeMirror editor themes - const editorThemesBuildDir = `${buildDir}/homebrew/cm-themes`; - await fs.copy('./node_modules/codemirror/theme', editorThemesBuildDir); - await fs.copy('./themes/codeMirror/customThemes', editorThemesBuildDir); - - const editorThemeFiles = fs.readdirSync(editorThemesBuildDir); - await fs.outputFile(`${buildDir}/homebrew/codeMirror/editorThemes.json`, - JSON.stringify(['default', ...editorThemeFiles.map((f)=>f.slice(0, -4))], null, 2), + await fs.outputFile( + `${buildDir}/homebrew/codeMirror/editorThemes.json`, + JSON.stringify(['light', 'dark'], null, 2) ); - - // Copy remaining CodeMirror assets - await fs.copy('./themes/codeMirror', `${buildDir}/homebrew/codeMirror`); }, }; } From 42e254c9c72f8faaded4042b3a9890c044fe6b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sat, 21 Mar 2026 22:53:38 +0100 Subject: [PATCH 02/56] basic css changes --- client/components/codeEditor/codeEditor.jsx | 249 ++++++++++-------- client/components/codeEditor/codeEditor.less | 101 ++++--- client/homebrew/editor/editor.less | 2 +- package-lock.json | 13 + package.json | 1 + themes/codeMirror/customEditorStyles.less | 2 +- .../codeMirror/customThemes/darkbrewery.css | 10 +- themes/codeMirror/customThemes/darkvision.css | 4 +- 8 files changed, 216 insertions(+), 166 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index c4189e21d..0c7740638 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -6,138 +6,153 @@ import { foldGutter, foldKeymap } from "@codemirror/language"; import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightActiveLine } from "@codemirror/view"; import { markdown } from "@codemirror/lang-markdown"; import { css } from "@codemirror/lang-css"; +import { oneDark } from "@codemirror/theme-one-dark"; -const CodeEditor = forwardRef(({ value = "", onChange = () => {} }, ref) => { - const editorRef = useRef(null); - const viewRef = useRef(null); +const CodeEditor = forwardRef( + ({ value = "", onChange = () => {}, language, editorTheme, tab, view, style, ...props }, ref) => { + const editorRef = useRef(null); + const viewRef = useRef(null); - // --- init editor --- - useEffect(() => { - if (!editorRef.current) return; + console.log(props); - const updateListener = EditorView.updateListener.of((update) => { - if (update.docChanged) { - onChange(update.state.doc.toString()); + // --- init editor --- + useEffect(() => { + if (!editorRef.current) return; + + const updateListener = EditorView.updateListener.of((update) => { + if (update.docChanged) { + onChange(update.state.doc.toString()); + } + }); + + const boldCommand = (view) => { + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = `**${selected}**`; + + view.dispatch({ + changes: { from, to, insert: text }, + selection: { anchor: from + text.length }, + }); + + return true; + }; + + const italicCommand = (view) => { + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = `*${selected}*`; + + view.dispatch({ + changes: { from, to, insert: text }, + selection: { anchor: from + text.length }, + }); + + return true; + }; + + const customKeymap = keymap.of([ + { key: "Mod-b", run: boldCommand }, + { key: "Mod-i", run: italicCommand }, + ]); + + const languageExtension = () => { + switch (language) { + case "gfm": + return markdown({ codeLanguages: [] }); // GitHub-flavored Markdown + case "css": + return css(); + default: + return markdown(); + } + }; + + const state = EditorState.create({ + doc: value, + extensions: [ + history(), + keymap.of(defaultKeymap), + customKeymap, + updateListener, + languageExtension(), + highlightActiveLine(), + highlightActiveLineGutter(), + keymap.of(foldKeymap), + foldGutter(), + lineNumbers(), + oneDark, + ], + }); + + viewRef.current = new EditorView({ + state, + parent: editorRef.current, + }); + + return () => viewRef.current?.destroy(); + }, []); + + // --- sync external value --- + useEffect(() => { + const view = viewRef.current; + if (!view) return; + + const current = view.state.doc.toString(); + if (value !== current) { + view.dispatch({ + changes: { from: 0, to: current.length, insert: value }, + }); } - }); + }, [value]); - const boldCommand = (view) => { - const { from, to } = view.state.selection.main; - const selected = view.state.doc.sliceString(from, to); - const text = `**${selected}**`; + // --- exposed API --- + useImperativeHandle(ref, () => ({ + getValue: () => viewRef.current.state.doc.toString(), - view.dispatch({ - changes: { from, to, insert: text }, - selection: { anchor: from + text.length }, - }); + setValue: (text) => { + const view = viewRef.current; + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: text }, + }); + }, - return true; - }; + injectText: (text) => { + const view = viewRef.current; + const { from, to } = view.state.selection.main; - const italicCommand = (view) => { - const { from, to } = view.state.selection.main; - const selected = view.state.doc.sliceString(from, to); - const text = `*${selected}*`; + view.dispatch({ + changes: { from, to, insert: text }, + selection: { anchor: from + text.length }, + }); - view.dispatch({ - changes: { from, to, insert: text }, - selection: { anchor: from + text.length }, - }); + view.focus(); + }, - return true; - }; + getCursorPosition: () => viewRef.current.state.selection.main.head, - const customKeymap = keymap.of([ - { key: "Mod-b", run: boldCommand }, - { key: "Mod-i", run: italicCommand }, - ]); + setCursorPosition: (pos) => { + viewRef.current.dispatch({ selection: { anchor: pos } }); + viewRef.current.focus(); + }, - const state = EditorState.create({ - doc: value, - extensions: [ - history(), - keymap.of(defaultKeymap), - customKeymap, - updateListener, - markdown(), - css(), - highlightActiveLine(), - highlightActiveLineGutter(), - keymap.of(foldKeymap), - foldGutter(), - lineNumbers(), - - ], - }); + undo: () => undo(viewRef.current), + redo: () => redo(viewRef.current), - viewRef.current = new EditorView({ - state, - parent: editorRef.current, - }); + historySize: () => { + const view = viewRef.current; + if (!view) return { done: 0, undone: 0 }; - return () => viewRef.current?.destroy(); - }, []); + const h = view.state.field(historyField, false); + if (!h) return { done: 0, undone: 0 }; - // --- sync external value --- - useEffect(() => { - const view = viewRef.current; - if (!view) return; + return { done: h.done.length, undone: h.undone.length }; + }, - const current = view.state.doc.toString(); - if (value !== current) { - view.dispatch({ - changes: { from: 0, to: current.length, insert: value }, - }); - } - }, [value]); + focus: () => viewRef.current.focus(), + })); - // --- exposed API --- - useImperativeHandle(ref, () => ({ - getValue: () => viewRef.current.state.doc.toString(), + return
; + }, +); - setValue: (text) => { - const view = viewRef.current; - view.dispatch({ - changes: { from: 0, to: view.state.doc.length, insert: text }, - }); - }, - - injectText: (text) => { - const view = viewRef.current; - const { from, to } = view.state.selection.main; - - view.dispatch({ - changes: { from, to, insert: text }, - selection: { anchor: from + text.length }, - }); - - view.focus(); - }, - - getCursorPosition: () => viewRef.current.state.selection.main.head, - - setCursorPosition: (pos) => { - viewRef.current.dispatch({ selection: { anchor: pos } }); - viewRef.current.focus(); - }, - - undo: () => undo(viewRef.current), - redo: () => redo(viewRef.current), - - historySize: () => { - const view = viewRef.current; - if (!view) return { done: 0, undone: 0 }; - - const h = view.state.field(historyField, false); - if (!h) return { done: 0, undone: 0 }; - - return { done: h.done.length, undone: h.undone.length }; - }, - - focus: () => viewRef.current.focus(), - })); - - return
; -}); - -export default CodeEditor; \ No newline at end of file +export default CodeEditor; diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less index 89d0c9497..be4e8b2f2 100644 --- a/client/components/codeEditor/codeEditor.less +++ b/client/components/codeEditor/codeEditor.less @@ -1,60 +1,81 @@ -@import (less) 'codemirror/lib/codemirror.css'; -@import (less) 'codemirror/addon/fold/foldgutter.css'; -@import (less) 'codemirror/addon/search/matchesonscrollbar.css'; -@import (less) 'codemirror/addon/dialog/dialog.css'; -@import (less) 'codemirror/addon/hint/show-hint.css'; - -//Icon fonts included so they can appear in emoji autosuggest dropdown -@import (less) '@themes/fonts/iconFonts/diceFont.less'; -@import (less) '@themes/fonts/iconFonts/elderberryInn.less'; -@import (less) '@themes/fonts/iconFonts/gameIcons.less'; -@import (less) '@themes/fonts/iconFonts/fontAwesome.less'; +// Icon fonts for emoji/autocomplete +@import (less) "@themes/fonts/iconFonts/diceFont.less"; +@import (less) "@themes/fonts/iconFonts/elderberryInn.less"; +@import (less) "@themes/fonts/iconFonts/gameIcons.less"; +@import (less) "@themes/fonts/iconFonts/fontAwesome.less"; @keyframes sourceMoveAnimation { - 50% { color : white;background-color : red;} - 100% { color : unset;background-color : unset;} + 50% { + color: white; + background-color: red; + } + 100% { + color: unset; + background-color: unset; + } } .codeEditor { - @media screen and (pointer : coarse) { - font-size : 16px; - } - .CodeMirror-foldmarker { - font-family : inherit; - font-weight : 600; - color : grey; - text-shadow : none; + font-family: monospace; + height: 100%; + + @media screen and (pointer: coarse) { + font-size: 16px; } - .CodeMirror-foldgutter { - cursor : pointer; - border-left : 1px solid #EEEEEE; - transition : background 0.1s; - &:hover { background : #DDDDDD; } + /* Line numbers and gutters */ + .cm-gutters { + background-color: #f0f0f0; + color: #555; + border-right: 1px solid #ddd; } - .sourceMoveFlash .CodeMirror-line { - animation-name : sourceMoveAnimation; - animation-duration : 0.4s; + /* Folding gutter */ + .cm-foldGutter { + cursor: pointer; + color: grey; + font-weight: 600; + transition: background 0.1s; + + &:hover { + background: #dddddd; + } } - .CodeMirror-search-field { - width:25em !important; - outline:1px inset #00000055 !important; + /* Active line */ + .cm-activeLine { + background-color: #f5f5f5; } + .cm-activeLineGutter { + background-color: #e0e0e0; + } + + /* Flash animation for source moves */ + .sourceMoveFlash .cm-line { + animation-name: sourceMoveAnimation; + animation-duration: 0.4s; + } + + /* Search input */ + .cm-searchField { + width: 25em !important; + outline: 1px inset #00000055 !important; + } + + /* Tab character visualization (optional) */ //.cm-tab { - // background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right; + // background: url(...) no-repeat right; //} - //.cm-trailingspace { - // .cm-space { - // background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right; - // } + /* Trailing space visualization (optional) */ + //.cm-trailingSpace .cm-space { + // background: url(...) no-repeat right; //} } +/* Emoji preview styling */ .emojiPreview { - font-size : 1.5em; - line-height : 1.2em; -} \ No newline at end of file + font-size: 1.5em; + line-height: 1.2em; +} diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 3851b50c5..9db6df26f 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -9,7 +9,7 @@ background:white; .codeEditor { height : calc(100% - 25px); - .CodeMirror { height : 100%; } + .cm-editor { height : 100%; } .pageLine, .snippetLine { background : #33333328; border-top : #333399 solid 1px; diff --git a/package-lock.json b/package-lock.json index 141c8a879..b372be43f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@codemirror/lang-markdown": "^6.5.0", "@codemirror/language": "^6.12.2", "@codemirror/state": "^6.6.0", + "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", "@googleapis/drive": "^20.1.0", @@ -2173,6 +2174,18 @@ "@marijn/find-cluster-break": "^1.0.0" } }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, "node_modules/@codemirror/view": { "version": "6.40.0", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.40.0.tgz", diff --git a/package.json b/package.json index c68c7feab..96a492b18 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "@codemirror/lang-markdown": "^6.5.0", "@codemirror/language": "^6.12.2", "@codemirror/state": "^6.6.0", + "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", "@googleapis/drive": "^20.1.0", diff --git a/themes/codeMirror/customEditorStyles.less b/themes/codeMirror/customEditorStyles.less index 8c48c1b43..b6e95d14b 100644 --- a/themes/codeMirror/customEditorStyles.less +++ b/themes/codeMirror/customEditorStyles.less @@ -1,4 +1,4 @@ -.editor .codeEditor .CodeMirror { +.editor .codeEditor .cm-editor { // Themes with dark backgrounds &.cm-s-3024-night, &.cm-s-abbott, diff --git a/themes/codeMirror/customThemes/darkbrewery.css b/themes/codeMirror/customThemes/darkbrewery.css index 6fba4001c..ec043c5ba 100644 --- a/themes/codeMirror/customThemes/darkbrewery.css +++ b/themes/codeMirror/customThemes/darkbrewery.css @@ -15,11 +15,11 @@ --highlight: #bcbcbc; color: #91A6AA; background: var(--bg); - .CodeMirror-scroll { - .CodeMirror-gutters { + .cm-scroller { + .cm-gutters { border-right: 1px solid #555; background: var(--bg); - .CodeMirror-gutter { + .cm-gutter { background-color: var(--bg); &.CodeMirror-foldgutter { cursor: pointer; @@ -31,9 +31,9 @@ } } } - .CodeMirror-lines { + .cm-content { /* Line numbers*/ - .CodeMirror-linenumber.CodeMirror-gutter-elt { + .CodeMirror-linenumber.cm-gutter-elt { background-color: var(--bg); color: #81969A; } diff --git a/themes/codeMirror/customThemes/darkvision.css b/themes/codeMirror/customThemes/darkvision.css index 4c74d105e..40a17ec2b 100644 --- a/themes/codeMirror/customThemes/darkvision.css +++ b/themes/codeMirror/customThemes/darkvision.css @@ -18,13 +18,13 @@ } /* Line number stuff */ - .CodeMirror-gutter-elt { + .cm-gutter-elt { color: #81969A; } .CodeMirror-linenumber { background-color: #0C0C0C; } - .CodeMirror-gutter { + .cm-gutter { background-color: #0C0C0C; } From c18e8edd34aa927e20433fbef2b74d8f91b9b65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sat, 21 Mar 2026 23:13:21 +0100 Subject: [PATCH 03/56] code folding --- client/components/codeEditor/codeEditor.jsx | 2 ++ client/components/codeEditor/codeEditor.less | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 0c7740638..b1beea3cb 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -1,3 +1,5 @@ + +import './codeEditor.less'; import React, { useEffect, useRef, forwardRef, useImperativeHandle } from "react"; import { EditorState } from "@codemirror/state"; diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less index be4e8b2f2..2aba0ae72 100644 --- a/client/components/codeEditor/codeEditor.less +++ b/client/components/codeEditor/codeEditor.less @@ -18,11 +18,26 @@ .codeEditor { font-family: monospace; height: 100%; + width:100%; @media screen and (pointer: coarse) { font-size: 16px; } + .cm-scroller { + width:100%; + + } + .cm-content { + width:70%; + + .cm-line { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; + } + } + /* Line numbers and gutters */ .cm-gutters { background-color: #f0f0f0; From 4ee9dfa509b6e81b0b26fe245cc77455e6279d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 22 Mar 2026 00:10:09 +0100 Subject: [PATCH 04/56] add basic light theme and scrollpastend --- client/components/codeEditor/codeEditor.jsx | 33 +- client/components/codeEditor/codeEditor.less | 13 - package-lock.json | 1705 +++++++++--------- package.json | 3 +- 4 files changed, 867 insertions(+), 887 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index b1beea3cb..d44f65e92 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -1,17 +1,35 @@ - -import './codeEditor.less'; +import "./codeEditor.less"; import React, { useEffect, useRef, forwardRef, useImperativeHandle } from "react"; import { EditorState } from "@codemirror/state"; import { defaultKeymap, history, historyField, undo, redo } from "@codemirror/commands"; import { foldGutter, foldKeymap } from "@codemirror/language"; -import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightActiveLine } from "@codemirror/view"; +import { + EditorView, + keymap, + lineNumbers, + highlightActiveLineGutter, + highlightActiveLine, + scrollPastEnd, +} from "@codemirror/view"; import { markdown } from "@codemirror/lang-markdown"; import { css } from "@codemirror/lang-css"; -import { oneDark } from "@codemirror/theme-one-dark"; +import { basicLightTheme } from "cm6-theme-basic-light"; const CodeEditor = forwardRef( - ({ value = "", onChange = () => {}, language, editorTheme, tab, view, style, ...props }, ref) => { + ( + { + value = "", + onChange = () => {}, + language = "", + tab = "brewText", + editorTheme = "default", + view, + style, + ...props + }, + ref, + ) => { const editorRef = useRef(null); const viewRef = useRef(null); @@ -76,13 +94,16 @@ const CodeEditor = forwardRef( keymap.of(defaultKeymap), customKeymap, updateListener, + EditorView.lineWrapping, + scrollPastEnd(), languageExtension(), highlightActiveLine(), highlightActiveLineGutter(), keymap.of(foldKeymap), foldGutter(), lineNumbers(), - oneDark, + basicLightTheme, + bracketMatching(), ], }); diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less index 2aba0ae72..7dc3bf0fd 100644 --- a/client/components/codeEditor/codeEditor.less +++ b/client/components/codeEditor/codeEditor.less @@ -24,19 +24,6 @@ font-size: 16px; } - .cm-scroller { - width:100%; - - } - .cm-content { - width:70%; - - .cm-line { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; - } - } /* Line numbers and gutters */ .cm-gutters { diff --git a/package-lock.json b/package-lock.json index b372be43f..0daeb5ab7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "@codemirror/lang-markdown": "^6.5.0", "@codemirror/language": "^6.12.2", "@codemirror/state": "^6.6.0", - "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", "@googleapis/drive": "^20.1.0", @@ -29,7 +28,7 @@ "@vitejs/plugin-react": "^5.1.2", "body-parser": "^2.2.0", "classnames": "^2.5.1", - "codemirror": "^6.0.2", + "cm6-theme-basic-light": "^0.2.0", "cookie-parser": "^1.4.7", "core-js": "^3.47.0", "cors": "^2.8.5", @@ -118,9 +117,9 @@ } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -142,9 +141,9 @@ } }, "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -295,9 +294,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", - "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", @@ -472,22 +471,22 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -1779,9 +1778,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", - "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", "license": "MIT", "dependencies": { "@babel/compat-data": "^7.29.0", @@ -1863,12 +1862,12 @@ } }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", - "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { @@ -1910,9 +1909,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2134,6 +2133,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz", "integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -2154,43 +2154,22 @@ "crelt": "^1.0.5" } }, - "node_modules/@codemirror/search": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", - "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.37.0", - "crelt": "^1.0.5" - } - }, "node_modules/@codemirror/state": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", "license": "MIT", + "peer": true, "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, - "node_modules/@codemirror/theme-one-dark": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", - "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/highlight": "^1.0.0" - } - }, "node_modules/@codemirror/view": { "version": "6.40.0", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.40.0.tgz", "integrity": "sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", @@ -2295,9 +2274,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.29.tgz", - "integrity": "sha512-jx9GjkkP5YHuTmko2eWAvpPnb0mB4mGRr2U7XwVNwevm8nlpobZEVk+GNmiYMk2VuA75v+plfXWyroWKmICZXg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", + "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", "dev": true, "funding": [ { @@ -2309,7 +2288,15 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0" + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } }, "node_modules/@csstools/css-tokenizer": { "version": "4.0.0", @@ -2409,21 +2396,21 @@ "license": "Apache-2.0" }, "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "dev": true, "license": "MIT", "optional": true, @@ -2432,9 +2419,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "license": "MIT", "optional": true, @@ -2443,9 +2430,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", "cpu": [ "ppc64" ], @@ -2459,9 +2446,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", "cpu": [ "arm" ], @@ -2475,9 +2462,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", "cpu": [ "arm64" ], @@ -2491,9 +2478,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", "cpu": [ "x64" ], @@ -2507,9 +2494,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", "cpu": [ "arm64" ], @@ -2523,9 +2510,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", "cpu": [ "x64" ], @@ -2539,9 +2526,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", "cpu": [ "arm64" ], @@ -2555,9 +2542,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", "cpu": [ "x64" ], @@ -2571,9 +2558,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", "cpu": [ "arm" ], @@ -2587,9 +2574,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", "cpu": [ "arm64" ], @@ -2603,9 +2590,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", "cpu": [ "ia32" ], @@ -2619,9 +2606,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", "cpu": [ "loong64" ], @@ -2635,9 +2622,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", "cpu": [ "mips64el" ], @@ -2651,9 +2638,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", "cpu": [ "ppc64" ], @@ -2667,9 +2654,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", "cpu": [ "riscv64" ], @@ -2683,9 +2670,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", "cpu": [ "s390x" ], @@ -2699,9 +2686,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", "cpu": [ "x64" ], @@ -2715,9 +2702,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", "cpu": [ "arm64" ], @@ -2731,9 +2718,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", "cpu": [ "x64" ], @@ -2747,9 +2734,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", "cpu": [ "arm64" ], @@ -2763,9 +2750,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", "cpu": [ "x64" ], @@ -2779,9 +2766,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", "cpu": [ "arm64" ], @@ -2795,9 +2782,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", "cpu": [ "x64" ], @@ -2811,9 +2798,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", "cpu": [ "arm64" ], @@ -2827,9 +2814,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", "cpu": [ "ia32" ], @@ -2843,9 +2830,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", "cpu": [ "x64" ], @@ -2916,9 +2903,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", - "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { @@ -2929,7 +2916,7 @@ "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.3", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -3034,6 +3021,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -3051,6 +3039,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3063,6 +3052,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -3126,17 +3116,17 @@ } }, "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -3144,39 +3134,38 @@ } }, "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", + "@jest/console": "30.3.0", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -3192,9 +3181,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", "dev": true, "license": "MIT", "engines": { @@ -3202,39 +3191,39 @@ } }, "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "30.2.0" + "jest-mock": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" + "expect": "30.3.0", + "jest-snapshot": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", "dev": true, "license": "MIT", "dependencies": { @@ -3245,18 +3234,18 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -3273,16 +3262,16 @@ } }, "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -3303,32 +3292,32 @@ } }, "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -3359,13 +3348,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -3390,14 +3379,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -3406,15 +3395,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", + "@jest/test-result": "30.3.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -3422,24 +3411,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.3.0", "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", + "jest-util": "30.3.0", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -3449,9 +3437,9 @@ } }, "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", "dev": true, "license": "MIT", "dependencies": { @@ -3541,6 +3529,7 @@ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", "license": "MIT", + "peer": true, "dependencies": { "@lezer/common": "^1.3.0" } @@ -3678,6 +3667,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -3704,9 +3694,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.1.tgz", + "integrity": "sha512-xB0b51TB7IfDEzAojXahmr+gfA00uYVInJGgNNkeQG6RPnCPGr7udsylFLTubuIUSRE6FkcI1NElyRt83PP5oQ==", "cpu": [ "arm" ], @@ -3717,9 +3707,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.1.tgz", + "integrity": "sha512-XOjPId0qwSDKHaIsdzHJtKCxX0+nH8MhBwvrNsT7tVyKmdTx1jJ4XzN5RZXCdTzMpufLb+B8llTC0D8uCrLhcw==", "cpu": [ "arm64" ], @@ -3730,9 +3720,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.1.tgz", + "integrity": "sha512-vQuRd28p0gQpPrS6kppd8IrWmFo42U8Pz1XLRjSZXq5zCqyMDYFABT7/sywL11mO1EL10Qhh7MVPEwkG8GiBeg==", "cpu": [ "arm64" ], @@ -3743,9 +3733,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.1.tgz", + "integrity": "sha512-x6VG6U29+Ivlnajrg1IHdzXeAwSoEHBFVO+CtC9Brugx6de712CUJobRUxsIA0KYrQvCmzNrMPFTT1A4CCqNTg==", "cpu": [ "x64" ], @@ -3756,9 +3746,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.1.tgz", + "integrity": "sha512-Sgi0Uo6t1YCHJMNO3Y8+bm+SvOanUGkoZKn/VJPwYUe2kp31X5KnXmzKd/NjW8iA3gFcfNZ64zh14uOGrIllCQ==", "cpu": [ "arm64" ], @@ -3769,9 +3759,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.1.tgz", + "integrity": "sha512-AM4xnwEZwukdhk7laMWfzWu9JGSVnJd+Fowt6Fd7QW1nrf3h0Hp7Qx5881M4aqrUlKBCybOxz0jofvIIfl7C5g==", "cpu": [ "x64" ], @@ -3782,9 +3772,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.1.tgz", + "integrity": "sha512-KUizqxpwaR2AZdAUsMWfL/C94pUu7TKpoPd88c8yFVixJ+l9hejkrwoK5Zj3wiNh65UeyryKnJyxL1b7yNqFQA==", "cpu": [ "arm" ], @@ -3795,9 +3785,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.1.tgz", + "integrity": "sha512-MZoQ/am77ckJtZGFAtPucgUuJWiop3m2R3lw7tC0QCcbfl4DRhQUBUkHWCkcrT3pqy5Mzv5QQgY6Dmlba6iTWg==", "cpu": [ "arm" ], @@ -3808,9 +3798,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.1.tgz", + "integrity": "sha512-Sez95TP6xGjkWB1608EfhCX1gdGrO5wzyN99VqzRtC17x/1bhw5VU1V0GfKUwbW/Xr1J8mSasoFoJa6Y7aGGSA==", "cpu": [ "arm64" ], @@ -3821,9 +3811,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.1.tgz", + "integrity": "sha512-9Cs2Seq98LWNOJzR89EGTZoiP8EkZ9UbQhBlDgfAkM6asVna1xJ04W2CLYWDN/RpUgOjtQvcv8wQVi1t5oQazA==", "cpu": [ "arm64" ], @@ -3834,9 +3824,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.1.tgz", + "integrity": "sha512-n9yqttftgFy7IrNEnHy1bOp6B4OSe8mJDiPkT7EqlM9FnKOwUMnCK62ixW0Kd9Clw0/wgvh8+SqaDXMFvw3KqQ==", "cpu": [ "loong64" ], @@ -3847,9 +3837,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.1.tgz", + "integrity": "sha512-SfpNXDzVTqs/riak4xXcLpq5gIQWsqGWMhN1AGRQKB4qGSs4r0sEs3ervXPcE1O9RsQ5bm8Muz6zmQpQnPss1g==", "cpu": [ "loong64" ], @@ -3860,9 +3850,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.1.tgz", + "integrity": "sha512-LjaChED0wQnjKZU+tsmGbN+9nN1XhaWUkAlSbTdhpEseCS4a15f/Q8xC2BN4GDKRzhhLZpYtJBZr2NZhR0jvNw==", "cpu": [ "ppc64" ], @@ -3873,9 +3863,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.1.tgz", + "integrity": "sha512-ojW7iTJSIs4pwB2xV6QXGwNyDctvXOivYllttuPbXguuKDX5vwpqYJsHc6D2LZzjDGHML414Tuj3LvVPe1CT1A==", "cpu": [ "ppc64" ], @@ -3886,9 +3876,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.1.tgz", + "integrity": "sha512-FP+Q6WTcxxvsr0wQczhSE+tOZvFPV8A/mUE6mhZYFW9/eea/y/XqAgRoLLMuE9Cz0hfX5bi7p116IWoB+P237A==", "cpu": [ "riscv64" ], @@ -3899,9 +3889,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.1.tgz", + "integrity": "sha512-L1uD9b/Ig8Z+rn1KttCJjwhN1FgjRMBKsPaBsDKkfUl7GfFq71pU4vWCnpOsGljycFEbkHWARZLf4lMYg3WOLw==", "cpu": [ "riscv64" ], @@ -3912,9 +3902,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.1.tgz", + "integrity": "sha512-EZc9NGTk/oSUzzOD4nYY4gIjteo2M3CiozX6t1IXGCOdgxJTlVu/7EdPeiqeHPSIrxkLhavqpBAUCfvC6vBOug==", "cpu": [ "s390x" ], @@ -3925,9 +3915,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.1.tgz", + "integrity": "sha512-NQ9KyU1Anuy59L8+HHOKM++CoUxrQWrZWXRik4BJFm+7i5NP6q/SW43xIBr80zzt+PDBJ7LeNmloQGfa0JGk0w==", "cpu": [ "x64" ], @@ -3938,9 +3928,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.1.tgz", + "integrity": "sha512-GZkLk2t6naywsveSFBsEb0PLU+JC9ggVjbndsbG20VPhar6D1gkMfCx4NfP9owpovBXTN+eRdqGSkDGIxPHhmQ==", "cpu": [ "x64" ], @@ -3951,9 +3941,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.1.tgz", + "integrity": "sha512-1hjG9Jpl2KDOetr64iQd8AZAEjkDUUK5RbDkYWsViYLC1op1oNzdjMJeFiofcGhqbNTaY2kfgqowE7DILifsrA==", "cpu": [ "x64" ], @@ -3964,9 +3954,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.1.tgz", + "integrity": "sha512-ARoKfflk0SiiYm3r1fmF73K/yB+PThmOwfWCk1sr7x/k9dc3uGLWuEE9if+Pw21el8MSpp3TMnG5vLNsJ/MMGQ==", "cpu": [ "arm64" ], @@ -3977,9 +3967,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.1.tgz", + "integrity": "sha512-oOST61G6VM45Mz2vdzWMr1s2slI7y9LqxEV5fCoWi2MDONmMvgsJVHSXxce/I2xOSZPTZ47nDPOl1tkwKWSHcw==", "cpu": [ "arm64" ], @@ -3990,9 +3980,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.1.tgz", + "integrity": "sha512-x5WgLi5dWpRz7WclKBGEF15LcWTh0ewrHM6Cq4A+WUbkysUMZNeqt05bwPonOQ3ihPS/WMhAZV5zB1DfnI4Sxg==", "cpu": [ "ia32" ], @@ -4003,9 +3993,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.1.tgz", + "integrity": "sha512-wS+zHAJRVP5zOL0e+a3V3E/NTEwM2HEvvNKoDy5Xcfs0o8lljxn+EAFPkUsxihBdmDq1JWzXmmB9cbssCPdxxw==", "cpu": [ "x64" ], @@ -4016,9 +4006,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.1.tgz", + "integrity": "sha512-rhHyrMeLpErT/C7BxcEsU4COHQUzHyrPYW5tOZUeUhziNtRuYxmDWvqQqzpuUt8xpOgmbKa1btGXfnA/ANVO+g==", "cpu": [ "x64" ], @@ -4068,9 +4058,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", + "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4185,9 +4175,9 @@ } }, "node_modules/@types/node": { - "version": "25.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", - "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -4234,14 +4224,14 @@ "license": "MIT" }, "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz", + "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", + "@typescript-eslint/tsconfig-utils": "^8.57.1", + "@typescript-eslint/types": "^8.57.1", "debug": "^4.4.3" }, "engines": { @@ -4256,14 +4246,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz", + "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4274,9 +4264,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz", + "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==", "dev": true, "license": "MIT", "engines": { @@ -4291,9 +4281,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz", + "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==", "dev": true, "license": "MIT", "engines": { @@ -4305,16 +4295,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz", + "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/project-service": "8.57.1", + "@typescript-eslint/tsconfig-utils": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -4385,16 +4375,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz", + "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4409,13 +4399,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz", + "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/types": "8.57.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -4716,9 +4706,9 @@ ] }, "node_modules/@vitejs/plugin-react": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", - "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", "license": "MIT", "dependencies": { "@babel/core": "^7.29.0", @@ -4732,7 +4722,7 @@ "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/accepts": { @@ -4852,6 +4842,19 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5051,16 +5054,16 @@ } }, "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.2.0", + "@jest/transform": "30.3.0", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", + "babel-preset-jest": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -5093,9 +5096,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", "dev": true, "license": "MIT", "dependencies": { @@ -5106,13 +5109,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", - "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { @@ -5133,12 +5136,12 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", - "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -5186,13 +5189,13 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", + "babel-plugin-jest-hoist": "30.3.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { @@ -5206,6 +5209,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -5229,9 +5233,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -5383,9 +5387,9 @@ } }, "node_modules/cacheable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.3.tgz", - "integrity": "sha512-iffYMX4zxKp54evOH27fm92hs+DeC1DhXmNVN8Tr94M/iZIV42dqTHSR2Ik4TOSPyOAwKr7Yu3rN9ALoLkbWyQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.4.tgz", + "integrity": "sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew==", "dev": true, "license": "MIT", "dependencies": { @@ -5393,7 +5397,7 @@ "@cacheable/utils": "^2.4.0", "hookified": "^1.15.0", "keyv": "^5.6.0", - "qified": "^0.6.0" + "qified": "^0.9.0" } }, "node_modules/cacheable/node_modules/keyv": { @@ -5475,9 +5479,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001776", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz", - "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==", + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", "funding": [ { "type": "opencollective", @@ -5605,6 +5609,18 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/cm6-theme-basic-light": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cm6-theme-basic-light/-/cm6-theme-basic-light-0.2.0.tgz", + "integrity": "sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5616,21 +5632,6 @@ "node": ">= 0.12.0" } }, - "node_modules/codemirror": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", - "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/commands": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/search": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" - } - }, "node_modules/collect-v8-coverage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", @@ -5754,21 +5755,24 @@ "license": "MIT" }, "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", "license": "MIT", "dependencies": { - "is-what": "^3.14.1" + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" }, "funding": { "url": "https://github.com/sponsors/mesqueeb" } }, "node_modules/core-js": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", - "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -5777,9 +5781,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", - "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "license": "MIT", "dependencies": { "browserslist": "^4.28.1" @@ -5853,6 +5857,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5874,14 +5879,14 @@ } }, "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" @@ -5917,9 +5922,9 @@ } }, "node_modules/cssstyle/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -6163,6 +6168,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { @@ -6181,9 +6187,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.307", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", - "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", "license": "ISC" }, "node_modules/emittery": { @@ -6203,6 +6209,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -6348,9 +6355,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", - "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz", + "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6369,6 +6376,7 @@ "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" }, "engines": { @@ -6434,9 +6442,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -6446,32 +6454,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" } }, "node_modules/escalade": { @@ -6849,18 +6857,18 @@ } }, "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.2.0", + "@jest/expect-utils": "30.3.0", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7051,6 +7059,23 @@ "bser": "2.1.1" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -7156,9 +7181,9 @@ } }, "node_modules/flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -7182,6 +7207,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -7511,6 +7537,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -7544,6 +7571,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -7553,6 +7581,7 @@ "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.2" @@ -7707,14 +7736,14 @@ "license": "MIT" }, "node_modules/google-auth-library": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.1.tgz", - "integrity": "sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA==", + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "7.1.3", + "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" @@ -7723,21 +7752,6 @@ "node": ">=18" } }, - "node_modules/google-auth-library/node_modules/gaxios": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", - "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2", - "rimraf": "^5.0.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/google-logging-utils": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", @@ -7867,13 +7881,13 @@ "license": "MIT" }, "node_modules/hashery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.0.tgz", - "integrity": "sha512-nhQ6ExaOIqti2FDWoEMWARUqIKyjr2VcZzXShrI+A3zpeiuPWzx6iPftt44LhP74E5sW36B75N6VHbvRtpvO6Q==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", "dev": true, "license": "MIT", "dependencies": { - "hookified": "^1.14.0" + "hookified": "^1.15.0" }, "engines": { "node": ">=20" @@ -8616,10 +8630,16 @@ } }, "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "license": "MIT" + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } }, "node_modules/isarray": { "version": "2.0.5", @@ -8632,6 +8652,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -8740,6 +8761,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -8752,17 +8774,17 @@ } }, "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", "import-local": "^3.2.0", - "jest-cli": "30.2.0" + "jest-cli": "30.3.0" }, "bin": { "jest": "bin/jest.js" @@ -8780,14 +8802,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.2.0", + "jest-util": "30.3.0", "p-limit": "^3.1.0" }, "engines": { @@ -8795,29 +8817,29 @@ } }, "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "p-limit": "^3.1.0", - "pretty-format": "30.2.0", + "pretty-format": "30.3.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -8827,21 +8849,21 @@ } }, "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "yargs": "^17.7.2" }, "bin": { @@ -8860,34 +8882,33 @@ } }, "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", + "jest-circus": "30.3.0", "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", + "jest-environment-node": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "parse-json": "^5.2.0", - "pretty-format": "30.2.0", + "pretty-format": "30.3.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -8912,16 +8933,16 @@ } }, "node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.0.1", + "@jest/diff-sequences": "30.3.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.2.0" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8941,36 +8962,36 @@ } }, "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" + "jest-util": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8984,21 +9005,21 @@ "license": "MIT" }, "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", "walker": "^1.0.8" }, "engines": { @@ -9009,49 +9030,49 @@ } }, "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -9060,15 +9081,15 @@ } }, "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-util": "30.2.0" + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -9103,18 +9124,18 @@ } }, "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.3.0", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -9123,46 +9144,46 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", "dev": true, "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" + "jest-snapshot": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -9171,32 +9192,32 @@ } }, "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -9205,9 +9226,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9216,20 +9237,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", + "@jest/expect-utils": "30.3.0", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.2.0", + "expect": "30.3.0", "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -9251,49 +9272,36 @@ } }, "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "picomatch": "^4.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.2.0" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -9313,19 +9321,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.2.0", + "jest-util": "30.3.0", "string-length": "^4.0.2" }, "engines": { @@ -9333,15 +9341,15 @@ } }, "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", + "jest-util": "30.3.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -9584,21 +9592,19 @@ } }, "node_modules/less": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/less/-/less-4.5.1.tgz", - "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", - "hasInstallScript": true, + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/less/-/less-4.6.4.tgz", + "integrity": "sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==", "license": "Apache-2.0", "dependencies": { - "copy-anything": "^2.0.1", - "parse-node-version": "^1.0.1", - "tslib": "^2.3.0" + "copy-anything": "^3.0.5", + "parse-node-version": "^1.0.1" }, "bin": { "lessc": "bin/lessc" }, "engines": { - "node": ">=14" + "node": ">=18" }, "optionalDependencies": { "errno": "^0.1.1", @@ -9900,9 +9906,9 @@ } }, "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, "license": "CC0-1.0" }, @@ -9986,6 +9992,19 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -10051,6 +10070,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -10113,13 +10133,13 @@ } }, "node_modules/mongoose": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.2.4.tgz", - "integrity": "sha512-XNh+jiztVMddDFDCv8TWxVxi/rGx+0FfsK3Ftj6hcYzEmhTcos2uC144OJRmUFPHSu3hJr6Pgip++Ab2+Da35Q==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.3.1.tgz", + "integrity": "sha512-58DuQti+LlRS74/UfWN4F3wZsC0Yr1dgTWZ2Wd3/TuSvm6rIdyAjDWbx2xGyuBooqJYdAWotVv4mQgVdivh+3Q==", "license": "MIT", "dependencies": { "kareem": "3.2.0", - "mongodb": "~7.0", + "mongodb": "~7.1", "mpath": "0.9.0", "mquery": "6.0.0", "ms": "2.1.3", @@ -10134,13 +10154,13 @@ } }, "node_modules/mongoose/node_modules/mongodb": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", - "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.0.tgz", + "integrity": "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==", "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.3.0", - "bson": "^7.0.0", + "bson": "^7.1.1", "mongodb-connection-string-url": "^7.0.0" }, "engines": { @@ -10335,9 +10355,9 @@ } }, "node_modules/needle": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", - "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", "license": "MIT", "optional": true, "dependencies": { @@ -10374,9 +10394,9 @@ } }, "node_modules/node": { - "version": "25.8.0", - "resolved": "https://registry.npmjs.org/node/-/node-25.8.0.tgz", - "integrity": "sha512-prXxZJ5522Khi5k5duwoxNPugh23L3V8Egf/dYt3/+/1FXuy1TqIHd8zHXseysEvVFf4Us5CFxylv2zu/wZz0g==", + "version": "25.8.1", + "resolved": "https://registry.npmjs.org/node/-/node-25.8.1.tgz", + "integrity": "sha512-AG1iDDN7Uzku6rFQTjfrqZZRjur8YPkO73PnfDfoj5348DQmFACA8D4X8EhvO8ObIntENK5e/aVviy+sFsAbOA==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -10726,6 +10746,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -10815,6 +10836,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10830,6 +10852,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -10846,6 +10869,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { @@ -10865,13 +10889,12 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -11005,9 +11028,9 @@ } }, "node_modules/postcss-sorting": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-9.1.0.tgz", - "integrity": "sha512-Mn8KJ45HNNG6JBpBizXcyf6LqY/qyqetGcou/nprDnFwBFBLGj0j/sNKV2lj2KMOVOwdXu14aEzqJv8CIV6e8g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-10.0.0.tgz", + "integrity": "sha512-TXbU+h6vVRW+86c/+ewhWq9k7pr7ijASTnepVhCQiC87zAOTkvB1v2dHyWP+ggstSTX/PNvjzS+IOqzejndz9w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -11050,9 +11073,9 @@ } }, "node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11142,18 +11165,25 @@ "license": "MIT" }, "node_modules/qified": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/qified/-/qified-0.6.0.tgz", - "integrity": "sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.9.0.tgz", + "integrity": "sha512-4q61YgkHbY6gmwkqm0BsxyLDO3UYdrdiJTJ7JiaZb3xpW1duxn135SB7KqUEkCiuu5O4W+TtwEWP2VjmSRanvA==", "dev": true, "license": "MIT", "dependencies": { - "hookified": "^1.14.0" + "hookified": "^2.1.0" }, "engines": { "node": ">=20" } }, + "node_modules/qified/node_modules/hookified": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.1.0.tgz", + "integrity": "sha512-ootKng4eaxNxa7rx6FJv2YKef3DuhqbEj3l70oGXwddPQEEnISm50TEZQclqiLTAtilT2nu7TErtCO523hHkyg==", + "dev": true, + "license": "MIT" + }, "node_modules/qs": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", @@ -11469,25 +11499,10 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.1.tgz", + "integrity": "sha512-iZKH8BeoCwTCBTZBZWQQMreekd4mdomwdjIQ40GC1oZm6o+8PnNMIxFOiCsGMWeS8iDJ7KZcl7KwmKk/0HOQpA==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -11500,31 +11515,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", + "@rollup/rollup-android-arm-eabi": "4.59.1", + "@rollup/rollup-android-arm64": "4.59.1", + "@rollup/rollup-darwin-arm64": "4.59.1", + "@rollup/rollup-darwin-x64": "4.59.1", + "@rollup/rollup-freebsd-arm64": "4.59.1", + "@rollup/rollup-freebsd-x64": "4.59.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.1", + "@rollup/rollup-linux-arm-musleabihf": "4.59.1", + "@rollup/rollup-linux-arm64-gnu": "4.59.1", + "@rollup/rollup-linux-arm64-musl": "4.59.1", + "@rollup/rollup-linux-loong64-gnu": "4.59.1", + "@rollup/rollup-linux-loong64-musl": "4.59.1", + "@rollup/rollup-linux-ppc64-gnu": "4.59.1", + "@rollup/rollup-linux-ppc64-musl": "4.59.1", + "@rollup/rollup-linux-riscv64-gnu": "4.59.1", + "@rollup/rollup-linux-riscv64-musl": "4.59.1", + "@rollup/rollup-linux-s390x-gnu": "4.59.1", + "@rollup/rollup-linux-x64-gnu": "4.59.1", + "@rollup/rollup-linux-x64-musl": "4.59.1", + "@rollup/rollup-openbsd-x64": "4.59.1", + "@rollup/rollup-openharmony-arm64": "4.59.1", + "@rollup/rollup-win32-arm64-msvc": "4.59.1", + "@rollup/rollup-win32-ia32-msvc": "4.59.1", + "@rollup/rollup-win32-x64-gnu": "4.59.1", + "@rollup/rollup-win32-x64-msvc": "4.59.1", "fsevents": "~2.3.2" } }, @@ -11665,9 +11680,9 @@ } }, "node_modules/sax": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", - "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "license": "BlueOak-1.0.0", "optional": true, "engines": { @@ -11818,6 +11833,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11830,6 +11846,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11917,6 +11934,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -12063,6 +12081,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -12081,6 +12100,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -12095,12 +12115,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -12113,6 +12135,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -12239,6 +12262,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12294,9 +12318,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.4.0.tgz", - "integrity": "sha512-3kQ2/cHv3Zt8OBg+h2B8XCx9evEABQIrv4hh3uXahGz/ZEHrTR80zxBiK2NfXNaSoyBzxO1pjsz1Vhdzwn5XSw==", + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.5.0.tgz", + "integrity": "sha512-o/NS6zhsPZFmgUm5tXX4pVNg1XDOZSlucLdf2qow/lVn4JIyzZIQ5b3kad1ugqUj3GSIgr2u5lQw7X8rjqw33g==", "dev": true, "funding": [ { @@ -12313,21 +12337,21 @@ "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-syntax-patches-for-csstree": "^1.0.27", + "@csstools/css-syntax-patches-for-csstree": "^1.0.29", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", "@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-specificity": "^6.0.0", "colord": "^2.9.3", - "cosmiconfig": "^9.0.0", + "cosmiconfig": "^9.0.1", "css-functions-list": "^3.3.3", - "css-tree": "^3.1.0", + "css-tree": "^3.2.1", "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.2", "global-modules": "^2.0.0", - "globby": "^16.1.0", + "globby": "^16.1.1", "globjoin": "^0.1.4", "html-tags": "^5.1.0", "ignore": "^7.0.5", @@ -12335,15 +12359,15 @@ "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "mathml-tag-names": "^4.0.0", - "meow": "^14.0.0", + "meow": "^14.1.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", - "postcss": "^8.5.6", + "postcss": "^8.5.8", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0", - "string-width": "^8.1.1", + "string-width": "^8.2.0", "supports-hyperlinks": "^4.4.0", "svg-tags": "^1.0.0", "table": "^6.9.0", @@ -12357,14 +12381,14 @@ } }, "node_modules/stylelint-config-recess-order": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-7.6.1.tgz", - "integrity": "sha512-ac0H/Iy2chh1YBADrua87G+nJCmG/SdG7gjnoLvtfpN0D+RuNfuADawfbCKvm0LMp5hvuRFNkJsu6xNoLM5ToA==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-7.7.0.tgz", + "integrity": "sha512-TWRkg+BrwHOki4pi9y1emWgx6pFwZXOYZhNsbEObke/mzYUJVJvDEzJJQXhH7ajslQJGcrExy8ZvJqDiUbhFpA==", "dev": true, "license": "ISC", "peerDependencies": { "stylelint": "^16.18.0 || ^17.0.0", - "stylelint-order": "^7.0.0" + "stylelint-order": "^7.0.0 || ^8.0.0" } }, "node_modules/stylelint-config-recommended": { @@ -12391,15 +12415,15 @@ } }, "node_modules/stylelint-order": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-7.0.1.tgz", - "integrity": "sha512-GWPei1zBVDDjxM+/BmcSCiOcHNd8rSqW6FUZtqQGlTRpD0Z5nSzspzWD8rtKif5KPdzUG68DApKEV/y/I9VbTw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-8.1.1.tgz", + "integrity": "sha512-LqsEB6VggJuu5v10RtkrQsBObcdwBE7GuAOlwfc/LR3VL/w8UqKX2BOLIjhyGt0Gne/njo7gRNGiJAKhfmPMNw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "postcss": "^8.5.6", - "postcss-sorting": "^9.1.0" + "postcss": "^8.5.8", + "postcss-sorting": "^10.0.0" }, "engines": { "node": ">=20.19.0" @@ -12432,14 +12456,14 @@ } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.20", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.20.tgz", - "integrity": "sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==", + "version": "6.1.21", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.21.tgz", + "integrity": "sha512-2u7cJfSf7Th7NxEk/VzQjnPoglok2YCsevS7TSbJjcDQWJPbqUUnSYtriHSvtnq+fRZHy1s0ugk4ApnQyhPGoQ==", "dev": true, "license": "MIT", "dependencies": { - "cacheable": "^2.3.2", - "flatted": "^3.3.3", + "cacheable": "^2.3.3", + "flatted": "^3.4.1", "hookified": "^1.15.0" } }, @@ -12776,53 +12800,23 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tldts": { - "version": "7.0.24", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.24.tgz", - "integrity": "sha512-1r6vQTTt1rUiJkI5vX7KG8PR342Ru/5Oh13kEQP2SMbRSZpOey9SrBe27IDxkoWulx8ShWu4K6C0BkctP8Z1bQ==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.24" + "tldts-core": "^7.0.27" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.24", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.24.tgz", - "integrity": "sha512-pj7yygNMoMRqG7ML2SDQ0xNIOfN3IBDUcPVM2Sg6hP96oFNN2nqnzHreT3z9xLq85IWJyNTvD38O002DdOrPMw==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", "dev": true, "license": "MIT" }, @@ -12856,9 +12850,9 @@ } }, "node_modules/tough-cookie": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", - "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -12891,9 +12885,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -12907,6 +12901,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "license": "0BSD" }, "node_modules/type-check": { @@ -13072,9 +13067,9 @@ } }, "node_modules/undici": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", - "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz", + "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==", "dev": true, "license": "MIT", "engines": { @@ -13352,36 +13347,6 @@ } } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -13459,6 +13424,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -13573,6 +13539,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -13591,6 +13558,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -13608,12 +13576,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -13628,6 +13598,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -13640,6 +13611,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -13652,6 +13624,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" diff --git a/package.json b/package.json index 96a492b18..432f59c16 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,6 @@ "@codemirror/lang-markdown": "^6.5.0", "@codemirror/language": "^6.12.2", "@codemirror/state": "^6.6.0", - "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", "@googleapis/drive": "^20.1.0", @@ -105,7 +104,7 @@ "@vitejs/plugin-react": "^5.1.2", "body-parser": "^2.2.0", "classnames": "^2.5.1", - "codemirror": "^6.0.2", + "cm6-theme-basic-light": "^0.2.0", "cookie-parser": "^1.4.7", "core-js": "^3.47.0", "cors": "^2.8.5", From 436d21f33ce2c9e6cb2889fbaede39ba32be23f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 22 Mar 2026 01:37:30 +0100 Subject: [PATCH 05/56] stable - remove bracket-matching --- client/components/codeEditor/codeEditor.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index d44f65e92..06d224c6a 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -103,7 +103,6 @@ const CodeEditor = forwardRef( foldGutter(), lineNumbers(), basicLightTheme, - bracketMatching(), ], }); From ec159e4f14715a03333bcb6c88a165350d5c3c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Mon, 23 Mar 2026 11:20:15 +0100 Subject: [PATCH 06/56] different editors working --- client/components/codeEditor/codeEditor.jsx | 112 +++-- package-lock.json | 461 ++++++++++++++++++++ package.json | 3 + 3 files changed, 544 insertions(+), 32 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 06d224c6a..c0be756b8 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -12,9 +12,29 @@ import { highlightActiveLine, scrollPastEnd, } from "@codemirror/view"; -import { markdown } from "@codemirror/lang-markdown"; +import { markdown, markdownLanguage } from "@codemirror/lang-markdown"; +import { languages } from "@codemirror/language-data"; import { css } from "@codemirror/lang-css"; -import { basicLightTheme } from "cm6-theme-basic-light"; +import { basicLightHighlightStyle } from "cm6-theme-basic-light"; +import { HighlightStyle } from "@codemirror/language"; + +import { syntaxHighlighting } from "@codemirror/language"; +import { tags } from "@lezer/highlight"; + +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", + }, + // … +]); const CodeEditor = forwardRef( ( @@ -32,13 +52,13 @@ const CodeEditor = forwardRef( ) => { const editorRef = useRef(null); const viewRef = useRef(null); + const docsRef = useRef({}); + const prevTabRef = useRef(tab); console.log(props); // --- init editor --- - useEffect(() => { - if (!editorRef.current) return; - + const createExtensions = ({ onChange, language, editorTheme }) => { const updateListener = EditorView.updateListener.of((update) => { if (update.docChanged) { onChange(update.state.doc.toString()); @@ -76,34 +96,36 @@ const CodeEditor = forwardRef( { key: "Mod-i", run: italicCommand }, ]); - const languageExtension = () => { - switch (language) { - case "gfm": - return markdown({ codeLanguages: [] }); // GitHub-flavored Markdown - case "css": - return css(); - default: - return markdown(); - } - }; + const languageExtension = + language === "css" ? css() : markdown({ base: markdownLanguage, codeLanguages: languages }); + const themeExtension = syntaxHighlighting(basicLightHighlightStyle); + + return [ + history(), + keymap.of(defaultKeymap), + customKeymap, + updateListener, + EditorView.lineWrapping, + scrollPastEnd(), + languageExtension, + highlightActiveLine(), + highlightActiveLineGutter(), + keymap.of(foldKeymap), + foldGutter(), + lineNumbers(), + themeExtension, + syntaxHighlighting(highlightStyle), + ]; + }; + + useEffect(() => { + if (!editorRef.current) return; + + // create initial editor state const state = EditorState.create({ doc: value, - extensions: [ - history(), - keymap.of(defaultKeymap), - customKeymap, - updateListener, - EditorView.lineWrapping, - scrollPastEnd(), - languageExtension(), - highlightActiveLine(), - highlightActiveLineGutter(), - keymap.of(foldKeymap), - foldGutter(), - lineNumbers(), - basicLightTheme, - ], + extensions: createExtensions({ onChange, language, editorTheme }), }); viewRef.current = new EditorView({ @@ -111,10 +133,37 @@ const CodeEditor = forwardRef( parent: editorRef.current, }); + // save initial state for current tab + docsRef.current[tab] = state; + return () => viewRef.current?.destroy(); }, []); - // --- sync external value --- + useEffect(() => { + const view = viewRef.current; + if (!view) return; + + 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) { + nextState = EditorState.create({ + doc: value, + extensions: createExtensions({ onChange, language, editorTheme }), + }); + } + + view.setState(nextState); + prevTabRef.current = tab; + } + }, [tab]); + useEffect(() => { const view = viewRef.current; if (!view) return; @@ -126,7 +175,6 @@ const CodeEditor = forwardRef( }); } }, [value]); - // --- exposed API --- useImperativeHandle(ref, () => ({ getValue: () => viewRef.current.state.doc.toString(), diff --git a/package-lock.json b/package-lock.json index 0daeb5ab7..b0747fce0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,14 +16,17 @@ "@babel/preset-react": "^7.28.5", "@babel/runtime": "^7.28.6", "@codemirror/commands": "^6.10.3", + "@codemirror/highlight": "^0.19.8", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lang-markdown": "^6.5.0", "@codemirror/language": "^6.12.2", + "@codemirror/language-data": "^6.5.2", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", "@googleapis/drive": "^20.1.0", + "@lezer/highlight": "^1.2.3", "@sanity/diff-match-patch": "^3.2.0", "@vitejs/plugin-react": "^5.1.2", "body-parser": "^2.2.0", @@ -2068,6 +2071,95 @@ "@lezer/common": "^1.1.0" } }, + "node_modules/@codemirror/highlight": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@codemirror/highlight/-/highlight-0.19.8.tgz", + "integrity": "sha512-v/lzuHjrYR8MN2mEJcUD6fHSTXXli9C1XGYpr+ElV6fLBIUhMTNKR3qThp611xuWfXfwDxeL7ppcbkM/MzPV3A==", + "deprecated": "As of 0.20.0, this package has been split between @lezer/highlight and @codemirror/language", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^0.19.0", + "@codemirror/rangeset": "^0.19.0", + "@codemirror/state": "^0.19.3", + "@codemirror/view": "^0.19.39", + "@lezer/common": "^0.15.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/highlight/node_modules/@codemirror/language": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.19.10.tgz", + "integrity": "sha512-yA0DZ3RYn2CqAAGW62VrU8c4YxscMQn45y/I9sjBlqB1e2OTQLg4CCkMBuMSLXk4xaqjlsgazeOQWaJQOKfV8Q==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^0.19.0", + "@codemirror/text": "^0.19.0", + "@codemirror/view": "^0.19.0", + "@lezer/common": "^0.15.5", + "@lezer/lr": "^0.15.0" + } + }, + "node_modules/@codemirror/highlight/node_modules/@codemirror/state": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.19.9.tgz", + "integrity": "sha512-psOzDolKTZkx4CgUqhBQ8T8gBc0xN5z4gzed109aF6x7D7umpDRoimacI/O6d9UGuyl4eYuDCZmDFr2Rq7aGOw==", + "license": "MIT", + "dependencies": { + "@codemirror/text": "^0.19.0" + } + }, + "node_modules/@codemirror/highlight/node_modules/@codemirror/view": { + "version": "0.19.48", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.19.48.tgz", + "integrity": "sha512-0eg7D2Nz4S8/caetCTz61rK0tkHI17V/d15Jy0kLOT8dTLGGNJUponDnW28h2B6bERmPlVHKh8MJIr5OCp1nGw==", + "license": "MIT", + "dependencies": { + "@codemirror/rangeset": "^0.19.5", + "@codemirror/state": "^0.19.3", + "@codemirror/text": "^0.19.0", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@codemirror/highlight/node_modules/@lezer/common": { + "version": "0.15.12", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz", + "integrity": "sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig==", + "license": "MIT" + }, + "node_modules/@codemirror/highlight/node_modules/@lezer/lr": { + "version": "0.15.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.15.8.tgz", + "integrity": "sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^0.15.0" + } + }, + "node_modules/@codemirror/lang-angular": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.4.tgz", + "integrity": "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.3" + } + }, + "node_modules/@codemirror/lang-cpp": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", + "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/cpp": "^1.0.0" + } + }, "node_modules/@codemirror/lang-css": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", @@ -2081,6 +2173,19 @@ "@lezer/css": "^1.1.7" } }, + "node_modules/@codemirror/lang-go": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", + "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/go": "^1.0.0" + } + }, "node_modules/@codemirror/lang-html": { "version": "6.4.11", "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", @@ -2098,6 +2203,16 @@ "@lezer/html": "^1.3.12" } }, + "node_modules/@codemirror/lang-java": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/java": "^1.0.0" + } + }, "node_modules/@codemirror/lang-javascript": { "version": "6.2.5", "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", @@ -2113,6 +2228,58 @@ "@lezer/javascript": "^1.0.0" } }, + "node_modules/@codemirror/lang-jinja": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.0.tgz", + "integrity": "sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-less": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz", + "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-liquid": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.3.2.tgz", + "integrity": "sha512-6PDVU3ZnfeYyz1at1E/ttorErZvZFXXt1OPhtfe1EZJ2V2iDFa0CwPqPgG5F7NXN0yONGoBogKmFAafKTqlwIw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, "node_modules/@codemirror/lang-markdown": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", @@ -2128,6 +2295,124 @@ "@lezer/markdown": "^1.0.0" } }, + "node_modules/@codemirror/lang-php": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/lang-rust": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/rust": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sass": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz", + "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/sass": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sql": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz", + "integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-vue": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz", + "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-wast": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz", + "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", + "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.12.2", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz", @@ -2143,6 +2428,46 @@ "style-mod": "^4.0.0" } }, + "node_modules/@codemirror/language-data": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.2.tgz", + "integrity": "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-angular": "^0.1.0", + "@codemirror/lang-cpp": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-go": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-java": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-jinja": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-less": "^6.0.0", + "@codemirror/lang-liquid": "^6.0.0", + "@codemirror/lang-markdown": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-python": "^6.0.0", + "@codemirror/lang-rust": "^6.0.0", + "@codemirror/lang-sass": "^6.0.0", + "@codemirror/lang-sql": "^6.0.0", + "@codemirror/lang-vue": "^0.1.1", + "@codemirror/lang-wast": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/legacy-modes": "^6.4.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz", + "integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, "node_modules/@codemirror/lint": { "version": "6.9.5", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz", @@ -2154,6 +2479,25 @@ "crelt": "^1.0.5" } }, + "node_modules/@codemirror/rangeset": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@codemirror/rangeset/-/rangeset-0.19.9.tgz", + "integrity": "sha512-V8YUuOvK+ew87Xem+71nKcqu1SXd5QROMRLMS/ljT5/3MCxtgrRie1Cvild0G/Z2f1fpWxzX78V0U4jjXBorBQ==", + "deprecated": "As of 0.20.0, this package has been merged into @codemirror/state", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^0.19.0" + } + }, + "node_modules/@codemirror/rangeset/node_modules/@codemirror/state": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.19.9.tgz", + "integrity": "sha512-psOzDolKTZkx4CgUqhBQ8T8gBc0xN5z4gzed109aF6x7D7umpDRoimacI/O6d9UGuyl4eYuDCZmDFr2Rq7aGOw==", + "license": "MIT", + "dependencies": { + "@codemirror/text": "^0.19.0" + } + }, "node_modules/@codemirror/state": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", @@ -2164,6 +2508,13 @@ "@marijn/find-cluster-break": "^1.0.0" } }, + "node_modules/@codemirror/text": { + "version": "0.19.6", + "resolved": "https://registry.npmjs.org/@codemirror/text/-/text-0.19.6.tgz", + "integrity": "sha512-T9jnREMIygx+TPC1bOuepz18maGq/92q2a+n4qTqObKwvNMg+8cMTslb8yxeEDEq7S3kpgGWxgO1UWbQRij0dA==", + "deprecated": "As of 0.20.0, this package has been merged into @codemirror/state", + "license": "MIT" + }, "node_modules/@codemirror/view": { "version": "6.40.0", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.40.0.tgz", @@ -3513,6 +3864,17 @@ "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", "license": "MIT" }, + "node_modules/@lezer/cpp": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.5.tgz", + "integrity": "sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lezer/css": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.2.tgz", @@ -3524,6 +3886,17 @@ "@lezer/lr": "^1.3.0" } }, + "node_modules/@lezer/go": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/highlight": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", @@ -3545,6 +3918,17 @@ "@lezer/lr": "^1.0.0" } }, + "node_modules/@lezer/java": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz", + "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lezer/javascript": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", @@ -3556,6 +3940,17 @@ "@lezer/lr": "^1.3.0" } }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lezer/lr": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", @@ -3575,6 +3970,72 @@ "@lezer/highlight": "^1.0.0" } }, + "node_modules/@lezer/php": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz", + "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "node_modules/@lezer/python": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz", + "integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/rust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz", + "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/sass": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.1.0.tgz", + "integrity": "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", diff --git a/package.json b/package.json index 432f59c16..1d1668437 100644 --- a/package.json +++ b/package.json @@ -92,14 +92,17 @@ "@babel/preset-react": "^7.28.5", "@babel/runtime": "^7.28.6", "@codemirror/commands": "^6.10.3", + "@codemirror/highlight": "^0.19.8", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lang-markdown": "^6.5.0", "@codemirror/language": "^6.12.2", + "@codemirror/language-data": "^6.5.2", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", "@googleapis/drive": "^20.1.0", + "@lezer/highlight": "^1.2.3", "@sanity/diff-match-patch": "^3.2.0", "@vitejs/plugin-react": "^5.1.2", "body-parser": "^2.2.0", From 4483537b9326d6b10fbae2e4deecd43c13fff075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Mon, 23 Mar 2026 12:13:20 +0100 Subject: [PATCH 07/56] not really working but it will --- client/components/codeEditor/codeEditor.jsx | 49 ++ .../codeEditor/customMarkdownGrammar.js | 95 +++ client/homebrew/editor/editor.jsx | 613 ++++++++++-------- client/homebrew/editor/editor.less | 24 +- 4 files changed, 503 insertions(+), 278 deletions(-) create mode 100644 client/components/codeEditor/customMarkdownGrammar.js diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index c0be756b8..d6d564a60 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -36,6 +36,53 @@ const highlightStyle = HighlightStyle.define([ // … ]); +/*custom tokens */ +import { Decoration, ViewPlugin, WidgetType } from "@codemirror/view"; +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 widgets = []; + const tokens = tokenizeCustomMarkdown(view.state.doc.toString()); + + // sort by line number + tokens.sort((a, b) => a.line - b.line); + + tokens.forEach((tok) => { + const line = view.state.doc.line(tok.line + 1); // CM lines are 1-based + widgets.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); + }); + + return Decoration.set(widgets); + } + }, + { + decorations: (v) => v.decorations, + }, +); + const CodeEditor = forwardRef( ( { @@ -116,6 +163,8 @@ const CodeEditor = forwardRef( lineNumbers(), themeExtension, syntaxHighlighting(highlightStyle), + customHighlightPlugin, + syntaxHighlighting(customHighlightStyle), ]; }; diff --git a/client/components/codeEditor/customMarkdownGrammar.js b/client/components/codeEditor/customMarkdownGrammar.js new file mode 100644 index 000000000..625983ad1 --- /dev/null +++ b/client/components/codeEditor/customMarkdownGrammar.js @@ -0,0 +1,95 @@ +// 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 + inlineBlock: "inline-block", // .cm-inline-block + block: "block", // .cm-block + emoji: "emoji", // .cm-emoji + superscript: "superscript", // .cm-superscript + subscript: "subscript", // .cm-subscript + definitionTerm: "dt-highlight", // .cm-dt-highlight + definitionDesc: "dd-highlight", // .cm-dd-highlight + injection: "injection", // .cm-injection +}; + +// --- 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/.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)) tokens.push({ line: lineNumber, type: customTags.subscript }); + if (/\^/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.superscript }); + + // --- Definition lists --- + if (/::/.test(lineText)) { + tokens.push({ line: lineNumber, type: customTags.definitionDesc }); + tokens.push({ line: lineNumber, type: customTags.definitionTerm }); + } + + // Track ranges already marked for injections + const injectionRanges = []; + + if (line.includes("{") && line.includes("}")) { + const regex = /{[^{}]*}/gm; + let match; + while ((match = regex.exec(line)) != null) { + codeMirror?.markText( + { line: lineNumber, ch: match.index }, + { line: lineNumber, ch: match.index + match[0].length }, + { className: "injection" }, + ); + injectionRanges.push([match.index, match.index + match[0].length]); + } + } + + // Now mark inline blocks, but skip overlapping injection ranges + if (line.includes("{{") && line.includes("}}")) { + const regex = /{{[^{}]*}}/gm; + let match; + while ((match = regex.exec(line)) != null) { + const start = match.index, + end = match.index + match[0].length; + const overlaps = injectionRanges.some(([iStart, iEnd]) => start < iEnd && end > iStart); + if (!overlaps) { + codeMirror?.markText( + { line: lineNumber, ch: start }, + { line: lineNumber, ch: end }, + { className: "inline-block" }, + ); + } + } + } + + // --- Multi-line blocks `{{…}}` --- only start/end lines + if (lineText.trimLeft().startsWith("{{") && !lineText.trimLeft().endsWith("}}")) { + inBlock = true; + blockStart = lineNumber; + tokens.push({ line: lineNumber, type: customTags.block }); + } + if (lineText.trimLeft().startsWith("}}") && inBlock) { + tokens.push({ line: lineNumber, type: customTags.block }); + inBlock = false; + } + }); + + return tokens; +} diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 06fd469a0..a2ecbcb37 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -1,16 +1,16 @@ /*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ -import './editor.less'; -import React from 'react'; -import createReactClass from 'create-react-class'; -import _ from 'lodash'; -import dedent from 'dedent'; -import Markdown from '@shared/markdown.js'; +import "./editor.less"; +import React from "react"; +import createReactClass from "create-react-class"; +import _ from "lodash"; +import dedent from "dedent"; +import Markdown from "@shared/markdown.js"; -import CodeEditor from '../../components/codeEditor/codeEditor.jsx'; -import SnippetBar from './snippetbar/snippetbar.jsx'; -import MetadataEditor from './metadataEditor/metadataEditor.jsx'; +import CodeEditor from "../../components/codeEditor/codeEditor.jsx"; +import SnippetBar from "./snippetbar/snippetbar.jsx"; +import MetadataEditor from "./metadataEditor/metadataEditor.jsx"; -const EDITOR_THEME_KEY = 'HB_editor_theme'; +const EDITOR_THEME_KEY = "HB_editor_theme"; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/; @@ -32,295 +32,348 @@ const DEFAULT_SNIPPET_TEXT = dedent` let isJumping = false; const Editor = createReactClass({ - displayName : 'Editor', - getDefaultProps : function() { + displayName: "Editor", + getDefaultProps: function () { return { - brew : { - text : '', - style : '' + brew: { + text: "", + style: "", }, - onBrewChange : ()=>{}, - reportError : ()=>{}, + onBrewChange: () => {}, + reportError: () => {}, - onCursorPageChange : ()=>{}, - onViewPageChange : ()=>{}, + onCursorPageChange: () => {}, + onViewPageChange: () => {}, - editorTheme : 'default', - renderer : 'legacy', + editorTheme: "default", + renderer: "legacy", - currentEditorCursorPageNum : 1, - currentEditorViewPageNum : 1, - currentBrewRendererPageNum : 1, + currentEditorCursorPageNum: 1, + currentEditorViewPageNum: 1, + currentBrewRendererPageNum: 1, }; }, - getInitialState : function() { + getInitialState: function () { return { - editorTheme : this.props.editorTheme, - view : 'text', //'text', 'style', 'meta', 'snippet' - snippetBarHeight : 26, + editorTheme: this.props.editorTheme, + view: "text", //'text', 'style', 'meta', 'snippet' + snippetBarHeight: 26, }; }, - editor : React.createRef(null), - codeEditor : React.createRef(null), + editor: React.createRef(null), + codeEditor: React.createRef(null), - isText : function() {return this.state.view == 'text';}, - isStyle : function() {return this.state.view == 'style';}, - isMeta : function() {return this.state.view == 'meta';}, - isSnip : function() {return this.state.view == 'snippet';}, - - componentDidMount : function() { + isText: function () { + return this.state.view == "text"; + }, + isStyle: function () { + return this.state.view == "style"; + }, + isMeta: function () { + return this.state.view == "meta"; + }, + isSnip: function () { + return this.state.view == "snippet"; + }, + componentDidMount: function () { this.highlightCustomMarkdown(); - document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys); - document.addEventListener('keydown', this.handleControlKeys); + document.getElementById("BrewRenderer").addEventListener("keydown", this.handleControlKeys); + document.addEventListener("keydown", this.handleControlKeys); - this.codeEditor.current.codeMirror?.on('cursorActivity', (cm)=>{this.updateCurrentCursorPage(cm.getCursor());}); - this.codeEditor.current.codeMirror?.on('scroll', _.throttle(()=>{this.updateCurrentViewPage(this.codeEditor.current.getTopVisibleLine());}, 200)); + this.codeEditor.current.codeMirror?.on("cursorActivity", (cm) => { + this.updateCurrentCursorPage(cm.getCursor()); + }); + this.codeEditor.current.codeMirror?.on( + "scroll", + _.throttle(() => { + this.updateCurrentViewPage(this.codeEditor.current.getTopVisibleLine()); + }, 200), + ); const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY); - if(editorTheme) { + if (editorTheme) { this.setState({ - editorTheme : editorTheme + editorTheme: editorTheme, }); } - const snippetBar = document.querySelector('.editor > .snippetBar'); - if(!snippetBar) return; + const snippetBar = document.querySelector(".editor > .snippetBar"); + if (!snippetBar) return; - this.resizeObserver = new ResizeObserver((entries)=>{ - const height = document.querySelector('.editor > .snippetBar').offsetHeight; + this.resizeObserver = new ResizeObserver((entries) => { + const height = document.querySelector(".editor > .snippetBar").offsetHeight; this.setState({ snippetBarHeight: height }); }); this.resizeObserver.observe(snippetBar); }, - componentDidUpdate : function(prevProps, prevState, snapshot) { - + componentDidUpdate: function (prevProps, prevState, snapshot) { this.highlightCustomMarkdown(); - if(prevProps.moveBrew !== this.props.moveBrew) - this.brewJump(); + if (prevProps.moveBrew !== this.props.moveBrew) this.brewJump(); - if(prevProps.moveSource !== this.props.moveSource) - this.sourceJump(); + if (prevProps.moveSource !== this.props.moveSource) this.sourceJump(); - if(this.props.liveScroll) { - if(prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) { + if (this.props.liveScroll) { + if (prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) { this.sourceJump(this.props.currentBrewRendererPageNum, false); - } else if(prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) { + } else if (prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) { this.brewJump(this.props.currentEditorViewPageNum, false); - } else if(prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) { + } else if (prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) { this.brewJump(this.props.currentEditorCursorPageNum, false); } } }, componentWillUnmount() { - if(this.resizeObserver) this.resizeObserver.disconnect(); + if (this.resizeObserver) this.resizeObserver.disconnect(); }, - handleControlKeys : function(e){ - if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return; + handleControlKeys: function (e) { + if (!(e.ctrlKey && e.metaKey && e.shiftKey)) return; const LEFTARROW_KEY = 37; const RIGHTARROW_KEY = 39; - if(e.keyCode == RIGHTARROW_KEY) this.brewJump(); - if(e.keyCode == LEFTARROW_KEY) this.sourceJump(); - if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) { + if (e.keyCode == RIGHTARROW_KEY) this.brewJump(); + if (e.keyCode == LEFTARROW_KEY) this.sourceJump(); + if (e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) { e.stopPropagation(); e.preventDefault(); } }, - updateCurrentCursorPage : function(cursor) { - const lines = this.props.brew.text.split('\n').slice(1, cursor.line + 1); - const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; - const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); + updateCurrentCursorPage: function (cursor) { + const lines = this.props.brew.text.split("\n").slice(1, cursor.line + 1); + const pageRegex = this.props.brew.renderer == "V3" ? PAGEBREAK_REGEX_V3 : /\\page/; + const currentPage = lines.reduce((count, line) => count + (pageRegex.test(line) ? 1 : 0), 1); this.props.onCursorPageChange(currentPage); }, - updateCurrentViewPage : function(topScrollLine) { - const lines = this.props.brew.text.split('\n').slice(1, topScrollLine + 1); - const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; - const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); + updateCurrentViewPage: function (topScrollLine) { + const lines = this.props.brew.text.split("\n").slice(1, topScrollLine + 1); + const pageRegex = this.props.brew.renderer == "V3" ? PAGEBREAK_REGEX_V3 : /\\page/; + const currentPage = lines.reduce((count, line) => count + (pageRegex.test(line) ? 1 : 0), 1); this.props.onViewPageChange(currentPage); }, - handleInject : function(injectText){ + handleInject: function (injectText) { this.codeEditor.current?.injectText(injectText, false); }, - handleViewChange : function(newView){ - this.props.setMoveArrows(newView === 'text'); + handleViewChange: function (newView) { + this.props.setMoveArrows(newView === "text"); - this.setState({ - view : newView - }, ()=>{ - this.codeEditor.current?.codeMirror?.focus(); - }); + this.setState( + { + view: newView, + }, + () => { + this.codeEditor.current?.codeMirror?.focus(); + }, + ); }, - highlightCustomMarkdown : function(){ - if(!this.codeEditor.current?.codeMirror) return; - if((this.state.view === 'text') ||(this.state.view === 'snippet')) { + highlightCustomMarkdown: function () { + if (!this.codeEditor.current?.codeMirror) return; + if (this.state.view === "text" || this.state.view === "snippet") { const codeMirror = this.codeEditor.current.codeMirror; - codeMirror?.operation(()=>{ // Batch CodeMirror styling + codeMirror?.operation(() => { + // Batch CodeMirror styling const foldLines = []; //reset custom text styles - const customHighlights = codeMirror?.getAllMarks().filter((mark)=>{ + const customHighlights = codeMirror?.getAllMarks().filter((mark) => { // Record details of folded sections - if(mark.__isFold) { + if (mark.__isFold) { const fold = mark.find(); foldLines.push({ from: fold.from?.line, to: fold.to?.line }); } return !mark.__isFold; }); //Don't undo code folding - for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear(); + for (let i = customHighlights.length - 1; i >= 0; i--) customHighlights[i].clear(); let userSnippetCount = 1; // start snippet count from snippet 1 let editorPageCount = 1; // start page count from page 1 - const whichSource = this.state.view === 'text' ? this.props.brew.text : this.props.brew.snippets; - _.forEach(whichSource?.split('\n'), (line, lineNumber)=>{ - - const tabHighlight = this.state.view === 'text' ? 'pageLine' : 'snippetLine'; - const textOrSnip = this.state.view === 'text'; + const whichSource = this.state.view === "text" ? this.props.brew.text : this.props.brew.snippets; + _.forEach(whichSource?.split("\n"), (line, lineNumber) => { + const tabHighlight = this.state.view === "text" ? "pageLine" : "snippetLine"; + const textOrSnip = this.state.view === "text"; //reset custom line styles - codeMirror?.removeLineClass(lineNumber, 'background', 'pageLine'); - codeMirror?.removeLineClass(lineNumber, 'background', 'snippetLine'); - codeMirror?.removeLineClass(lineNumber, 'text'); - codeMirror?.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash'); + codeMirror?.removeLineClass(lineNumber, "background", "pageLine"); + codeMirror?.removeLineClass(lineNumber, "background", "snippetLine"); + codeMirror?.removeLineClass(lineNumber, "text"); + codeMirror?.removeLineClass(lineNumber, "wrap", "sourceMoveFlash"); // Don't process lines inside folded text // If the current lineNumber is inside any folded marks, skip line styling - if(foldLines.some((fold)=>lineNumber >= fold.from && lineNumber <= fold.to)) - return; + if (foldLines.some((fold) => lineNumber >= fold.from && lineNumber <= fold.to)) return; // Styling for \page breaks - if((this.props.renderer == 'legacy' && line.includes('\\page')) || - (this.props.renderer == 'V3' && line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3))) { - - if((lineNumber > 0) && (textOrSnip)) // Since \page is optional on first line of document, + if ( + (this.props.renderer == "legacy" && line.includes("\\page")) || + (this.props.renderer == "V3" && + line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3)) + ) { + if (lineNumber > 0 && textOrSnip) + // Since \page is optional on first line of document, editorPageCount += 1; // don't use it to increment page count; stay at 1 - else if(this.state.view !== 'text') userSnippetCount += 1; + else if (this.state.view !== "text") userSnippetCount += 1; // add back the original class 'background' but also add the new class '.pageline' - codeMirror?.addLineClass(lineNumber, 'background', tabHighlight); - const pageCountElement = Object.assign(document.createElement('span'), { - className : 'editor-page-count', - textContent : textOrSnip ? editorPageCount : userSnippetCount + codeMirror?.addLineClass(lineNumber, "background", tabHighlight); + const pageCountElement = Object.assign(document.createElement("span"), { + className: "editor-page-count", + textContent: textOrSnip ? editorPageCount : userSnippetCount, }); codeMirror?.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement); - }; - + } // New CodeMirror styling for V3 renderer - if(this.props.renderer === 'V3') { - if(line.match(/^\\column(?:break)?$/)){ - codeMirror?.addLineClass(lineNumber, 'text', 'columnSplit'); + if (this.props.renderer === "V3") { + if (line.match(/^\\column(?:break)?$/)) { + codeMirror?.addLineClass(lineNumber, "text", "columnSplit"); } // definition lists - if(line.includes('::')){ - if(/^:*$/.test(line) == true){ return; }; - const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error. + if (line.includes("::")) { + if (/^:*$/.test(line) == true) { + return; + } + const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/dmy; // the `d` flag, for match indices, throws an ESLint error. let match; - while ((match = regex.exec(line)) != null){ - codeMirror?.markText({ line: lineNumber, ch: match.indices[0][0] }, { line: lineNumber, ch: match.indices[0][1] }, { className: 'dl-highlight' }); - codeMirror?.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' }); - codeMirror?.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' }); + while ((match = regex.exec(line)) != null) { + codeMirror?.markText( + { line: lineNumber, ch: match.indices[0][0] }, + { line: lineNumber, ch: match.indices[0][1] }, + { className: "dl-highlight" }, + ); + codeMirror?.markText( + { line: lineNumber, ch: match.indices[1][0] }, + { line: lineNumber, ch: match.indices[1][1] }, + { className: "dt-highlight" }, + ); + codeMirror?.markText( + { line: lineNumber, ch: match.indices[2][0] }, + { line: lineNumber, ch: match.indices[2][1] }, + { className: "dd-highlight" }, + ); const ddIndex = match.indices[2][0]; const colons = /::/g; const colonMatches = colons.exec(match[2]); - if(colonMatches !== null){ - codeMirror?.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight' }); + if (colonMatches !== null) { + codeMirror?.markText( + { line: lineNumber, ch: colonMatches.index + ddIndex }, + { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, + { className: "dl-colon-highlight" }, + ); } } } // Subscript & Superscript - if(line.includes('^')) { - let startIndex = line.indexOf('^'); + if (line.includes("^")) { + let startIndex = line.indexOf("^"); const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy; - const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy; + const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy; while (startIndex >= 0) { superRegex.lastIndex = subRegex.lastIndex = startIndex; let isSuper = false; const match = subRegex.exec(line) || superRegex.exec(line); - if(match) { + if (match) { isSuper = !subRegex.lastIndex; - codeMirror?.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' }); + codeMirror?.markText( + { line: lineNumber, ch: match.index }, + { line: lineNumber, ch: match.index + match[0].length }, + { className: isSuper ? "superscript" : "subscript" }, + ); } - startIndex = line.indexOf('^', Math.max(startIndex + 1, subRegex.lastIndex, superRegex.lastIndex)); + startIndex = line.indexOf( + "^", + Math.max(startIndex + 1, subRegex.lastIndex, superRegex.lastIndex), + ); } } - // Highlight injectors {style} - if(line.includes('{') && line.includes('}')){ - const regex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm; + // Injections: single-line {…} + if (line.includes("{") && line.includes("}")) { + const injectionRegex = + /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm; let match; - while ((match = regex.exec(line)) != null) { - codeMirror?.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' }); + while ((match = injectionRegex.exec(line)) !== null) { + tokens.push({ + line: lineNumber, + from: match.index, + to: match.index + match[1].length, + type: "injection", + }); } - } - // Highlight inline spans {{content}} - if(line.includes('{{') && line.includes('}}')){ - const regex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g; + } else if (line.includes("{{") && line.includes("}}")) { // Inline blocks: single-line {{…}} + const spanRegex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g; let match; let blockCount = 0; - while ((match = regex.exec(line)) != null) { - if(match[0].startsWith('{')) { + while ((match = spanRegex.exec(line)) !== null) { + if (match[0].startsWith("{{")) { blockCount += 1; } else { blockCount -= 1; } - if(blockCount < 0) { + if (blockCount < 0) { blockCount = 0; continue; } - codeMirror?.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' }); + tokens.push({ + line: lineNumber, + from: match.index, + to: match.index + match[0].length, + type: "inline-block", + }); } - } else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){ + } else if (line.trimLeft().startsWith("{{") || line.trimLeft().startsWith("}}")) { // Highlight block divs {{\n Content \n}} - let endCh = line.length+1; + let endCh = line.length + 1; - const match = line.match(/^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/); - if(match) - endCh = match.index+match[0].length; - codeMirror?.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); + const match = line.match( + /^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/, + ); + if (match) endCh = match.index + match[0].length; + codeMirror?.markText( + { line: lineNumber, ch: 0 }, + { line: lineNumber, ch: endCh }, + { className: "block" }, + ); } // Emojis - if(line.match(/:[^\s:]+:/g)) { - let startIndex = line.indexOf(':'); + if (line.match(/:[^\s:]+:/g)) { + let startIndex = line.indexOf(":"); const emojiRegex = /:[^\s:]+:/gy; while (startIndex >= 0) { emojiRegex.lastIndex = startIndex; const match = emojiRegex.exec(line); - if(match) { + if (match) { let tokens = Markdown.marked.lexer(match[0]); - tokens = tokens[0].tokens.filter((t)=>t.type == 'emoji'); - if(!tokens.length) - return; + tokens = tokens[0].tokens.filter((t) => t.type == "emoji"); + if (!tokens.length) return; const startPos = { line: lineNumber, ch: match.index }; - const endPos = { line: lineNumber, ch: match.index + match[0].length }; + const endPos = { line: lineNumber, ch: match.index + match[0].length }; // Iterate over conflicting marks and clear them const marks = codeMirror?.findMarks(startPos, endPos); - marks.forEach(function(marker) { - if(!marker.__isFold) marker.clear(); + marks.forEach(function (marker) { + if (!marker.__isFold) marker.clear(); }); - codeMirror?.markText(startPos, endPos, { className: 'emoji' }); + codeMirror?.markText(startPos, endPos, { className: "emoji" }); } - startIndex = line.indexOf(':', Math.max(startIndex + 1, emojiRegex.lastIndex)); + startIndex = line.indexOf(":", Math.max(startIndex + 1, emojiRegex.lastIndex)); } } } @@ -329,198 +382,226 @@ const Editor = createReactClass({ } }, - brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){ - if(!window || !this.isText() || isJumping) - return; + brewJump: function (targetPage = this.props.currentEditorCursorPageNum, smooth = true) { + if (!window || !this.isText() || isJumping) return; // Get current brewRenderer scroll position and calculate target position - const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0]; + const brewRenderer = window.frames["BrewRenderer"].contentDocument.getElementsByClassName("brewRenderer")[0]; const currentPos = brewRenderer.scrollTop; - const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top; + const targetPos = window.frames["BrewRenderer"].contentDocument + .getElementById(`p${targetPage}`) + .getBoundingClientRect().top; let scrollingTimeout; - const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times - clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs - scrollingTimeout = setTimeout(()=>{ + const checkIfScrollComplete = () => { + // Prevent interrupting a scroll in progress if user clicks multiple times + clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs + scrollingTimeout = setTimeout(() => { isJumping = false; - brewRenderer.removeEventListener('scroll', checkIfScrollComplete); - }, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done + brewRenderer.removeEventListener("scroll", checkIfScrollComplete); + }, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done }; isJumping = true; checkIfScrollComplete(); - brewRenderer.addEventListener('scroll', checkIfScrollComplete); + brewRenderer.addEventListener("scroll", checkIfScrollComplete); - if(smooth) { - const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling + if (smooth) { + const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling const bounceDelay = 100; const scrollDelay = 500; - if(!this.throttleBrewMove) { - this.throttleBrewMove = _.throttle((currentPos, bouncePos, targetPos)=>{ - brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' }); - setTimeout(()=>{ - brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' }); - }, bounceDelay); - }, scrollDelay, { leading: true, trailing: false }); - }; + if (!this.throttleBrewMove) { + this.throttleBrewMove = _.throttle( + (currentPos, bouncePos, targetPos) => { + brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: "smooth" }); + setTimeout(() => { + brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: "smooth", block: "start" }); + }, bounceDelay); + }, + scrollDelay, + { leading: true, trailing: false }, + ); + } this.throttleBrewMove(currentPos, bouncePos, targetPos); } else { - brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'instant', block: 'start' }); + brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: "instant", block: "start" }); } }, - sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){ - if(!this.isText() || isJumping) - return; + sourceJump: function (targetPage = this.props.currentBrewRendererPageNum, smooth = true) { + if (!this.isText() || isJumping) return; - const textSplit = this.props.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; - const textString = this.props.brew.text.split(textSplit).slice(0, targetPage-1).join(textSplit); - const targetLine = textString.match('\n') ? textString.split('\n').length - 1 : -1; + const textSplit = this.props.renderer == "V3" ? PAGEBREAK_REGEX_V3 : /\\page/; + const textString = this.props.brew.text + .split(textSplit) + .slice(0, targetPage - 1) + .join(textSplit); + const targetLine = textString.match("\n") ? textString.split("\n").length - 1 : -1; let currentY = this.codeEditor.current.codeMirror?.getScrollInfo().top; - let targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, 'local', true); + let targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, "local", true); let scrollingTimeout; - const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times + const checkIfScrollComplete = () => { + // Prevent interrupting a scroll in progress if user clicks multiple times clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs - scrollingTimeout = setTimeout(()=>{ + scrollingTimeout = setTimeout(() => { isJumping = false; - this.codeEditor.current.codeMirror?.off('scroll', checkIfScrollComplete); + this.codeEditor.current.codeMirror?.off("scroll", checkIfScrollComplete); }, 150); // If 150 ms pass without a scroll event, assume scrolling is done }; isJumping = true; checkIfScrollComplete(); - if(this.codeEditor.current?.codeMirror) { - this.codeEditor.current.codeMirror?.on('scroll', checkIfScrollComplete); + if (this.codeEditor.current?.codeMirror) { + this.codeEditor.current.codeMirror?.on("scroll", checkIfScrollComplete); } - if(smooth) { + if (smooth) { //Scroll 1/10 of the way every 10ms until 1px off. - const incrementalScroll = setInterval(()=>{ + const incrementalScroll = setInterval(() => { currentY += (targetY - currentY) / 10; this.codeEditor.current.codeMirror?.scrollTo(null, currentY); // Update target: target height is not accurate until within +-10 lines of the visible window - if(Math.abs(targetY - currentY > 100)) - targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, 'local', true); + if (Math.abs(targetY - currentY > 100)) + targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, "local", true); // End when close enough - if(Math.abs(targetY - currentY) < 1) { - this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference + if (Math.abs(targetY - currentY) < 1) { + this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); - this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); + this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, "wrap", "sourceMoveFlash"); clearInterval(incrementalScroll); } }, 10); } else { - this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference + this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); - this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); + this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, "wrap", "sourceMoveFlash"); } }, //Called when there are changes to the editor's dimensions - update : function(){}, + update: function () {}, - updateEditorTheme : function(newTheme){ + updateEditorTheme: function (newTheme) { window.localStorage.setItem(EDITOR_THEME_KEY, newTheme); this.setState({ - editorTheme : newTheme + editorTheme: newTheme, }); }, //Called by CodeEditor after document switch, so Snippetbar can refresh UndoHistory - rerenderParent : function (){ + rerenderParent: function () { this.forceUpdate(); }, - renderEditor : function(){ - if(this.isText()){ - return <> - - ; + renderEditor: function () { + if (this.isText()) { + return ( + <> + + + ); } - if(this.isStyle()){ - return <> - - ; + if (this.isStyle()) { + return ( + <> + + + ); } - if(this.isMeta()){ - return <> - - - ; + if (this.isMeta()) { + return ( + <> + + + + ); } - if(this.isSnip()){ - if(!this.props.brew.snippets) { this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; } - return <> - - ; + if (this.isSnip()) { + if (!this.props.brew.snippets) { + this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; + } + return ( + <> + + + ); } }, - redo : function(){ + redo: function () { return this.codeEditor.current?.redo(); }, - historySize : function(){ + historySize: function () { return this.codeEditor.current?.historySize(); }, - undo : function(){ + undo: function () { return this.codeEditor.current?.undo(); }, - foldCode : function(){ + foldCode: function () { return this.codeEditor.current?.foldAllCode(); }, - unfoldCode : function(){ + unfoldCode: function () { return this.codeEditor.current?.unfoldAllCode(); }, - render : function(){ + render: function () { return ( -
+
); - } + }, }); export default Editor; diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 9db6df26f..db2421b0f 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -10,25 +10,25 @@ .codeEditor { height : calc(100% - 25px); .cm-editor { height : 100%; } - .pageLine, .snippetLine { + .cm-pageLine, .cm-snippetLine { background : #33333328; border-top : #333399 solid 1px; } - .editor-page-count { + .cm-editor-page-count { float : right; color : grey; } - .editor-snippet-count { + .cm-editor-snippet-count { float : right; color : grey; } - .columnSplit { + .cm-cm-columnSplit { font-style : italic; color : grey; background-color : fade(#229999, 15%); border-bottom : #229999 solid 1px; } - .define { + .cm-define { &:not(.term):not(.definition) { font-weight : bold; color : #949494; @@ -38,21 +38,21 @@ &.term { color : rgb(96, 117, 143); } &.definition { color : rgb(97, 57, 178); } } - .block:not(.cm-comment) { + .cm-block:not(.cm-comment) { font-weight : bold; color : purple; //font-style: italic; } - .inline-block:not(.cm-comment) { + .cm-inline-block:not(.cm-comment) { font-weight : bold; color : red; //font-style: italic; } - .injection:not(.cm-comment) { + .cm-injection:not(.cm-comment) { font-weight : bold; color : green; } - .emoji:not(.cm-comment) { + .cm-emoji:not(.cm-comment) { padding-bottom : 1px; margin-left : 2px; font-weight : bold; @@ -62,19 +62,19 @@ background : #FFC8FF; border-radius : 6px; } - .superscript:not(.cm-comment) { + .cm-superscript:not(.cm-comment) { font-size : 0.9em; font-weight : bold; vertical-align : super; color : goldenrod; } - .subscript:not(.cm-comment) { + .cm-subscript:not(.cm-comment) { font-size : 0.9em; font-weight : bold; vertical-align : sub; color : rgb(123, 123, 15); } - .dl-highlight { + .cm-dl-highlight { &.dl-colon-highlight { font-weight : bold; color : #949494; From c76b174fd6215448e0520e3ea924cccd75915ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Mon, 23 Mar 2026 12:13:55 +0100 Subject: [PATCH 08/56] blocks working, spans and injections confused --- .../codeEditor/customMarkdownGrammar.js | 51 ++++++++----------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/client/components/codeEditor/customMarkdownGrammar.js b/client/components/codeEditor/customMarkdownGrammar.js index 625983ad1..bc45aee28 100644 --- a/client/components/codeEditor/customMarkdownGrammar.js +++ b/client/components/codeEditor/customMarkdownGrammar.js @@ -45,38 +45,27 @@ export function tokenizeCustomMarkdown(text) { tokens.push({ line: lineNumber, type: customTags.definitionTerm }); } - // Track ranges already marked for injections - const injectionRanges = []; - - if (line.includes("{") && line.includes("}")) { - const regex = /{[^{}]*}/gm; - let match; - while ((match = regex.exec(line)) != null) { - codeMirror?.markText( - { line: lineNumber, ch: match.index }, - { line: lineNumber, ch: match.index + match[0].length }, - { className: "injection" }, - ); - injectionRanges.push([match.index, match.index + match[0].length]); - } + // --- Injection `{…}` --- + const injectorRegex = /{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1}/g; + let match; + while ((match = injectorRegex.exec(lineText)) !== null) { + tokens.push({ + line: lineNumber, + type: customTags.injection, + from: match.index, + to: match.index + match[0].length, + }); } - // Now mark inline blocks, but skip overlapping injection ranges - if (line.includes("{{") && line.includes("}}")) { - const regex = /{{[^{}]*}}/gm; - let match; - while ((match = regex.exec(line)) != null) { - const start = match.index, - end = match.index + match[0].length; - const overlaps = injectionRanges.some(([iStart, iEnd]) => start < iEnd && end > iStart); - if (!overlaps) { - codeMirror?.markText( - { line: lineNumber, ch: start }, - { line: lineNumber, ch: end }, - { className: "inline-block" }, - ); - } - } + // --- Inline block `{{…}}` on the same line --- + const inlineRegex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *}}/g; + while ((match = inlineRegex.exec(lineText)) !== null) { + tokens.push({ + line: lineNumber, + type: customTags.inlineBlock, + from: match.index, + to: match.index + match[0].length, + }); } // --- Multi-line blocks `{{…}}` --- only start/end lines @@ -89,6 +78,8 @@ export function tokenizeCustomMarkdown(text) { tokens.push({ line: lineNumber, type: customTags.block }); inBlock = false; } + + }); return tokens; From 02ae7176f7351d6ed0f0e76573f56bae5ae203df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Mon, 23 Mar 2026 23:55:35 +0100 Subject: [PATCH 09/56] subscript and superscript --- client/components/codeEditor/codeEditor.jsx | 26 ++++++++++---- .../codeEditor/customMarkdownGrammar.js | 34 ++++++++++++++++--- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index d6d564a60..14fd4914a 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -64,18 +64,30 @@ const customHighlightPlugin = ViewPlugin.fromClass( } } buildDecorations(view) { - const widgets = []; + const decos = []; const tokens = tokenizeCustomMarkdown(view.state.doc.toString()); - // sort by line number - tokens.sort((a, b) => a.line - b.line); - tokens.forEach((tok) => { - const line = view.state.doc.line(tok.line + 1); // CM lines are 1-based - widgets.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); + 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}` }), + }); + } else { + decos.push({ + from: line.from, + to: line.from, + deco: Decoration.line({ class: `cm-${tok.type}` }), + }); + } }); - return Decoration.set(widgets); + 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))); } }, { diff --git a/client/components/codeEditor/customMarkdownGrammar.js b/client/components/codeEditor/customMarkdownGrammar.js index bc45aee28..6290982e2 100644 --- a/client/components/codeEditor/customMarkdownGrammar.js +++ b/client/components/codeEditor/customMarkdownGrammar.js @@ -36,9 +36,37 @@ export function tokenizeCustomMarkdown(text) { if (/:\w+?:/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.emoji }); // --- Superscript / Subscript --- - if (/\^\^/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.subscript }); - if (/\^/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.superscript }); + 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), + ); + } + }; // --- Definition lists --- if (/::/.test(lineText)) { tokens.push({ line: lineNumber, type: customTags.definitionDesc }); @@ -78,8 +106,6 @@ export function tokenizeCustomMarkdown(text) { tokens.push({ line: lineNumber, type: customTags.block }); inBlock = false; } - - }); return tokens; 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 10/56] 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); } } } From bba3199dbac0bc53d63d9a38bc0ad467ef8ea9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Tue, 24 Mar 2026 23:41:17 +0100 Subject: [PATCH 11/56] fix back editor.jsx --- client/homebrew/editor/editor.jsx | 615 +++++++++++++----------------- 1 file changed, 267 insertions(+), 348 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index a2ecbcb37..62cea09eb 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -1,16 +1,16 @@ /*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ -import "./editor.less"; -import React from "react"; -import createReactClass from "create-react-class"; -import _ from "lodash"; -import dedent from "dedent"; -import Markdown from "@shared/markdown.js"; +import './editor.less'; +import React from 'react'; +import createReactClass from 'create-react-class'; +import _ from 'lodash'; +import dedent from 'dedent'; +import Markdown from '@shared/markdown.js'; -import CodeEditor from "../../components/codeEditor/codeEditor.jsx"; -import SnippetBar from "./snippetbar/snippetbar.jsx"; -import MetadataEditor from "./metadataEditor/metadataEditor.jsx"; +import CodeEditor from '../../components/codeEditor/codeEditor.jsx'; +import SnippetBar from './snippetbar/snippetbar.jsx'; +import MetadataEditor from './metadataEditor/metadataEditor.jsx'; -const EDITOR_THEME_KEY = "HB_editor_theme"; +const EDITOR_THEME_KEY = 'HB_editor_theme'; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/; @@ -32,348 +32,295 @@ const DEFAULT_SNIPPET_TEXT = dedent` let isJumping = false; const Editor = createReactClass({ - displayName: "Editor", - getDefaultProps: function () { + displayName : 'Editor', + getDefaultProps : function() { return { - brew: { - text: "", - style: "", + brew : { + text : '', + style : '' }, - onBrewChange: () => {}, - reportError: () => {}, + onBrewChange : ()=>{}, + reportError : ()=>{}, - onCursorPageChange: () => {}, - onViewPageChange: () => {}, + onCursorPageChange : ()=>{}, + onViewPageChange : ()=>{}, - editorTheme: "default", - renderer: "legacy", + editorTheme : 'default', + renderer : 'legacy', - currentEditorCursorPageNum: 1, - currentEditorViewPageNum: 1, - currentBrewRendererPageNum: 1, + currentEditorCursorPageNum : 1, + currentEditorViewPageNum : 1, + currentBrewRendererPageNum : 1, }; }, - getInitialState: function () { + getInitialState : function() { return { - editorTheme: this.props.editorTheme, - view: "text", //'text', 'style', 'meta', 'snippet' - snippetBarHeight: 26, + editorTheme : this.props.editorTheme, + view : 'text', //'text', 'style', 'meta', 'snippet' + snippetBarHeight : 26, }; }, - editor: React.createRef(null), - codeEditor: React.createRef(null), + editor : React.createRef(null), + codeEditor : React.createRef(null), - isText: function () { - return this.state.view == "text"; - }, - isStyle: function () { - return this.state.view == "style"; - }, - isMeta: function () { - return this.state.view == "meta"; - }, - isSnip: function () { - return this.state.view == "snippet"; - }, + isText : function() {return this.state.view == 'text';}, + isStyle : function() {return this.state.view == 'style';}, + isMeta : function() {return this.state.view == 'meta';}, + isSnip : function() {return this.state.view == 'snippet';}, + + componentDidMount : function() { - componentDidMount: function () { this.highlightCustomMarkdown(); - document.getElementById("BrewRenderer").addEventListener("keydown", this.handleControlKeys); - document.addEventListener("keydown", this.handleControlKeys); + document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys); + document.addEventListener('keydown', this.handleControlKeys); - this.codeEditor.current.codeMirror?.on("cursorActivity", (cm) => { - this.updateCurrentCursorPage(cm.getCursor()); - }); - this.codeEditor.current.codeMirror?.on( - "scroll", - _.throttle(() => { - this.updateCurrentViewPage(this.codeEditor.current.getTopVisibleLine()); - }, 200), - ); + this.codeEditor.current.codeMirror?.on('cursorActivity', (cm)=>{this.updateCurrentCursorPage(cm.getCursor());}); + this.codeEditor.current.codeMirror?.on('scroll', _.throttle(()=>{this.updateCurrentViewPage(this.codeEditor.current.getTopVisibleLine());}, 200)); const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY); - if (editorTheme) { + if(editorTheme) { this.setState({ - editorTheme: editorTheme, + editorTheme : editorTheme }); } - const snippetBar = document.querySelector(".editor > .snippetBar"); - if (!snippetBar) return; + const snippetBar = document.querySelector('.editor > .snippetBar'); + if(!snippetBar) return; - this.resizeObserver = new ResizeObserver((entries) => { - const height = document.querySelector(".editor > .snippetBar").offsetHeight; + this.resizeObserver = new ResizeObserver((entries)=>{ + const height = document.querySelector('.editor > .snippetBar').offsetHeight; this.setState({ snippetBarHeight: height }); }); this.resizeObserver.observe(snippetBar); }, - componentDidUpdate: function (prevProps, prevState, snapshot) { + componentDidUpdate : function(prevProps, prevState, snapshot) { + this.highlightCustomMarkdown(); - if (prevProps.moveBrew !== this.props.moveBrew) this.brewJump(); + if(prevProps.moveBrew !== this.props.moveBrew) + this.brewJump(); - if (prevProps.moveSource !== this.props.moveSource) this.sourceJump(); + if(prevProps.moveSource !== this.props.moveSource) + this.sourceJump(); - if (this.props.liveScroll) { - if (prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) { + if(this.props.liveScroll) { + if(prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) { this.sourceJump(this.props.currentBrewRendererPageNum, false); - } else if (prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) { + } else if(prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) { this.brewJump(this.props.currentEditorViewPageNum, false); - } else if (prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) { + } else if(prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) { this.brewJump(this.props.currentEditorCursorPageNum, false); } } }, componentWillUnmount() { - if (this.resizeObserver) this.resizeObserver.disconnect(); + if(this.resizeObserver) this.resizeObserver.disconnect(); }, - handleControlKeys: function (e) { - if (!(e.ctrlKey && e.metaKey && e.shiftKey)) return; + handleControlKeys : function(e){ + if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return; const LEFTARROW_KEY = 37; const RIGHTARROW_KEY = 39; - if (e.keyCode == RIGHTARROW_KEY) this.brewJump(); - if (e.keyCode == LEFTARROW_KEY) this.sourceJump(); - if (e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) { + if(e.keyCode == RIGHTARROW_KEY) this.brewJump(); + if(e.keyCode == LEFTARROW_KEY) this.sourceJump(); + if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) { e.stopPropagation(); e.preventDefault(); } }, - updateCurrentCursorPage: function (cursor) { - const lines = this.props.brew.text.split("\n").slice(1, cursor.line + 1); - const pageRegex = this.props.brew.renderer == "V3" ? PAGEBREAK_REGEX_V3 : /\\page/; - const currentPage = lines.reduce((count, line) => count + (pageRegex.test(line) ? 1 : 0), 1); + updateCurrentCursorPage : function(cursor) { + const lines = this.props.brew.text.split('\n').slice(1, cursor.line + 1); + const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; + const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); this.props.onCursorPageChange(currentPage); }, - updateCurrentViewPage: function (topScrollLine) { - const lines = this.props.brew.text.split("\n").slice(1, topScrollLine + 1); - const pageRegex = this.props.brew.renderer == "V3" ? PAGEBREAK_REGEX_V3 : /\\page/; - const currentPage = lines.reduce((count, line) => count + (pageRegex.test(line) ? 1 : 0), 1); + updateCurrentViewPage : function(topScrollLine) { + const lines = this.props.brew.text.split('\n').slice(1, topScrollLine + 1); + const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; + const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); this.props.onViewPageChange(currentPage); }, - handleInject: function (injectText) { + handleInject : function(injectText){ this.codeEditor.current?.injectText(injectText, false); }, - handleViewChange: function (newView) { - this.props.setMoveArrows(newView === "text"); + handleViewChange : function(newView){ + this.props.setMoveArrows(newView === 'text'); - this.setState( - { - view: newView, - }, - () => { - this.codeEditor.current?.codeMirror?.focus(); - }, - ); + this.setState({ + view : newView + }, ()=>{ + this.codeEditor.current?.codeMirror?.focus(); + }); }, - highlightCustomMarkdown: function () { - if (!this.codeEditor.current?.codeMirror) return; - if (this.state.view === "text" || this.state.view === "snippet") { + highlightCustomMarkdown : function(){ + if(!this.codeEditor.current?.codeMirror) return; + if((this.state.view === 'text') ||(this.state.view === 'snippet')) { const codeMirror = this.codeEditor.current.codeMirror; - codeMirror?.operation(() => { - // Batch CodeMirror styling + codeMirror?.operation(()=>{ // Batch CodeMirror styling const foldLines = []; //reset custom text styles - const customHighlights = codeMirror?.getAllMarks().filter((mark) => { + const customHighlights = codeMirror?.getAllMarks().filter((mark)=>{ // Record details of folded sections - if (mark.__isFold) { + if(mark.__isFold) { const fold = mark.find(); foldLines.push({ from: fold.from?.line, to: fold.to?.line }); } return !mark.__isFold; }); //Don't undo code folding - for (let i = customHighlights.length - 1; i >= 0; i--) customHighlights[i].clear(); + for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear(); let userSnippetCount = 1; // start snippet count from snippet 1 let editorPageCount = 1; // start page count from page 1 - const whichSource = this.state.view === "text" ? this.props.brew.text : this.props.brew.snippets; - _.forEach(whichSource?.split("\n"), (line, lineNumber) => { - const tabHighlight = this.state.view === "text" ? "pageLine" : "snippetLine"; - const textOrSnip = this.state.view === "text"; + const whichSource = this.state.view === 'text' ? this.props.brew.text : this.props.brew.snippets; + _.forEach(whichSource?.split('\n'), (line, lineNumber)=>{ + + const tabHighlight = this.state.view === 'text' ? 'pageLine' : 'snippetLine'; + const textOrSnip = this.state.view === 'text'; //reset custom line styles - codeMirror?.removeLineClass(lineNumber, "background", "pageLine"); - codeMirror?.removeLineClass(lineNumber, "background", "snippetLine"); - codeMirror?.removeLineClass(lineNumber, "text"); - codeMirror?.removeLineClass(lineNumber, "wrap", "sourceMoveFlash"); + codeMirror?.removeLineClass(lineNumber, 'background', 'pageLine'); + codeMirror?.removeLineClass(lineNumber, 'background', 'snippetLine'); + codeMirror?.removeLineClass(lineNumber, 'text'); + codeMirror?.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash'); // Don't process lines inside folded text // If the current lineNumber is inside any folded marks, skip line styling - if (foldLines.some((fold) => lineNumber >= fold.from && lineNumber <= fold.to)) return; + if(foldLines.some((fold)=>lineNumber >= fold.from && lineNumber <= fold.to)) + return; // Styling for \page breaks - if ( - (this.props.renderer == "legacy" && line.includes("\\page")) || - (this.props.renderer == "V3" && - line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3)) - ) { - if (lineNumber > 0 && textOrSnip) - // Since \page is optional on first line of document, + if((this.props.renderer == 'legacy' && line.includes('\\page')) || + (this.props.renderer == 'V3' && line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3))) { + + if((lineNumber > 0) && (textOrSnip)) // Since \page is optional on first line of document, editorPageCount += 1; // don't use it to increment page count; stay at 1 - else if (this.state.view !== "text") userSnippetCount += 1; + else if(this.state.view !== 'text') userSnippetCount += 1; // add back the original class 'background' but also add the new class '.pageline' - codeMirror?.addLineClass(lineNumber, "background", tabHighlight); - const pageCountElement = Object.assign(document.createElement("span"), { - className: "editor-page-count", - textContent: textOrSnip ? editorPageCount : userSnippetCount, + codeMirror?.addLineClass(lineNumber, 'background', tabHighlight); + const pageCountElement = Object.assign(document.createElement('span'), { + className : 'editor-page-count', + textContent : textOrSnip ? editorPageCount : userSnippetCount }); codeMirror?.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement); - } + }; + // New CodeMirror styling for V3 renderer - if (this.props.renderer === "V3") { - if (line.match(/^\\column(?:break)?$/)) { - codeMirror?.addLineClass(lineNumber, "text", "columnSplit"); + if(this.props.renderer === 'V3') { + if(line.match(/^\\column(?:break)?$/)){ + codeMirror?.addLineClass(lineNumber, 'text', 'columnSplit'); } // definition lists - if (line.includes("::")) { - if (/^:*$/.test(line) == true) { - return; - } - const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/dmy; // the `d` flag, for match indices, throws an ESLint error. + if(line.includes('::')){ + if(/^:*$/.test(line) == true){ return; }; + const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error. let match; - while ((match = regex.exec(line)) != null) { - codeMirror?.markText( - { line: lineNumber, ch: match.indices[0][0] }, - { line: lineNumber, ch: match.indices[0][1] }, - { className: "dl-highlight" }, - ); - codeMirror?.markText( - { line: lineNumber, ch: match.indices[1][0] }, - { line: lineNumber, ch: match.indices[1][1] }, - { className: "dt-highlight" }, - ); - codeMirror?.markText( - { line: lineNumber, ch: match.indices[2][0] }, - { line: lineNumber, ch: match.indices[2][1] }, - { className: "dd-highlight" }, - ); + while ((match = regex.exec(line)) != null){ + codeMirror?.markText({ line: lineNumber, ch: match.indices[0][0] }, { line: lineNumber, ch: match.indices[0][1] }, { className: 'dl-highlight' }); + codeMirror?.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' }); + codeMirror?.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' }); const ddIndex = match.indices[2][0]; const colons = /::/g; const colonMatches = colons.exec(match[2]); - if (colonMatches !== null) { - codeMirror?.markText( - { line: lineNumber, ch: colonMatches.index + ddIndex }, - { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, - { className: "dl-colon-highlight" }, - ); + if(colonMatches !== null){ + codeMirror?.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight' }); } } } // Subscript & Superscript - if (line.includes("^")) { - let startIndex = line.indexOf("^"); + if(line.includes('^')) { + let startIndex = line.indexOf('^'); const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy; - const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy; + const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy; while (startIndex >= 0) { superRegex.lastIndex = subRegex.lastIndex = startIndex; let isSuper = false; const match = subRegex.exec(line) || superRegex.exec(line); - if (match) { + if(match) { isSuper = !subRegex.lastIndex; - codeMirror?.markText( - { line: lineNumber, ch: match.index }, - { line: lineNumber, ch: match.index + match[0].length }, - { className: isSuper ? "superscript" : "subscript" }, - ); + codeMirror?.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' }); } - startIndex = line.indexOf( - "^", - Math.max(startIndex + 1, subRegex.lastIndex, superRegex.lastIndex), - ); + startIndex = line.indexOf('^', Math.max(startIndex + 1, subRegex.lastIndex, superRegex.lastIndex)); } } - // Injections: single-line {…} - if (line.includes("{") && line.includes("}")) { - const injectionRegex = - /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm; + // Highlight injectors {style} + if(line.includes('{') && line.includes('}')){ + const regex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm; let match; - while ((match = injectionRegex.exec(line)) !== null) { - tokens.push({ - line: lineNumber, - from: match.index, - to: match.index + match[1].length, - type: "injection", - }); + while ((match = regex.exec(line)) != null) { + codeMirror?.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' }); } - } else if (line.includes("{{") && line.includes("}}")) { // Inline blocks: single-line {{…}} - const spanRegex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g; + } + // Highlight inline spans {{content}} + if(line.includes('{{') && line.includes('}}')){ + const regex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g; let match; let blockCount = 0; - while ((match = spanRegex.exec(line)) !== null) { - if (match[0].startsWith("{{")) { + while ((match = regex.exec(line)) != null) { + if(match[0].startsWith('{')) { blockCount += 1; } else { blockCount -= 1; } - if (blockCount < 0) { + if(blockCount < 0) { blockCount = 0; continue; } - tokens.push({ - line: lineNumber, - from: match.index, - to: match.index + match[0].length, - type: "inline-block", - }); + codeMirror?.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' }); } - } else if (line.trimLeft().startsWith("{{") || line.trimLeft().startsWith("}}")) { + } else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){ // Highlight block divs {{\n Content \n}} - let endCh = line.length + 1; + let endCh = line.length+1; - const match = line.match( - /^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/, - ); - if (match) endCh = match.index + match[0].length; - codeMirror?.markText( - { line: lineNumber, ch: 0 }, - { line: lineNumber, ch: endCh }, - { className: "block" }, - ); + const match = line.match(/^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/); + if(match) + endCh = match.index+match[0].length; + codeMirror?.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); } // Emojis - if (line.match(/:[^\s:]+:/g)) { - let startIndex = line.indexOf(":"); + if(line.match(/:[^\s:]+:/g)) { + let startIndex = line.indexOf(':'); const emojiRegex = /:[^\s:]+:/gy; while (startIndex >= 0) { emojiRegex.lastIndex = startIndex; const match = emojiRegex.exec(line); - if (match) { + if(match) { let tokens = Markdown.marked.lexer(match[0]); - tokens = tokens[0].tokens.filter((t) => t.type == "emoji"); - if (!tokens.length) return; + tokens = tokens[0].tokens.filter((t)=>t.type == 'emoji'); + if(!tokens.length) + return; const startPos = { line: lineNumber, ch: match.index }; - const endPos = { line: lineNumber, ch: match.index + match[0].length }; + const endPos = { line: lineNumber, ch: match.index + match[0].length }; // Iterate over conflicting marks and clear them const marks = codeMirror?.findMarks(startPos, endPos); - marks.forEach(function (marker) { - if (!marker.__isFold) marker.clear(); + marks.forEach(function(marker) { + if(!marker.__isFold) marker.clear(); }); - codeMirror?.markText(startPos, endPos, { className: "emoji" }); + codeMirror?.markText(startPos, endPos, { className: 'emoji' }); } - startIndex = line.indexOf(":", Math.max(startIndex + 1, emojiRegex.lastIndex)); + startIndex = line.indexOf(':', Math.max(startIndex + 1, emojiRegex.lastIndex)); } } } @@ -382,226 +329,198 @@ const Editor = createReactClass({ } }, - brewJump: function (targetPage = this.props.currentEditorCursorPageNum, smooth = true) { - if (!window || !this.isText() || isJumping) return; + brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){ + if(!window || !this.isText() || isJumping) + return; // Get current brewRenderer scroll position and calculate target position - const brewRenderer = window.frames["BrewRenderer"].contentDocument.getElementsByClassName("brewRenderer")[0]; + const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0]; const currentPos = brewRenderer.scrollTop; - const targetPos = window.frames["BrewRenderer"].contentDocument - .getElementById(`p${targetPage}`) - .getBoundingClientRect().top; + const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top; let scrollingTimeout; - const checkIfScrollComplete = () => { - // Prevent interrupting a scroll in progress if user clicks multiple times - clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs - scrollingTimeout = setTimeout(() => { + const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times + clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs + scrollingTimeout = setTimeout(()=>{ isJumping = false; - brewRenderer.removeEventListener("scroll", checkIfScrollComplete); - }, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done + brewRenderer.removeEventListener('scroll', checkIfScrollComplete); + }, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done }; isJumping = true; checkIfScrollComplete(); - brewRenderer.addEventListener("scroll", checkIfScrollComplete); + brewRenderer.addEventListener('scroll', checkIfScrollComplete); - if (smooth) { - const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling + if(smooth) { + const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling const bounceDelay = 100; const scrollDelay = 500; - if (!this.throttleBrewMove) { - this.throttleBrewMove = _.throttle( - (currentPos, bouncePos, targetPos) => { - brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: "smooth" }); - setTimeout(() => { - brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: "smooth", block: "start" }); - }, bounceDelay); - }, - scrollDelay, - { leading: true, trailing: false }, - ); - } + if(!this.throttleBrewMove) { + this.throttleBrewMove = _.throttle((currentPos, bouncePos, targetPos)=>{ + brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' }); + setTimeout(()=>{ + brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' }); + }, bounceDelay); + }, scrollDelay, { leading: true, trailing: false }); + }; this.throttleBrewMove(currentPos, bouncePos, targetPos); } else { - brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: "instant", block: "start" }); + brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'instant', block: 'start' }); } }, - sourceJump: function (targetPage = this.props.currentBrewRendererPageNum, smooth = true) { - if (!this.isText() || isJumping) return; + sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){ + if(!this.isText() || isJumping) + return; - const textSplit = this.props.renderer == "V3" ? PAGEBREAK_REGEX_V3 : /\\page/; - const textString = this.props.brew.text - .split(textSplit) - .slice(0, targetPage - 1) - .join(textSplit); - const targetLine = textString.match("\n") ? textString.split("\n").length - 1 : -1; + const textSplit = this.props.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; + const textString = this.props.brew.text.split(textSplit).slice(0, targetPage-1).join(textSplit); + const targetLine = textString.match('\n') ? textString.split('\n').length - 1 : -1; let currentY = this.codeEditor.current.codeMirror?.getScrollInfo().top; - let targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, "local", true); + let targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, 'local', true); let scrollingTimeout; - const checkIfScrollComplete = () => { - // Prevent interrupting a scroll in progress if user clicks multiple times + const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs - scrollingTimeout = setTimeout(() => { + scrollingTimeout = setTimeout(()=>{ isJumping = false; - this.codeEditor.current.codeMirror?.off("scroll", checkIfScrollComplete); + this.codeEditor.current.codeMirror?.off('scroll', checkIfScrollComplete); }, 150); // If 150 ms pass without a scroll event, assume scrolling is done }; isJumping = true; checkIfScrollComplete(); - if (this.codeEditor.current?.codeMirror) { - this.codeEditor.current.codeMirror?.on("scroll", checkIfScrollComplete); + if(this.codeEditor.current?.codeMirror) { + this.codeEditor.current.codeMirror?.on('scroll', checkIfScrollComplete); } - if (smooth) { + if(smooth) { //Scroll 1/10 of the way every 10ms until 1px off. - const incrementalScroll = setInterval(() => { + const incrementalScroll = setInterval(()=>{ currentY += (targetY - currentY) / 10; this.codeEditor.current.codeMirror?.scrollTo(null, currentY); // Update target: target height is not accurate until within +-10 lines of the visible window - if (Math.abs(targetY - currentY > 100)) - targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, "local", true); + if(Math.abs(targetY - currentY > 100)) + targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, 'local', true); // End when close enough - if (Math.abs(targetY - currentY) < 1) { - this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference + if(Math.abs(targetY - currentY) < 1) { + this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); - this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, "wrap", "sourceMoveFlash"); + this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); clearInterval(incrementalScroll); } }, 10); } else { - this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference + this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); - this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, "wrap", "sourceMoveFlash"); + this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); } }, //Called when there are changes to the editor's dimensions - update: function () {}, + update : function(){}, - updateEditorTheme: function (newTheme) { + updateEditorTheme : function(newTheme){ window.localStorage.setItem(EDITOR_THEME_KEY, newTheme); this.setState({ - editorTheme: newTheme, + editorTheme : newTheme }); }, //Called by CodeEditor after document switch, so Snippetbar can refresh UndoHistory - rerenderParent: function () { + rerenderParent : function (){ this.forceUpdate(); }, - renderEditor: function () { - if (this.isText()) { - return ( - <> - - - ); + renderEditor : function(){ + if(this.isText()){ + return <> + + ; } - if (this.isStyle()) { - return ( - <> - - - ); + if(this.isStyle()){ + return <> + + ; } - if (this.isMeta()) { - return ( - <> - - - - ); + if(this.isMeta()){ + return <> + + + ; } - if (this.isSnip()) { - if (!this.props.brew.snippets) { - this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; - } - return ( - <> - - - ); + if(this.isSnip()){ + if(!this.props.brew.snippets) { this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; } + return <> + + ; } }, - redo: function () { + redo : function(){ return this.codeEditor.current?.redo(); }, - historySize: function () { + historySize : function(){ return this.codeEditor.current?.historySize(); }, - undo: function () { + undo : function(){ return this.codeEditor.current?.undo(); }, - foldCode: function () { + foldCode : function(){ return this.codeEditor.current?.foldAllCode(); }, - unfoldCode: function () { + unfoldCode : function(){ return this.codeEditor.current?.unfoldAllCode(); }, - render: function () { + render : function(){ return ( -
+
); - }, + } }); -export default Editor; +export default Editor; \ No newline at end of file From 47b47ff2558bea06710996c99b2332606f777b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Wed, 25 Mar 2026 10:51:25 +0100 Subject: [PATCH 12/56] custom highlight blocks --- .../codeEditor/customMarkdownGrammar.js | 83 ++++++++++--------- client/homebrew/editor/editor.less | 2 +- 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/client/components/codeEditor/customMarkdownGrammar.js b/client/components/codeEditor/customMarkdownGrammar.js index 9ebafd92e..f5819f9d2 100644 --- a/client/components/codeEditor/customMarkdownGrammar.js +++ b/client/components/codeEditor/customMarkdownGrammar.js @@ -6,8 +6,9 @@ export const customTags = { snippetLine: "snippetLine", // .cm-snippetLine columnSplit: "columnSplit", // .cm-columnSplit snippetBreak: "snippetBreak", // .cm-snippetBreak - inlineBlock: "inline-block", // .cm-inline-block 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 @@ -15,7 +16,6 @@ export const customTags = { definitionTerm: "definitionTerm", // .cm-definitionTerm definitionDesc: "definitionDesc", // .cm-definitionDesc definitionColon: "definitionColon", // .cm-definitionColon - injection: "injection", // .cm-injection }; // --- Tokenizer function --- @@ -120,12 +120,8 @@ export function tokenizeCustomMarkdown(text) { } } - // --- Multiline definition list: term:\n::def1\n::def2 --- - // Only treat this line as a term if next line starts with :: + // multiline def list 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 = []; @@ -142,7 +138,6 @@ export function tokenizeCustomMarkdown(text) { } else break; } - console.log(defs); if (defs.length > 0) { tokens.push({ line: startLine, @@ -180,39 +175,53 @@ export function tokenizeCustomMarkdown(text) { } } - // --- Injection `{…}` --- - const injectorRegex = /{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1}/g; - let match; - while ((match = injectorRegex.exec(lineText)) !== null) { - tokens.push({ - line: lineNumber, - type: customTags.injection, - from: match.index, - to: match.index + match[0].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, + }); + console.log(match); + } } + 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; - // --- Inline block `{{…}}` on the same line --- - const inlineRegex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *}}/g; - while ((match = inlineRegex.exec(lineText)) !== null) { - tokens.push({ - line: lineNumber, - type: customTags.inlineBlock, - from: match.index, - to: match.index + match[0].length, - }); - } - - // --- Multi-line blocks `{{…}}` --- only start/end lines - if (lineText.trimLeft().startsWith("{{") && !lineText.trimLeft().endsWith("}}")) { - inBlock = true; - blockStart = lineNumber; + const match = lineText.match( + /^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/, + ); + if (match) endCh = match.index + match[0].length; tokens.push({ line: lineNumber, type: customTags.block }); } - if (lineText.trimLeft().startsWith("}}") && inBlock) { - tokens.push({ line: lineNumber, type: customTags.block }); - inBlock = false; - } + + }); return tokens; diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index ada924387..9ac16464b 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -22,7 +22,7 @@ float : right; color : grey; } - .cm-cm-columnSplit { + .cm-columnSplit { font-style : italic; color : grey; background-color : fade(#229999, 15%); From 5c8a6bc4d359bdf1c4524ad2875706065d307a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Wed, 25 Mar 2026 11:25:33 +0100 Subject: [PATCH 13/56] match regex properly --- client/components/codeEditor/customMarkdownGrammar.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/components/codeEditor/customMarkdownGrammar.js b/client/components/codeEditor/customMarkdownGrammar.js index f5819f9d2..d39bc4cdd 100644 --- a/client/components/codeEditor/customMarkdownGrammar.js +++ b/client/components/codeEditor/customMarkdownGrammar.js @@ -29,8 +29,8 @@ export function tokenizeCustomMarkdown(text) { lines.forEach((lineText, lineNumber) => { // --- Page / snippet lines --- - if (/\\page/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.pageLine }); - if (/\\snippet/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetLine }); + 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 }); @@ -185,7 +185,6 @@ export function tokenizeCustomMarkdown(text) { to: match.index + match[1].length +1, type: customTags.injection, }); - console.log(match); } } if (lineText.includes("{{") && lineText.includes("}}")) { From 3e6f006811aaf94fa8e71760e5b1eb3ad8e45c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Wed, 25 Mar 2026 11:25:38 +0100 Subject: [PATCH 14/56] fix lock --- package-lock.json | 386 ++++++++++++++++++++++++---------------------- 1 file changed, 201 insertions(+), 185 deletions(-) diff --git a/package-lock.json b/package-lock.json index b0747fce0..8e92d7ce8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2399,9 +2399,9 @@ } }, "node_modules/@codemirror/lang-yaml": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", - "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", + "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -2414,9 +2414,9 @@ } }, "node_modules/@codemirror/language": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz", - "integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", "license": "MIT", "peer": true, "dependencies": { @@ -3876,9 +3876,9 @@ } }, "node_modules/@lezer/css": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.2.tgz", - "integrity": "sha512-tRZAPl1j0pkmPL/pGG85GAbyQYnYv0j6UEdEt5e4ZYvp+OxDu2zVp2c/YddiuO8ZTa2CHsVELqRd/fGWjlISrg==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz", + "integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -4155,9 +4155,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.1.tgz", - "integrity": "sha512-xB0b51TB7IfDEzAojXahmr+gfA00uYVInJGgNNkeQG6RPnCPGr7udsylFLTubuIUSRE6FkcI1NElyRt83PP5oQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", "cpu": [ "arm" ], @@ -4168,9 +4168,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.1.tgz", - "integrity": "sha512-XOjPId0qwSDKHaIsdzHJtKCxX0+nH8MhBwvrNsT7tVyKmdTx1jJ4XzN5RZXCdTzMpufLb+B8llTC0D8uCrLhcw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", "cpu": [ "arm64" ], @@ -4181,9 +4181,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.1.tgz", - "integrity": "sha512-vQuRd28p0gQpPrS6kppd8IrWmFo42U8Pz1XLRjSZXq5zCqyMDYFABT7/sywL11mO1EL10Qhh7MVPEwkG8GiBeg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", "cpu": [ "arm64" ], @@ -4194,9 +4194,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.1.tgz", - "integrity": "sha512-x6VG6U29+Ivlnajrg1IHdzXeAwSoEHBFVO+CtC9Brugx6de712CUJobRUxsIA0KYrQvCmzNrMPFTT1A4CCqNTg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", "cpu": [ "x64" ], @@ -4207,9 +4207,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.1.tgz", - "integrity": "sha512-Sgi0Uo6t1YCHJMNO3Y8+bm+SvOanUGkoZKn/VJPwYUe2kp31X5KnXmzKd/NjW8iA3gFcfNZ64zh14uOGrIllCQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", "cpu": [ "arm64" ], @@ -4220,9 +4220,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.1.tgz", - "integrity": "sha512-AM4xnwEZwukdhk7laMWfzWu9JGSVnJd+Fowt6Fd7QW1nrf3h0Hp7Qx5881M4aqrUlKBCybOxz0jofvIIfl7C5g==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", "cpu": [ "x64" ], @@ -4233,9 +4233,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.1.tgz", - "integrity": "sha512-KUizqxpwaR2AZdAUsMWfL/C94pUu7TKpoPd88c8yFVixJ+l9hejkrwoK5Zj3wiNh65UeyryKnJyxL1b7yNqFQA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", "cpu": [ "arm" ], @@ -4246,9 +4246,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.1.tgz", - "integrity": "sha512-MZoQ/am77ckJtZGFAtPucgUuJWiop3m2R3lw7tC0QCcbfl4DRhQUBUkHWCkcrT3pqy5Mzv5QQgY6Dmlba6iTWg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", "cpu": [ "arm" ], @@ -4259,9 +4259,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.1.tgz", - "integrity": "sha512-Sez95TP6xGjkWB1608EfhCX1gdGrO5wzyN99VqzRtC17x/1bhw5VU1V0GfKUwbW/Xr1J8mSasoFoJa6Y7aGGSA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", "cpu": [ "arm64" ], @@ -4272,9 +4272,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.1.tgz", - "integrity": "sha512-9Cs2Seq98LWNOJzR89EGTZoiP8EkZ9UbQhBlDgfAkM6asVna1xJ04W2CLYWDN/RpUgOjtQvcv8wQVi1t5oQazA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", "cpu": [ "arm64" ], @@ -4285,9 +4285,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.1.tgz", - "integrity": "sha512-n9yqttftgFy7IrNEnHy1bOp6B4OSe8mJDiPkT7EqlM9FnKOwUMnCK62ixW0Kd9Clw0/wgvh8+SqaDXMFvw3KqQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", "cpu": [ "loong64" ], @@ -4298,9 +4298,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.1.tgz", - "integrity": "sha512-SfpNXDzVTqs/riak4xXcLpq5gIQWsqGWMhN1AGRQKB4qGSs4r0sEs3ervXPcE1O9RsQ5bm8Muz6zmQpQnPss1g==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", "cpu": [ "loong64" ], @@ -4311,9 +4311,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.1.tgz", - "integrity": "sha512-LjaChED0wQnjKZU+tsmGbN+9nN1XhaWUkAlSbTdhpEseCS4a15f/Q8xC2BN4GDKRzhhLZpYtJBZr2NZhR0jvNw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", "cpu": [ "ppc64" ], @@ -4324,9 +4324,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.1.tgz", - "integrity": "sha512-ojW7iTJSIs4pwB2xV6QXGwNyDctvXOivYllttuPbXguuKDX5vwpqYJsHc6D2LZzjDGHML414Tuj3LvVPe1CT1A==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", "cpu": [ "ppc64" ], @@ -4337,9 +4337,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.1.tgz", - "integrity": "sha512-FP+Q6WTcxxvsr0wQczhSE+tOZvFPV8A/mUE6mhZYFW9/eea/y/XqAgRoLLMuE9Cz0hfX5bi7p116IWoB+P237A==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", "cpu": [ "riscv64" ], @@ -4350,9 +4350,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.1.tgz", - "integrity": "sha512-L1uD9b/Ig8Z+rn1KttCJjwhN1FgjRMBKsPaBsDKkfUl7GfFq71pU4vWCnpOsGljycFEbkHWARZLf4lMYg3WOLw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", "cpu": [ "riscv64" ], @@ -4363,9 +4363,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.1.tgz", - "integrity": "sha512-EZc9NGTk/oSUzzOD4nYY4gIjteo2M3CiozX6t1IXGCOdgxJTlVu/7EdPeiqeHPSIrxkLhavqpBAUCfvC6vBOug==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", "cpu": [ "s390x" ], @@ -4376,9 +4376,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.1.tgz", - "integrity": "sha512-NQ9KyU1Anuy59L8+HHOKM++CoUxrQWrZWXRik4BJFm+7i5NP6q/SW43xIBr80zzt+PDBJ7LeNmloQGfa0JGk0w==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", "cpu": [ "x64" ], @@ -4389,9 +4389,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.1.tgz", - "integrity": "sha512-GZkLk2t6naywsveSFBsEb0PLU+JC9ggVjbndsbG20VPhar6D1gkMfCx4NfP9owpovBXTN+eRdqGSkDGIxPHhmQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", "cpu": [ "x64" ], @@ -4402,9 +4402,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.1.tgz", - "integrity": "sha512-1hjG9Jpl2KDOetr64iQd8AZAEjkDUUK5RbDkYWsViYLC1op1oNzdjMJeFiofcGhqbNTaY2kfgqowE7DILifsrA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", "cpu": [ "x64" ], @@ -4415,9 +4415,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.1.tgz", - "integrity": "sha512-ARoKfflk0SiiYm3r1fmF73K/yB+PThmOwfWCk1sr7x/k9dc3uGLWuEE9if+Pw21el8MSpp3TMnG5vLNsJ/MMGQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", "cpu": [ "arm64" ], @@ -4428,9 +4428,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.1.tgz", - "integrity": "sha512-oOST61G6VM45Mz2vdzWMr1s2slI7y9LqxEV5fCoWi2MDONmMvgsJVHSXxce/I2xOSZPTZ47nDPOl1tkwKWSHcw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", "cpu": [ "arm64" ], @@ -4441,9 +4441,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.1.tgz", - "integrity": "sha512-x5WgLi5dWpRz7WclKBGEF15LcWTh0ewrHM6Cq4A+WUbkysUMZNeqt05bwPonOQ3ihPS/WMhAZV5zB1DfnI4Sxg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", "cpu": [ "ia32" ], @@ -4454,9 +4454,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.1.tgz", - "integrity": "sha512-wS+zHAJRVP5zOL0e+a3V3E/NTEwM2HEvvNKoDy5Xcfs0o8lljxn+EAFPkUsxihBdmDq1JWzXmmB9cbssCPdxxw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", "cpu": [ "x64" ], @@ -4467,9 +4467,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.1.tgz", - "integrity": "sha512-rhHyrMeLpErT/C7BxcEsU4COHQUzHyrPYW5tOZUeUhziNtRuYxmDWvqQqzpuUt8xpOgmbKa1btGXfnA/ANVO+g==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", "cpu": [ "x64" ], @@ -4685,14 +4685,14 @@ "license": "MIT" }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz", - "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", + "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.1", - "@typescript-eslint/types": "^8.57.1", + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", "debug": "^4.4.3" }, "engines": { @@ -4707,14 +4707,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz", - "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", + "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1" + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4725,9 +4725,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz", - "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", + "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", "dev": true, "license": "MIT", "engines": { @@ -4742,9 +4742,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz", - "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", "dev": true, "license": "MIT", "engines": { @@ -4756,16 +4756,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz", - "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", + "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.57.1", - "@typescript-eslint/tsconfig-utils": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", + "@typescript-eslint/project-service": "8.57.2", + "@typescript-eslint/tsconfig-utils": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -4794,9 +4794,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4836,16 +4836,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz", - "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", + "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1" + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4860,13 +4860,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz", - "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", + "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -5304,9 +5304,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -5940,9 +5940,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001780", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", - "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", "funding": [ { "type": "opencollective", @@ -6648,9 +6648,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.321", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", - "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "version": "1.5.323", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.323.tgz", + "integrity": "sha512-oQm+FxbazvN2WICCbvJgj3IYPKV8awip57+W5VP+Aatk4kFU4pDYCPHZOX22Z27zpw8uttBehEqgK+VTJAYrVw==", "license": "ISC" }, "node_modules/emittery": { @@ -7025,9 +7025,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "29.15.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.0.tgz", - "integrity": "sha512-ZCGr7vTH2WSo2hrK5oM2RULFmMruQ7W3cX7YfwoTiPfzTGTFBMmrVIz45jZHd++cGKj/kWf02li/RhTGcANJSA==", + "version": "29.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.1.tgz", + "integrity": "sha512-6BjyErCQauz3zfJvzLw/kAez2lf4LEpbHLvWBfEcG4EI0ZiRSwjoH2uZulMouU8kRkBH+S0rhqn11IhTvxKgKw==", "dev": true, "license": "MIT", "dependencies": { @@ -7040,7 +7040,7 @@ "@typescript-eslint/eslint-plugin": "^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "jest": "*", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <7.0.0" }, "peerDependenciesMeta": { "@typescript-eslint/eslint-plugin": { @@ -10454,9 +10454,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -10594,9 +10594,9 @@ } }, "node_modules/mongoose": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.3.1.tgz", - "integrity": "sha512-58DuQti+LlRS74/UfWN4F3wZsC0Yr1dgTWZ2Wd3/TuSvm6rIdyAjDWbx2xGyuBooqJYdAWotVv4mQgVdivh+3Q==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.3.2.tgz", + "integrity": "sha512-4hMJA4vLAeNG6LnDwJImPYM+uJMVw+sFs7jtS/91OKOT7rzv19ytAAeswtAdWoinVBUVrzmClL/ZoPhcU5RL8w==", "license": "MIT", "dependencies": { "kareem": "3.2.0", @@ -10614,10 +10614,26 @@ "url": "https://opencollective.com/mongoose" } }, + "node_modules/mongoose/node_modules/gcp-metadata": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", + "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/mongoose/node_modules/mongodb": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.0.tgz", - "integrity": "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz", + "integrity": "sha512-067DXiMjcpYQl6bGjWQoTUEE9UoRViTtKFcoqX7z08I+iDZv/emH1g8XEFiO3qiDfXAheT5ozl1VffDTKhIW/w==", "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.3.0", @@ -10855,9 +10871,9 @@ } }, "node_modules/node": { - "version": "25.8.1", - "resolved": "https://registry.npmjs.org/node/-/node-25.8.1.tgz", - "integrity": "sha512-AG1iDDN7Uzku6rFQTjfrqZZRjur8YPkO73PnfDfoj5348DQmFACA8D4X8EhvO8ObIntENK5e/aVviy+sFsAbOA==", + "version": "25.8.2", + "resolved": "https://registry.npmjs.org/node/-/node-25.8.2.tgz", + "integrity": "sha512-ooLo0DWtJsL1qq7mv+bIn4HRjUou8PgFr8DqfXuXQOi/yRYrXX5+j82r4Pa6FJYXMBRmRql8Yjv+lVDdz268bQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -11350,9 +11366,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -11729,14 +11745,14 @@ } }, "node_modules/react-frame-component": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/react-frame-component/-/react-frame-component-5.2.7.tgz", - "integrity": "sha512-ROjHtSLoSVYUBfTieazj/nL8jIX9rZFmHC0yXEU+dx6Y82OcBEGgU9o7VyHMrBFUN9FuQ849MtIPNNLsb4krbg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-frame-component/-/react-frame-component-5.3.1.tgz", + "integrity": "sha512-nvhGilo7MqrYRSd2b1aO+vIxLMRnUM+85gqUxO2sRs1LTDK5BHn4QNkKoFJJAcB9jo2W0GWLTev1anDb2qF28Q==", "license": "MIT", "peerDependencies": { - "prop-types": "^15.5.9", - "react": ">= 16.3", - "react-dom": ">= 16.3" + "prop-types": "^15.8.1 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react": ">= 16.8 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": ">= 16.8 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-is": { @@ -11756,9 +11772,9 @@ } }, "node_modules/react-router": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", - "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.2.tgz", + "integrity": "sha512-tX1Aee+ArlKQP+NIUd7SE6Li+CiGKwQtbS+FfRxPX6Pe4vHOo6nr9d++u5cwg+Z8K/x8tP+7qLmujDtfrAoUJA==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -11961,9 +11977,9 @@ } }, "node_modules/rollup": { - "version": "4.59.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.1.tgz", - "integrity": "sha512-iZKH8BeoCwTCBTZBZWQQMreekd4mdomwdjIQ40GC1oZm6o+8PnNMIxFOiCsGMWeS8iDJ7KZcl7KwmKk/0HOQpA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -11976,31 +11992,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.1", - "@rollup/rollup-android-arm64": "4.59.1", - "@rollup/rollup-darwin-arm64": "4.59.1", - "@rollup/rollup-darwin-x64": "4.59.1", - "@rollup/rollup-freebsd-arm64": "4.59.1", - "@rollup/rollup-freebsd-x64": "4.59.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.1", - "@rollup/rollup-linux-arm-musleabihf": "4.59.1", - "@rollup/rollup-linux-arm64-gnu": "4.59.1", - "@rollup/rollup-linux-arm64-musl": "4.59.1", - "@rollup/rollup-linux-loong64-gnu": "4.59.1", - "@rollup/rollup-linux-loong64-musl": "4.59.1", - "@rollup/rollup-linux-ppc64-gnu": "4.59.1", - "@rollup/rollup-linux-ppc64-musl": "4.59.1", - "@rollup/rollup-linux-riscv64-gnu": "4.59.1", - "@rollup/rollup-linux-riscv64-musl": "4.59.1", - "@rollup/rollup-linux-s390x-gnu": "4.59.1", - "@rollup/rollup-linux-x64-gnu": "4.59.1", - "@rollup/rollup-linux-x64-musl": "4.59.1", - "@rollup/rollup-openbsd-x64": "4.59.1", - "@rollup/rollup-openharmony-arm64": "4.59.1", - "@rollup/rollup-win32-arm64-msvc": "4.59.1", - "@rollup/rollup-win32-ia32-msvc": "4.59.1", - "@rollup/rollup-win32-x64-gnu": "4.59.1", - "@rollup/rollup-win32-x64-msvc": "4.59.1", + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", "fsevents": "~2.3.2" } }, From 456c149f7b9b2fd55e04da0aa3e70fe88bf0854a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Wed, 25 Mar 2026 17:15:23 +0100 Subject: [PATCH 15/56] themes working again --- client/components/codeEditor/codeEditor.jsx | 40 +- .../homebrew/editor/snippetbar/snippetbar.jsx | 11 +- package-lock.json | 500 +++++++++++++++++- package.json | 1 + 4 files changed, 531 insertions(+), 21 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index f6c2014c6..137a12deb 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -16,6 +16,25 @@ import { markdown, markdownLanguage } from "@codemirror/lang-markdown"; import { languages } from "@codemirror/language-data"; import { css } from "@codemirror/lang-css"; import { basicLightHighlightStyle } from "cm6-theme-basic-light"; + +// themes + +import * as themes from "@uiw/codemirror-themes-all"; + +const themeList = Object.entries(themes) + .filter(([name, value]) => Array.isArray(value) && !name.endsWith("Init") && !name.endsWith("Style")) + .map(([name, theme]) => ({ + name, + theme, + })); +console.log(themeList); + +import { Compartment } from "@codemirror/state"; + +const themeCompartment = new Compartment(); + +// custom highlighting + import { HighlightStyle } from "@codemirror/language"; import { syntaxHighlighting } from "@codemirror/language"; @@ -111,7 +130,6 @@ const CodeEditor = forwardRef( const docsRef = useRef({}); const prevTabRef = useRef(tab); - // --- init editor --- const createExtensions = ({ onChange, language, editorTheme }) => { const updateListener = EditorView.updateListener.of((update) => { @@ -154,7 +172,7 @@ const CodeEditor = forwardRef( const languageExtension = language === "css" ? css() : markdown({ base: markdownLanguage, codeLanguages: languages }); - const themeExtension = syntaxHighlighting(basicLightHighlightStyle); + const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; return [ history(), @@ -169,7 +187,9 @@ const CodeEditor = forwardRef( keymap.of(foldKeymap), foldGutter(), lineNumbers(), - themeExtension, + + themeCompartment.of(themeExtension), // 👈 key line + syntaxHighlighting(highlightStyle), customHighlightPlugin, syntaxHighlighting(customHighlightStyle), @@ -232,7 +252,19 @@ const CodeEditor = forwardRef( }); } }, [value]); - // --- exposed API --- + + useEffect(() => { //rebuild theme extension on theme change + const view = viewRef.current; + if (!view) return; + + const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; + + view.dispatch({ + effects: themeCompartment.reconfigure(themeExtension), + }); + }, [editorTheme]); + + useImperativeHandle(ref, () => ({ getValue: () => viewRef.current.state.doc.toString(), diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index 304664ff5..2ecab8834 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -23,7 +23,16 @@ const ThemeSnippets = { V3_Blank : V3_Blank, }; -import EditorThemes from '../../../../build/homebrew/codeMirror/editorThemes.json'; +//import EditorThemes from '../../../../build/homebrew/codeMirror/editorThemes.json'; +import * as themes from '@uiw/codemirror-themes-all'; + +const EditorThemes = Object.entries(themes) + .filter(([name, value]) => + Array.isArray(value) && + !name.endsWith('Init') && + !name.endsWith('Style') + ) + .map(([name]) => name); const execute = function(val, props){ if(_.isFunction(val)) return val(props); diff --git a/package-lock.json b/package-lock.json index 8e92d7ce8..6d8207510 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@googleapis/drive": "^20.1.0", "@lezer/highlight": "^1.2.3", "@sanity/diff-match-patch": "^3.2.0", + "@uiw/codemirror-themes-all": "^4.25.8", "@vitejs/plugin-react": "^5.1.2", "body-parser": "^2.2.0", "classnames": "^2.5.1", @@ -4890,6 +4891,489 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@uiw/codemirror-theme-abcdef": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-abcdef/-/codemirror-theme-abcdef-4.25.8.tgz", + "integrity": "sha512-oT9Ho+X4Ty2qlpYEc7YjHps2rwqvU2FnhZxngbk2uY/P/83kTS1QNq1DygRgxfp1g2aer7ZQk1pguzDS8GSXtA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-abyss": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-abyss/-/codemirror-theme-abyss-4.25.8.tgz", + "integrity": "sha512-CXjPQxSv2rnG778B0S9WbTsxelxvrdRqe54Kbl+Y7U725KCu8TUm/toGSvq8YaV5uzoEcOU8NBCcQBZQKu+EZg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-androidstudio": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-androidstudio/-/codemirror-theme-androidstudio-4.25.8.tgz", + "integrity": "sha512-dKSvP0eF29/7WbIT6vC8Jg625a1+ktUBmDZWQ5HsVhgAk3Jlv3hekKsE4raXH0Hy1Gr+Djz36TGVS7fl/pl+1g==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-andromeda": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-andromeda/-/codemirror-theme-andromeda-4.25.8.tgz", + "integrity": "sha512-qzp0c7+PzaNqtKskvcplkNxNKtB9nRtDTy8kpaKKjcrj6fMpj0hu0yfUxPXhnZCKBwQjtszuCxAcxkuNTkDsFQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-atomone": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-atomone/-/codemirror-theme-atomone-4.25.8.tgz", + "integrity": "sha512-gt3uSsI9KWYRku5rMz6opUOtH73A12jX/T9/C70rf7z5jclP5I2OWU1uGzMzE+Zf9gDou6F/HVJqQmVMcD2fKQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-aura": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-aura/-/codemirror-theme-aura-4.25.8.tgz", + "integrity": "sha512-FwY++XuhQU/sKZN1AKxDvu44JM0dEBSxUIvR1Wr5p8MpNY0ctHC9Y3yb1pEM4Dysmf98zTVg7wajafHhg6HXZg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-basic": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-basic/-/codemirror-theme-basic-4.25.8.tgz", + "integrity": "sha512-EvY7I9RoT8HL1IynuL/nndPmCBcrciq3P0RdBabA9fsKJ8ThviJSWxlYY2NziYs0prlEZdorfC8KHfyfWSu/LQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-bbedit": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-bbedit/-/codemirror-theme-bbedit-4.25.8.tgz", + "integrity": "sha512-uzU7tiMGtf3j7IjMdNRq7Ewi3SBOG8wM5EILE9CHs2CLpitOxlJMxWUOg59folxVS2Vvnd783CqsBXOEeUpZ9A==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-bespin": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-bespin/-/codemirror-theme-bespin-4.25.8.tgz", + "integrity": "sha512-SHAR/qWYxA6MQYytje8Y9t5Fdp/7o3VA+FFvqoKbqTlZArtS8MelVayri5/zCvpdwet2igl+LvbQZ/zK9jlsTA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-console": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-console/-/codemirror-theme-console-4.25.8.tgz", + "integrity": "sha512-zypacwtTAAax6QMst+c2BKtQsj6/+or+8lCmxCWcUMAgD2XXwXZlca0jLfCWwg/2jC26sxMRentPYul5/zQL1w==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + } + }, + "node_modules/@uiw/codemirror-theme-copilot": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-copilot/-/codemirror-theme-copilot-4.25.8.tgz", + "integrity": "sha512-gjszhGBwaecw9fNcqIL5hrEQgpQdfEHdu8MU4VIjg6VGnribdeZYMrqs4DiZj1I0e7dNzlb+4AopLzw5RtmJyA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-darcula": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-darcula/-/codemirror-theme-darcula-4.25.8.tgz", + "integrity": "sha512-oggD67Ag2k4HULP5Hr3GBfoW3KaCprrOgU2TkC3qfLiwRyBdxZ4/8EFPHJFNSiPA5OBPugRn0q1/0eHOf9aDNw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-dracula": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-dracula/-/codemirror-theme-dracula-4.25.8.tgz", + "integrity": "sha512-1AZSkk6FE+bt/6elp4cm02/yxYyZAxPJ6zk9ZC1P68/g4Haf4vWUuuL1xqOHroI1HlSvrM7cmlnxQFYS+JavEg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-duotone": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-duotone/-/codemirror-theme-duotone-4.25.8.tgz", + "integrity": "sha512-f6CkWEil087ocEDlnCjSoOIAVsOF7bAMQRWhVBqE51iAD4DbuAxKzHkvEa58i8PrQPohPHUfUS9yTi0n4DY1WQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-eclipse": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-eclipse/-/codemirror-theme-eclipse-4.25.8.tgz", + "integrity": "sha512-Un6yA6uJ46Y7MKdTtOkekJsMYCjzK3v7EI/+FDvTgIU9bvil9ZIcuGicP+mKioHyfztyx5TBx3HrbhoC7ybkdQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-github": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.25.8.tgz", + "integrity": "sha512-kTZsf2PmOCHBuudB50BX1kAODmErzks6lxxhbHj76KQ+p4eizu0giowV1LoyY/FCLLqRjIe77tE31hvo8agasg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-gruvbox-dark": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-gruvbox-dark/-/codemirror-theme-gruvbox-dark-4.25.8.tgz", + "integrity": "sha512-vo3rbo0R1niTg0BumjG6/vYYyxcpLORmcfqHaA2jj3XFvIZEz6xMQyLFsW1iGzU9kUevIEGvWw2CKebwa0UeDA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-kimbie": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-kimbie/-/codemirror-theme-kimbie-4.25.8.tgz", + "integrity": "sha512-Zy4DSIgDEyW/ctfmnJT6DJ+GPF16a30x4k2i3NaItUuuzA4S64kYlRcekptLUgjRijOFYMZD6IlhrTTM+wRDmA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-material": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-material/-/codemirror-theme-material-4.25.8.tgz", + "integrity": "sha512-VlSkd2ZgQ9YXkW0x3fPssyngrPVhy6XAx84wWO55yU/VQHGIHxTr1/0gF2eEcKOy4M/7aQ4EtYzAVVJlSvrUoA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-monokai": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-monokai/-/codemirror-theme-monokai-4.25.8.tgz", + "integrity": "sha512-Y+Vfa23rj95f6iuD4IGYQuSwjdhqVtfw3vGWHX2bcS7/h/m/GdCsdtmMVwuMy+uCwTJAmOdMxPJh8a5E1f6UaQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-monokai-dimmed": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-monokai-dimmed/-/codemirror-theme-monokai-dimmed-4.25.8.tgz", + "integrity": "sha512-wTRj/OKnULUhjYMvf/HGfuOA1DDBpLIh+vjBP+/SN/3ZCElKgy2AqVXw86D2JYrb3MeEwIITL2s5CovnZfpjaA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-noctis-lilac": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-noctis-lilac/-/codemirror-theme-noctis-lilac-4.25.8.tgz", + "integrity": "sha512-ry0yLvGtDrmctDCrq3Dqwk55bBKLEf1ZUcNjxK8M5HKfkCBga18z5n6cQfqizpoEHaM9uvwzaN+7VhJeXOHyZQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-nord": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-nord/-/codemirror-theme-nord-4.25.8.tgz", + "integrity": "sha512-acHLqao6dT+4xtTUH1YzdUx1NjNsvNSSYYxrB/S5MaCrmFDRSRqWCjXpFdhR0EEidghm9GzQGyzBhIT+99kuMA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-okaidia": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-okaidia/-/codemirror-theme-okaidia-4.25.8.tgz", + "integrity": "sha512-zk1kJ+oRLXBPv6sipQz7ILaQc4LLEvRMUCeBynF1RRubcA2/BTKjKE/++QikSLw4ckmniBCqKSv62PcSZWiThQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-quietlight": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-quietlight/-/codemirror-theme-quietlight-4.25.8.tgz", + "integrity": "sha512-exClw88eGUgTta8rLRlIJA4sjS6VoHKi1yFsIhqOXMrfgFhMtxNvZdIYPzePv+JsGizRJF/8d8KMMCmBl7i6ag==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-red": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-red/-/codemirror-theme-red-4.25.8.tgz", + "integrity": "sha512-dVhxhR+QO//soEVrVrSnK2OSy/7pxu/P77LFLe1/PwWNmFM3u/OHfPmc4NTo2B/xManT0AhIcO47/SRcCcjHRA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-solarized": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-solarized/-/codemirror-theme-solarized-4.25.8.tgz", + "integrity": "sha512-/KWcpUx+hXaiWGWCSzpFQiwZ0gXNKTI009t9/uEYmlPJnh0Mc8GZA6rgJH/8xuvqCaf20hGEZaoJr2l36PO0JA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-sublime": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-sublime/-/codemirror-theme-sublime-4.25.8.tgz", + "integrity": "sha512-OGExIcxt3gYGlPvaXx0DhgVdEFz8nJgMqeVBYJMcB4BlqrYQb+okLyraWoohBvxWf+vj1YbUvyFJi/DDEkIlPQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-tokyo-night": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night/-/codemirror-theme-tokyo-night-4.25.8.tgz", + "integrity": "sha512-jADC+TOj8FOSkvqPpw8CYa0IP07WWf12ASg1vtirC74iKcwhZE4a0XzvRnYAKHhxAzpCmw4vivLGK/KJT/as5A==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-tokyo-night-day": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night-day/-/codemirror-theme-tokyo-night-day-4.25.8.tgz", + "integrity": "sha512-FuNoKoF1DNxoWjLObXD51d30Zb+5c8Ki4Av0RtL7SxgGqbIklGmM9UGVqz0PTE+N7EAQOL/W+HQpyunclMtKCQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-tokyo-night-storm": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night-storm/-/codemirror-theme-tokyo-night-storm-4.25.8.tgz", + "integrity": "sha512-ktBsbSiJI+Lyy/LV5PupNud82A/LU22aTu00QaV8L2cKfv8Expe45UwS4d+O5ijhAZZPLMgnNkkDEinSslA57Q==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-tomorrow-night-blue": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tomorrow-night-blue/-/codemirror-theme-tomorrow-night-blue-4.25.8.tgz", + "integrity": "sha512-qMfgfz7bdZsFqR8Xj905kkKEzqxrMBgyJgpBlhs57Cix84N1bN6GdhRcbqlnJJxwFBmB/uFtC+lyiPysqlIUfA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-vscode": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.25.8.tgz", + "integrity": "sha512-J4fNBFBQK1R1VV4nY+gJ4RRzGDUufe3JDtbeZVehjx34OAbqux8GsD08CTMQReyxJr5DHXcQ/eszQKbESP1J6Q==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-white": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-white/-/codemirror-theme-white-4.25.8.tgz", + "integrity": "sha512-BzECjiT18zY1bqdjOmXE9dTHLfZfVJeQ0DHdN/MhV/GU9n6kHv6qh5zB84uBfYtVTcDRBTw8vNiBwH7NYWuzGg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-xcode": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-xcode/-/codemirror-theme-xcode-4.25.8.tgz", + "integrity": "sha512-x8qlXckT4ZcvSEkxpqwjgu87ZnNBkAGoap03pMNTz/cnRcNY6ZulCXGz7ZQejTOuiWowjmCwjkIGTpEbDj2rzA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-themes": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.8.tgz", + "integrity": "sha512-U6ZSO9A+nsN8zvNddtwhxxpi33J9okb4Li9HdhAItApKjYM22IgC8XSpGfs+ABGfsp1u6NhDSfBR9vAh3oTWXg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/language": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/codemirror-themes-all": { + "version": "4.25.8", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes-all/-/codemirror-themes-all-4.25.8.tgz", + "integrity": "sha512-YacF3Wxkj/T9m8hNjlUfhGK9OzaRsn4wK7G18F5j9HEK02oCw2EnPWxmsrqBY2USounUmYU05OAYtFz1vwrBFw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-theme-abcdef": "4.25.8", + "@uiw/codemirror-theme-abyss": "4.25.8", + "@uiw/codemirror-theme-androidstudio": "4.25.8", + "@uiw/codemirror-theme-andromeda": "4.25.8", + "@uiw/codemirror-theme-atomone": "4.25.8", + "@uiw/codemirror-theme-aura": "4.25.8", + "@uiw/codemirror-theme-basic": "4.25.8", + "@uiw/codemirror-theme-bbedit": "4.25.8", + "@uiw/codemirror-theme-bespin": "4.25.8", + "@uiw/codemirror-theme-console": "4.25.8", + "@uiw/codemirror-theme-copilot": "4.25.8", + "@uiw/codemirror-theme-darcula": "4.25.8", + "@uiw/codemirror-theme-dracula": "4.25.8", + "@uiw/codemirror-theme-duotone": "4.25.8", + "@uiw/codemirror-theme-eclipse": "4.25.8", + "@uiw/codemirror-theme-github": "4.25.8", + "@uiw/codemirror-theme-gruvbox-dark": "4.25.8", + "@uiw/codemirror-theme-kimbie": "4.25.8", + "@uiw/codemirror-theme-material": "4.25.8", + "@uiw/codemirror-theme-monokai": "4.25.8", + "@uiw/codemirror-theme-monokai-dimmed": "4.25.8", + "@uiw/codemirror-theme-noctis-lilac": "4.25.8", + "@uiw/codemirror-theme-nord": "4.25.8", + "@uiw/codemirror-theme-okaidia": "4.25.8", + "@uiw/codemirror-theme-quietlight": "4.25.8", + "@uiw/codemirror-theme-red": "4.25.8", + "@uiw/codemirror-theme-solarized": "4.25.8", + "@uiw/codemirror-theme-sublime": "4.25.8", + "@uiw/codemirror-theme-tokyo-night": "4.25.8", + "@uiw/codemirror-theme-tokyo-night-day": "4.25.8", + "@uiw/codemirror-theme-tokyo-night-storm": "4.25.8", + "@uiw/codemirror-theme-tomorrow-night-blue": "4.25.8", + "@uiw/codemirror-theme-vscode": "4.25.8", + "@uiw/codemirror-theme-white": "4.25.8", + "@uiw/codemirror-theme-xcode": "4.25.8", + "@uiw/codemirror-themes": "4.25.8" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -10614,22 +11098,6 @@ "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose/node_modules/gcp-metadata": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", - "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/mongoose/node_modules/mongodb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz", diff --git a/package.json b/package.json index 1d1668437..70dcbcb4a 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "@googleapis/drive": "^20.1.0", "@lezer/highlight": "^1.2.3", "@sanity/diff-match-patch": "^3.2.0", + "@uiw/codemirror-themes-all": "^4.25.8", "@vitejs/plugin-react": "^5.1.2", "body-parser": "^2.2.0", "classnames": "^2.5.1", From 52f2f532a7bb3fb87e75177a16d2d5eaa4ca09f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Wed, 25 Mar 2026 17:23:38 +0100 Subject: [PATCH 16/56] lint --- client/components/codeEditor/codeEditor.jsx | 201 +++++++++----------- 1 file changed, 94 insertions(+), 107 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 137a12deb..db2a45be9 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -1,9 +1,6 @@ -import "./codeEditor.less"; -import React, { useEffect, useRef, forwardRef, useImperativeHandle } from "react"; +import './codeEditor.less'; +import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react'; -import { EditorState } from "@codemirror/state"; -import { defaultKeymap, history, historyField, undo, redo } from "@codemirror/commands"; -import { foldGutter, foldKeymap } from "@codemirror/language"; import { EditorView, keymap, @@ -11,65 +8,54 @@ import { highlightActiveLineGutter, highlightActiveLine, scrollPastEnd, -} from "@codemirror/view"; -import { markdown, markdownLanguage } from "@codemirror/lang-markdown"; -import { languages } from "@codemirror/language-data"; -import { css } from "@codemirror/lang-css"; -import { basicLightHighlightStyle } from "cm6-theme-basic-light"; + Decoration, + ViewPlugin, +} from '@codemirror/view'; +import { EditorState, Compartment } from '@codemirror/state'; +import { foldGutter, foldKeymap, syntaxHighlighting, HighlightStyle } 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'; -// themes +import { tags } from '@lezer/highlight'; -import * as themes from "@uiw/codemirror-themes-all"; +// ######################### THEMES ############################# -const themeList = Object.entries(themes) - .filter(([name, value]) => Array.isArray(value) && !name.endsWith("Init") && !name.endsWith("Style")) - .map(([name, theme]) => ({ - name, - theme, - })); -console.log(themeList); - -import { Compartment } from "@codemirror/state"; +import * as themes from '@uiw/codemirror-themes-all'; const themeCompartment = new Compartment(); -// custom highlighting - -import { HighlightStyle } from "@codemirror/language"; - -import { syntaxHighlighting } from "@codemirror/language"; -import { tags } from "@lezer/highlight"; +// ######################### CUSTOM HIGHLIGHTS ############################# const highlightStyle = HighlightStyle.define([ { - tag: tags.heading1, - color: "black", - fontSize: "1.75em", - fontWeight: "700", - class: "cm-header cm-header-1", + tag : tags.heading1, + color : 'black', + fontSize : '1.75em', + fontWeight : '700', + class : 'cm-header cm-header-1', }, { - tag: tags.processingInstruction, - color: "blue", + tag : tags.processingInstruction, + color : 'blue', }, // … ]); -/*custom tokens */ -import { Decoration, ViewPlugin, WidgetType } from "@codemirror/view"; -import { tokenizeCustomMarkdown, customTags } from "./customMarkdownGrammar.js"; +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" }, + { 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( @@ -78,7 +64,7 @@ const customHighlightPlugin = ViewPlugin.fromClass( this.decorations = this.buildDecorations(view); } update(update) { - if (update.docChanged) { + if(update.docChanged) { this.decorations = this.buildDecorations(update.view); } } @@ -86,10 +72,10 @@ const customHighlightPlugin = ViewPlugin.fromClass( const decos = []; const tokens = tokenizeCustomMarkdown(view.state.doc.toString()); - tokens.forEach((tok) => { + tokens.forEach((tok)=>{ const line = view.state.doc.line(tok.line + 1); - if (tok.from != null && tok.to != null && tok.from < tok.to) { + 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), @@ -101,76 +87,77 @@ const customHighlightPlugin = ViewPlugin.fromClass( }); // sort by absolute start position - decos.sort((a, b) => a.from - b.from || a.to - b.to); + decos.sort((a, b)=>a.from - b.from || a.to - b.to); return Decoration.set(decos); } }, { - decorations: (v) => v.decorations, + decorations : (v)=>v.decorations, }, ); +// ######################### COMPONENT ############################# + const CodeEditor = forwardRef( ( { - value = "", - onChange = () => {}, - language = "", - tab = "brewText", - editorTheme = "default", + value = '', + onChange = ()=>{}, + language = '', + tab = 'brewText', + editorTheme = 'default', view, style, ...props }, ref, - ) => { + )=>{ const editorRef = useRef(null); const viewRef = useRef(null); const docsRef = useRef({}); const prevTabRef = useRef(tab); - // --- init editor --- - const createExtensions = ({ onChange, language, editorTheme }) => { - const updateListener = EditorView.updateListener.of((update) => { - if (update.docChanged) { + const createExtensions = ({ onChange, language, editorTheme })=>{ + const updateListener = EditorView.updateListener.of((update)=>{ + if(update.docChanged) { onChange(update.state.doc.toString()); } }); - const boldCommand = (view) => { + const boldCommand = (view)=>{ const { from, to } = view.state.selection.main; const selected = view.state.doc.sliceString(from, to); const text = `**${selected}**`; view.dispatch({ - changes: { from, to, insert: text }, - selection: { anchor: from + text.length }, + changes : { from, to, insert: text }, + selection : { anchor: from + text.length }, }); return true; }; - const italicCommand = (view) => { + const italicCommand = (view)=>{ const { from, to } = view.state.selection.main; const selected = view.state.doc.sliceString(from, to); const text = `*${selected}*`; view.dispatch({ - changes: { from, to, insert: text }, - selection: { anchor: from + text.length }, + changes : { from, to, insert: text }, + selection : { anchor: from + text.length }, }); return true; }; const customKeymap = keymap.of([ - { key: "Mod-b", run: boldCommand }, - { key: "Mod-i", run: italicCommand }, + { key: 'Mod-b', run: boldCommand }, + { key: 'Mod-i', run: italicCommand }, ]); const languageExtension = - language === "css" ? css() : markdown({ base: markdownLanguage, codeLanguages: languages }); + language === 'css' ? css() : markdown({ base: markdownLanguage, codeLanguages: languages }); const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; @@ -196,43 +183,43 @@ const CodeEditor = forwardRef( ]; }; - useEffect(() => { - if (!editorRef.current) return; + useEffect(()=>{ + if(!editorRef.current) return; // create initial editor state const state = EditorState.create({ - doc: value, - extensions: createExtensions({ onChange, language, editorTheme }), + doc : value, + extensions : createExtensions({ onChange, language, editorTheme }), }); viewRef.current = new EditorView({ state, - parent: editorRef.current, + parent : editorRef.current, }); // save initial state for current tab docsRef.current[tab] = state; - return () => viewRef.current?.destroy(); + return ()=>viewRef.current?.destroy(); }, []); - useEffect(() => { + useEffect(()=>{ const view = viewRef.current; - if (!view) return; + if(!view) return; const prevTab = prevTabRef.current; - if (prevTab !== tab) { + if(prevTab !== tab) { // save current state docsRef.current[prevTab] = view.state; // restore or create let nextState = docsRef.current[tab]; - if (!nextState) { + if(!nextState) { nextState = EditorState.create({ - doc: value, - extensions: createExtensions({ onChange, language, editorTheme }), + doc : value, + extensions : createExtensions({ onChange, language, editorTheme }), }); } @@ -241,76 +228,76 @@ const CodeEditor = forwardRef( } }, [tab]); - useEffect(() => { + useEffect(()=>{ const view = viewRef.current; - if (!view) return; + if(!view) return; const current = view.state.doc.toString(); - if (value !== current) { + if(value !== current) { view.dispatch({ - changes: { from: 0, to: current.length, insert: value }, + changes : { from: 0, to: current.length, insert: value }, }); } }, [value]); - useEffect(() => { //rebuild theme extension on theme change + useEffect(()=>{ + //rebuild theme extension on theme change const view = viewRef.current; - if (!view) return; + if(!view) return; const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; view.dispatch({ - effects: themeCompartment.reconfigure(themeExtension), + effects : themeCompartment.reconfigure(themeExtension), }); }, [editorTheme]); + useImperativeHandle(ref, ()=>({ + getValue : ()=>viewRef.current.state.doc.toString(), - useImperativeHandle(ref, () => ({ - getValue: () => viewRef.current.state.doc.toString(), - - setValue: (text) => { + setValue : (text)=>{ const view = viewRef.current; view.dispatch({ - changes: { from: 0, to: view.state.doc.length, insert: text }, + changes : { from: 0, to: view.state.doc.length, insert: text }, }); }, - injectText: (text) => { + injectText : (text)=>{ const view = viewRef.current; const { from, to } = view.state.selection.main; view.dispatch({ - changes: { from, to, insert: text }, - selection: { anchor: from + text.length }, + changes : { from, to, insert: text }, + selection : { anchor: from + text.length }, }); view.focus(); }, - getCursorPosition: () => viewRef.current.state.selection.main.head, + getCursorPosition : ()=>viewRef.current.state.selection.main.head, - setCursorPosition: (pos) => { + setCursorPosition : (pos)=>{ viewRef.current.dispatch({ selection: { anchor: pos } }); viewRef.current.focus(); }, - undo: () => undo(viewRef.current), - redo: () => redo(viewRef.current), + undo : ()=>undo(viewRef.current), + redo : ()=>redo(viewRef.current), - historySize: () => { + historySize : ()=>{ const view = viewRef.current; - if (!view) return { done: 0, undone: 0 }; + if(!view) return { done: 0, undone: 0 }; const h = view.state.field(historyField, false); - if (!h) return { done: 0, undone: 0 }; + if(!h) return { done: 0, undone: 0 }; return { done: h.done.length, undone: h.undone.length }; }, - focus: () => viewRef.current.focus(), + focus : ()=>viewRef.current.focus(), })); - return
; + return
; }, ); From b45795fac28f51bec7656643b3006c4182a2ae12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Wed, 25 Mar 2026 21:07:21 +0100 Subject: [PATCH 17/56] this should fold --- client/components/codeEditor/codeEditor.jsx | 55 ++++++++++++++++++++ client/components/codeEditor/codeEditor.less | 29 ----------- client/components/codeEditor/fold-css.js | 44 ---------------- 3 files changed, 55 insertions(+), 73 deletions(-) delete mode 100644 client/components/codeEditor/fold-css.js diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index db2a45be9..ed8fbb6ce 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -10,6 +10,7 @@ import { scrollPastEnd, Decoration, ViewPlugin, + WidgetType, } from '@codemirror/view'; import { EditorState, Compartment } from '@codemirror/state'; import { foldGutter, foldKeymap, syntaxHighlighting, HighlightStyle } from '@codemirror/language'; @@ -97,6 +98,59 @@ const customHighlightPlugin = ViewPlugin.fromClass( }, ); +// ######################### FOLDING ############################### + + +import { foldService } from '@codemirror/language'; + +class FoldPreviewWidget extends WidgetType { + constructor(text) { + super(); + this.text = text; + } + toDOM() { + console.log(this.text); + const span = document.createElement('span'); + span.className = 'cm-fold-placeholder'; + span.textContent = this.text; + return span; + } +} + +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; + + let preview = ''; + for (let i = startLine.number; i <= endLine; i++) { + const text = doc.line(i).text.trim(); + if(text.length > 0) { + preview = text; + break; + } + } + + if(!preview) preview = `Lines ${startLine.number}-${endLine}`; + + preview = preview.replace('{', '').trim(); + if(preview.length > 50) preview = `${preview.slice(0, 50)}...`; + preview = `↤ ${preview} ↦`; + + return { from: startLine.from, to: doc.line(endLine).to, placeholder: new FoldPreviewWidget(preview) }; +}); + // ######################### COMPONENT ############################# const CodeEditor = forwardRef( @@ -174,6 +228,7 @@ const CodeEditor = forwardRef( keymap.of(foldKeymap), foldGutter(), lineNumbers(), + homebreweryFold, themeCompartment.of(themeExtension), // 👈 key line diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less index 7dc3bf0fd..f28f66140 100644 --- a/client/components/codeEditor/codeEditor.less +++ b/client/components/codeEditor/codeEditor.less @@ -24,35 +24,6 @@ font-size: 16px; } - - /* Line numbers and gutters */ - .cm-gutters { - background-color: #f0f0f0; - color: #555; - border-right: 1px solid #ddd; - } - - /* Folding gutter */ - .cm-foldGutter { - cursor: pointer; - color: grey; - font-weight: 600; - transition: background 0.1s; - - &:hover { - background: #dddddd; - } - } - - /* Active line */ - .cm-activeLine { - background-color: #f5f5f5; - } - - .cm-activeLineGutter { - background-color: #e0e0e0; - } - /* Flash animation for source moves */ .sourceMoveFlash .cm-line { animation-name: sourceMoveAnimation; diff --git a/client/components/codeEditor/fold-css.js b/client/components/codeEditor/fold-css.js deleted file mode 100644 index 06bfd96a4..000000000 --- a/client/components/codeEditor/fold-css.js +++ /dev/null @@ -1,44 +0,0 @@ -export default { - registerHomebreweryHelper : function(CodeMirror) { - CodeMirror.registerHelper('fold', 'homebrewerycss', function(cm, start) { - - // BRACE FOLDING - const startMatcher = /\{[ \t]*$/; - const endMatcher = /\}[ \t]*$/; - const activeLine = cm.getLine(start.line); - - - if(activeLine.match(startMatcher)) { - const lastLineNo = cm.lastLine(); - let end = start.line + 1; - let braceCount = 1; - - while (end < lastLineNo) { - const curLine = cm.getLine(end); - if(curLine.match(startMatcher)) braceCount++; - if(curLine.match(endMatcher)) braceCount--; - if(braceCount == 0) break; - ++end; - } - - return { - from : CodeMirror.Pos(start.line, 0), - to : CodeMirror.Pos(end, cm.getLine(end).length) - }; - } - - // @import and data-url folding - const importMatcher = /^@import.*?;/; - const dataURLMatcher = /url\(.*?data\:.*\)/; - - if(activeLine.match(importMatcher) || activeLine.match(dataURLMatcher)) { - return { - from : CodeMirror.Pos(start.line, 0), - to : CodeMirror.Pos(start.line, activeLine.length) - }; - } - - return null; - }); - } -}; From ad50be45c69a67ad0bb5068a31b27748ddac16fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 26 Mar 2026 12:42:26 +0100 Subject: [PATCH 18/56] custom folding finally --- client/components/codeEditor/codeEditor.jsx | 70 ++++++++++++++------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index ed8fbb6ce..5d119e629 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -100,23 +100,8 @@ const customHighlightPlugin = ViewPlugin.fromClass( // ######################### FOLDING ############################### - import { foldService } from '@codemirror/language'; -class FoldPreviewWidget extends WidgetType { - constructor(text) { - super(); - this.text = text; - } - toDOM() { - console.log(this.text); - const span = document.createElement('span'); - span.className = 'cm-fold-placeholder'; - span.textContent = this.text; - return span; - } -} - const homebreweryFold = foldService.of((state, lineStart)=>{ const doc = state.doc; const matcher = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; @@ -133,24 +118,58 @@ const homebreweryFold = foldService.of((state, lineStart)=>{ 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.number; i <= endLine; i++) { + + for (let i = startLine + 1; i <= endLine; i++) { const text = doc.line(i).text.trim(); - if(text.length > 0) { + if (text.length > 0) { preview = text; break; } } - if(!preview) preview = `Lines ${startLine.number}-${endLine}`; + if (!preview) preview = `Lines ${startLine}-${endLine}`; preview = preview.replace('{', '').trim(); - if(preview.length > 50) preview = `${preview.slice(0, 50)}...`; - preview = `↤ ${preview} ↦`; + if (preview.length > 50) preview = `${preview.slice(0, 50)}...`; - return { from: startLine.from, to: doc.line(endLine).to, placeholder: new FoldPreviewWidget(preview) }; + 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 ############################# const CodeEditor = forwardRef( @@ -225,11 +244,16 @@ const CodeEditor = forwardRef( languageExtension, highlightActiveLine(), highlightActiveLineGutter(), - keymap.of(foldKeymap), - foldGutter(), + lineNumbers(), homebreweryFold, + hbFolding, + keymap.of(foldKeymap), + foldGutter({ + openText : '▾', + closedText : '▸' + }), themeCompartment.of(themeExtension), // 👈 key line syntaxHighlighting(highlightStyle), 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 19/56] 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; - }); - } -}; From b0ebeaffb9ea42d6e8824ff1bb91a586db2c5831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 26 Mar 2026 14:22:39 +0100 Subject: [PATCH 20/56] syntax highligting for legacy as well --- client/components/codeEditor/codeEditor.jsx | 77 ++++++++++++++++++- client/components/codeEditor/customFolding.js | 1 - .../components/codeEditor/customHighlight.js | 55 ++++--------- .../codeEditor/legacyCustomHighlight.js | 34 ++++++++ client/homebrew/editor/editor.jsx | 3 + 5 files changed, 125 insertions(+), 45 deletions(-) create mode 100644 client/components/codeEditor/legacyCustomHighlight.js diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 7b718a1f4..2d1778811 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -8,6 +8,8 @@ import { highlightActiveLineGutter, highlightActiveLine, scrollPastEnd, + Decoration, + ViewPlugin, } from '@codemirror/view'; import { EditorState, Compartment } from '@codemirror/state'; import { foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language'; @@ -19,8 +21,50 @@ import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import * as themes from '@uiw/codemirror-themes-all'; const themeCompartment = new Compartment(); -import { customHighlightPlugin, customHighlightStyle } from './customHighlight.js'; +const highlightCompartment = new Compartment(); + import { homebreweryFold, hbFolding } from './customFolding.js'; +import { customHighlightStyle, tokenizeCustomMarkdown } from './customHighlight.js'; +import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.js'; //only makes highlight for + +const createHighlightPlugin = (renderer)=>{ + console.log(renderer); + const tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; + + return 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 = tokenize(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) { + decos.push( + Decoration.mark({ class: `cm-${tok.type}` }).range(line.from + tok.from, line.from + tok.to) + ); + } else { + decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); + } + }); + + decos.sort((a, b)=>a.from - b.from || a.to - b.to); + return Decoration.set(decos); + } + }, + { decorations: (v)=>v.decorations } + ); +}; + const CodeEditor = forwardRef( ( @@ -32,6 +76,7 @@ const CodeEditor = forwardRef( editorTheme = 'default', view, style, + renderer, ...props }, ref, @@ -79,11 +124,24 @@ const CodeEditor = forwardRef( { key: 'Mod-i', run: italicCommand }, ]); + const highlightExtension = renderer === 'V3' + ? syntaxHighlighting(customHighlightStyle) + : syntaxHighlighting(legacyCustomHighlightStyle); + + const customHighlightPlugin = createHighlightPlugin(renderer); + + const combinedHighlight = [ + customHighlightPlugin, + highlightExtension, + ]; + + const languageExtension = language === 'css' ? css() : markdown({ base: markdownLanguage, codeLanguages: languages }); const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; + return [ history(), keymap.of(defaultKeymap), @@ -107,7 +165,7 @@ const CodeEditor = forwardRef( highlightActiveLine(), highlightActiveLineGutter(), customHighlightPlugin, - syntaxHighlighting(customHighlightStyle), + highlightCompartment.of(combinedHighlight), ]; }; @@ -175,6 +233,21 @@ const CodeEditor = forwardRef( effects : themeCompartment.reconfigure(themeExtension), }); }, [editorTheme]); + useEffect(()=>{ + const view = viewRef.current; + if(!view) return; + + const highlightExtension = + renderer === 'V3' + ? syntaxHighlighting(customHighlightStyle) + : syntaxHighlighting(legacyCustomHighlightStyle); + + const customHighlightPlugin = createHighlightPlugin(renderer); + + view.dispatch({ + effects : highlightCompartment.reconfigure([customHighlightPlugin, highlightExtension]), + }); + }, [renderer]); useImperativeHandle(ref, ()=>({ getValue : ()=>viewRef.current.state.doc.toString(), diff --git a/client/components/codeEditor/customFolding.js b/client/components/codeEditor/customFolding.js index 778901371..cf6bea5c3 100644 --- a/client/components/codeEditor/customFolding.js +++ b/client/components/codeEditor/customFolding.js @@ -31,7 +31,6 @@ export const homebreweryFold = foldService.of((state, lineStart)=>{ if(endLine === startLine.number) return null; const widgetObject = { from: startLine.from, to: doc.line(endLine).to }; - console.log(widgetObject); return widgetObject; }); diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index 29df5576d..fda24a7ba 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -1,9 +1,5 @@ import { HighlightStyle } from '@codemirror/language'; import { tags } from '@lezer/highlight'; -import { - Decoration, - ViewPlugin, -} from '@codemirror/view'; // Making the tokens const customTags = { @@ -23,7 +19,7 @@ const customTags = { definitionColon : 'definitionColon', // .cm-definitionColon }; -function tokenizeCustomMarkdown(text) { +export function tokenizeCustomMarkdown(text) { const tokens = []; const lines = text.split('\n'); @@ -39,7 +35,18 @@ function tokenizeCustomMarkdown(text) { if(/\\snippet/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetBreak }); // --- Emoji --- - if(/:\w+?:/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.emoji }); + if(/:.\w+?:/.test(lineText)) { + const emojiRegex = /(:\w+?:)/g; + let match; + while ((match = emojiRegex.exec(lineText)) !== null) { + tokens.push({ + line : lineNumber, + type : customTags.emoji, + from : match.index, + to : match.index + match[0].length, + }); + } + } // --- Superscript / Subscript --- if(/\^/.test(lineText)) { @@ -243,41 +250,5 @@ export const customHighlightStyle = HighlightStyle.define([ { 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/legacyCustomHighlight.js b/client/components/codeEditor/legacyCustomHighlight.js new file mode 100644 index 000000000..2777b3102 --- /dev/null +++ b/client/components/codeEditor/legacyCustomHighlight.js @@ -0,0 +1,34 @@ +import { HighlightStyle } from '@codemirror/language'; +import { tags } from '@lezer/highlight'; + +// Making the tokens +const customTags = { + pageLine : 'pageLine', // .cm-pageLine + snippetLine : 'snippetLine', // .cm-snippetLine +}; + +export function legacyTokenizeCustomMarkdown(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 }); + }); + + return tokens; +} + +export const legacyCustomHighlightStyle = 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.snippetLine, class: 'cm-snippetLine', color: '#0af' }, +]); + + diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 62cea09eb..535e3cbf5 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -447,6 +447,7 @@ const Editor = createReactClass({ value={this.props.brew.text} onChange={this.props.onBrewChange('text')} editorTheme={this.state.editorTheme} + renderer={this.props.brew.renderer} rerenderParent={this.rerenderParent} style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }} /> ; @@ -462,6 +463,7 @@ const Editor = createReactClass({ onChange={this.props.onBrewChange('style')} enableFolding={true} editorTheme={this.state.editorTheme} + renderer={this.props.brew.renderer} rerenderParent={this.rerenderParent} style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }} /> ; @@ -492,6 +494,7 @@ const Editor = createReactClass({ onChange={this.props.onBrewChange('snippets')} enableFolding={true} editorTheme={this.state.editorTheme} + renderer={this.props.brew.renderer} rerenderParent={this.rerenderParent} style={{ height: `calc(100% -${this.state.snippetBarHeight}px)` }} /> ; From 3b9eae2e4f62fe1bc1208f8909abb8339ffae3b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 26 Mar 2026 14:42:56 +0100 Subject: [PATCH 21/56] small fixes --- client/components/codeEditor/codeEditor.jsx | 11 +++----- .../components/codeEditor/customHighlight.js | 26 ++++++++----------- client/homebrew/editor/editor.less | 3 ++- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 2d1778811..5e118be3b 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -28,7 +28,6 @@ import { customHighlightStyle, tokenizeCustomMarkdown } from './customHighlight. import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.js'; //only makes highlight for const createHighlightPlugin = (renderer)=>{ - console.log(renderer); const tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; return ViewPlugin.fromClass( @@ -164,7 +163,6 @@ const CodeEditor = forwardRef( highlightActiveLine(), highlightActiveLineGutter(), - customHighlightPlugin, highlightCompartment.of(combinedHighlight), ]; }; @@ -237,10 +235,9 @@ const CodeEditor = forwardRef( const view = viewRef.current; if(!view) return; - const highlightExtension = - renderer === 'V3' - ? syntaxHighlighting(customHighlightStyle) - : syntaxHighlighting(legacyCustomHighlightStyle); + const highlightExtension =renderer === 'V3' + ? syntaxHighlighting(customHighlightStyle) + : syntaxHighlighting(legacyCustomHighlightStyle); const customHighlightPlugin = createHighlightPlugin(renderer); @@ -294,7 +291,7 @@ const CodeEditor = forwardRef( focus : ()=>viewRef.current.focus(), })); - return
; + return
; }, ); diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index fda24a7ba..88a219b30 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -3,20 +3,19 @@ import { tags } from '@lezer/highlight'; // 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 + pageLine : 'pageLine', // .cm-pageLine + snippetLine : 'snippetLine', // .cm-snippetLine + columnSplit : 'columnSplit', // .cm-columnSplit + 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 + definitionColon : 'definitionColon',// .cm-definitionColon }; export function tokenizeCustomMarkdown(text) { @@ -32,7 +31,6 @@ export function tokenizeCustomMarkdown(text) { 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)) { @@ -230,8 +228,6 @@ export function tokenizeCustomMarkdown(text) { if(match) endCh = match.index + match[0].length; tokens.push({ line: lineNumber, type: customTags.block }); } - - }); return tokens; @@ -241,7 +237,7 @@ 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.snippetLine, class: 'cm-snippetLine', 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' }, diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 9ac16464b..34358e5f8 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -10,10 +10,11 @@ .codeEditor { height : calc(100% - 25px); .cm-editor { height : 100%; } - .cm-pageLine, .cm-snippetLine { + .cm-pageLine, &.brewSnippets .cm-snippetLine { background : #33333328; border-top : #333399 solid 1px; } + .cm-editor-page-count { float : right; color : grey; From 3a671cf48ff77be727144af07ac8825d3b11c012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 26 Mar 2026 17:08:01 +0100 Subject: [PATCH 22/56] custom key map --- client/components/codeEditor/codeEditor.jsx | 39 +--- client/components/codeEditor/customKeyMap.js | 224 +++++++++++++++++++ 2 files changed, 226 insertions(+), 37 deletions(-) create mode 100644 client/components/codeEditor/customKeyMap.js diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 5e118be3b..188bb020a 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -20,9 +20,9 @@ import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import * as themes from '@uiw/codemirror-themes-all'; const themeCompartment = new Compartment(); - const highlightCompartment = new Compartment(); +import { customKeymap } from './customKeyMap.js'; import { homebreweryFold, hbFolding } from './customFolding.js'; import { customHighlightStyle, tokenizeCustomMarkdown } from './customHighlight.js'; import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.js'; //only makes highlight for @@ -64,7 +64,6 @@ const createHighlightPlugin = (renderer)=>{ ); }; - const CodeEditor = forwardRef( ( { @@ -92,37 +91,6 @@ const CodeEditor = forwardRef( } }); - const boldCommand = (view)=>{ - const { from, to } = view.state.selection.main; - const selected = view.state.doc.sliceString(from, to); - const text = `**${selected}**`; - - view.dispatch({ - changes : { from, to, insert: text }, - selection : { anchor: from + text.length }, - }); - - return true; - }; - - const italicCommand = (view)=>{ - const { from, to } = view.state.selection.main; - const selected = view.state.doc.sliceString(from, to); - const text = `*${selected}*`; - - view.dispatch({ - changes : { from, to, insert: text }, - selection : { anchor: from + text.length }, - }); - - return true; - }; - - const customKeymap = keymap.of([ - { key: 'Mod-b', run: boldCommand }, - { key: 'Mod-i', run: italicCommand }, - ]); - const highlightExtension = renderer === 'V3' ? syntaxHighlighting(customHighlightStyle) : syntaxHighlighting(legacyCustomHighlightStyle); @@ -134,13 +102,10 @@ const CodeEditor = forwardRef( highlightExtension, ]; - - const languageExtension = - language === 'css' ? css() : markdown({ base: markdownLanguage, codeLanguages: languages }); + const languageExtension = language === 'css' ? css() : markdown({ base: markdownLanguage, codeLanguages: languages }); const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; - return [ history(), keymap.of(defaultKeymap), diff --git a/client/components/codeEditor/customKeyMap.js b/client/components/codeEditor/customKeyMap.js new file mode 100644 index 000000000..9be1ac9e5 --- /dev/null +++ b/client/components/codeEditor/customKeyMap.js @@ -0,0 +1,224 @@ +import { keymap } from '@codemirror/view'; + +const indentMore = (view) => { + const { from, to } = view.state.selection.main; + const lines = []; + for (let l = view.state.doc.lineAt(from).number; l <= view.state.doc.lineAt(to).number; l++) { + const line = view.state.doc.line(l); + lines.push({ from: line.from, to: line.from, insert: ' ' }); // 2 spaces for tab + } + view.dispatch({ changes: lines }); + return true; +}; + +const indentLess = (view) => { + const { from, to } = view.state.selection.main; + const lines = []; + for (let l = view.state.doc.lineAt(from).number; l <= view.state.doc.lineAt(to).number; l++) { + const line = view.state.doc.line(l); + const match = line.text.match(/^ {1,2}/); // match up to 2 spaces + if (match) { + lines.push({ from: line.from, to: line.from + match[0].length, insert: '' }); + } + } + if (lines.length > 0) view.dispatch({ changes: lines }); + return true; +}; + +const makeBold = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = selected.startsWith('**') && selected.endsWith('**') + ? selected.slice(2, -2) + : `**${selected}**`; + view.dispatch({ + changes : { from, to, insert: text }, + selection : { anchor: from + text.length }, + }); + return true; +}; + +const makeItalic = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = selected.startsWith('*') && selected.endsWith('*') + ? selected.slice(1, -1) + : `*${selected}*`; + view.dispatch({ + changes : { from, to, insert: text }, + selection : { anchor: from + text.length }, + }); + return true; +}; + +const makeUnderline = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = selected.startsWith('') && selected.endsWith('') + ? selected.slice(3, -4) + : `${selected}`; + view.dispatch({ + changes : { from, to, insert: text }, + selection : { anchor: from + text.length }, + }); + return true; +}; + +const makeSuper = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = selected.startsWith('^') && selected.endsWith('^') + ? selected.slice(1, -1) + : `^${selected}^`; + view.dispatch({ + changes : { from, to, insert: text }, + selection : { anchor: from + text.length }, + }); + return true; +}; + +const makeSub = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = selected.startsWith('^^') && selected.endsWith('^^') + ? selected.slice(2, -2) + : `^^${selected}^^`; + view.dispatch({ + changes : { from, to, insert: text }, + selection : { anchor: from + text.length }, + }); + return true; +}; + +const makeNbsp = (view)=>{ + const { from, to } = view.state.selection.main; + view.dispatch({ changes: { from, to, insert: ' ' } }); + return true; +}; + +const makeSpace = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const match = selected.match(/^{{width:(\d+)% }}$/); + let newText = '{{width:10% }}'; + if(match) { + const percent = Math.min(parseInt(match[1], 10) + 10, 100); + newText = `{{width:${percent}% }}`; + } + view.dispatch({ changes: { from, to, insert: newText } }); + return true; +}; + +const removeSpace = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const match = selected.match(/^{{width:(\d+)% }}$/); + if(match) { + const percent = parseInt(match[1], 10) - 10; + const newText = percent > 0 ? `{{width:${percent}% }}` : ''; + view.dispatch({ changes: { from, to, insert: newText } }); + } + return true; +}; + +const makeSpan = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = selected.startsWith('{{') && selected.endsWith('}}') + ? selected.slice(2, -2) + : `{{${selected}}}`; + view.dispatch({ changes: { from, to, insert: text } }); + return true; +}; + +const makeDiv = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = selected.startsWith('{{') && selected.endsWith('}}') + ? selected.slice(2, -2) + : `{{\n${selected}\n}}`; + view.dispatch({ changes: { from, to, insert: text } }); + return true; +}; + +const makeComment = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const isHtmlComment = selected.startsWith(''); + const text = isHtmlComment + ? selected.slice(4, -3) + : ``; + view.dispatch({ changes: { from, to, insert: text } }); + return true; +}; + +const makeLink = (view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to).trim(); + const isLink = /^\[(.*)\]\((.*)\)$/.exec(selected); + const text = isLink ? `${isLink[1]} ${isLink[2]}` : `[${selected || 'alt text'}](url)`; + view.dispatch({ changes: { from, to, insert: text } }); + return true; +}; + +const makeList = (type)=>(view)=>{ + const { from, to } = view.state.selection.main; + const lines = []; + for (let l = from; l <= to; l++) { + const lineText = view.state.doc.line(l + 1).text; + lines.push(lineText); + } + const joined = lines.join('\n'); + let newText; + if(type === 'UL') newText = joined.replace(/^/gm, '- '); + else newText = joined.replace(/^/gm, (m, i)=>`${i + 1}. `); + view.dispatch({ changes: { from, to, insert: newText } }); + return true; +}; + +const makeHeader = (level)=>(view)=>{ + const { from, to } = view.state.selection.main; + const selected = view.state.doc.sliceString(from, to); + const text = `${'#'.repeat(level)} ${selected}`; + view.dispatch({ changes: { from, to, insert: text } }); + return true; +}; + +const newColumn = (view)=>{ + const { from, to } = view.state.selection.main; + view.dispatch({ changes: { from, to, insert: '\n\\column\n\n' } }); + return true; +}; + +const newPage = (view)=>{ + const { from, to } = view.state.selection.main; + view.dispatch({ changes: { from, to, insert: '\n\\page\n\n' } }); + return true; +}; + +export const customKeymap = keymap.of([ + { key: 'Tab', run: indentMore }, + { key: 'Shift-Tab', run: indentLess }, + { key: 'Mod-b', run: makeBold }, + { key: 'Mod-i', run: makeItalic }, + { key: 'Mod-u', run: makeUnderline }, + { key: 'Shift-Mod-=', run: makeSuper }, + { key: 'Mod-=', run: makeSub }, + { key: 'Mod-.', run: makeNbsp }, + { key: 'Shift-Mod-.', run: makeSpace }, + { key: 'Shift-Mod-,', run: removeSpace }, + { key: 'Mod-m', run: makeSpan }, + { key: 'Shift-Mod-m', run: makeDiv }, + { key: 'Mod-/', run: makeComment }, + { key: 'Mod-k', run: makeLink }, + { key: 'Mod-l', run: makeList('UL') }, + { key: 'Shift-Mod-l', run: makeList('OL') }, + { key: 'Shift-Mod-1', run: makeHeader(1) }, + { key: 'Shift-Mod-2', run: makeHeader(2) }, + { key: 'Shift-Mod-3', run: makeHeader(3) }, + { key: 'Shift-Mod-4', run: makeHeader(4) }, + { key: 'Shift-Mod-5', run: makeHeader(5) }, + { key: 'Shift-Mod-6', run: makeHeader(6) }, + { key: 'Shift-Mod-Enter', run: newColumn }, + { key: 'Mod-Enter', run: newPage }, +]); From 96e1d6e100a199795989efd85b4fffc1b74732a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 26 Mar 2026 17:19:54 +0100 Subject: [PATCH 23/56] update key map --- client/components/codeEditor/customKeyMap.js | 46 ++++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/client/components/codeEditor/customKeyMap.js b/client/components/codeEditor/customKeyMap.js index 9be1ac9e5..07dae0e6d 100644 --- a/client/components/codeEditor/customKeyMap.js +++ b/client/components/codeEditor/customKeyMap.js @@ -1,28 +1,28 @@ import { keymap } from '@codemirror/view'; -const indentMore = (view) => { - const { from, to } = view.state.selection.main; - const lines = []; - for (let l = view.state.doc.lineAt(from).number; l <= view.state.doc.lineAt(to).number; l++) { - const line = view.state.doc.line(l); - lines.push({ from: line.from, to: line.from, insert: ' ' }); // 2 spaces for tab - } - view.dispatch({ changes: lines }); - return true; +const indentMore = (view)=>{ + const { from, to } = view.state.selection.main; + const lines = []; + for (let l = view.state.doc.lineAt(from).number; l <= view.state.doc.lineAt(to).number; l++) { + const line = view.state.doc.line(l); + lines.push({ from: line.from, to: line.from, insert: ' ' }); // 2 spaces for tab + } + view.dispatch({ changes: lines }); + return true; }; -const indentLess = (view) => { - const { from, to } = view.state.selection.main; - const lines = []; - for (let l = view.state.doc.lineAt(from).number; l <= view.state.doc.lineAt(to).number; l++) { - const line = view.state.doc.line(l); - const match = line.text.match(/^ {1,2}/); // match up to 2 spaces - if (match) { - lines.push({ from: line.from, to: line.from + match[0].length, insert: '' }); - } - } - if (lines.length > 0) view.dispatch({ changes: lines }); - return true; +const indentLess = (view)=>{ + const { from, to } = view.state.selection.main; + const lines = []; + for (let l = view.state.doc.lineAt(from).number; l <= view.state.doc.lineAt(to).number; l++) { + const line = view.state.doc.line(l); + const match = line.text.match(/^ {1,2}/); // match up to 2 spaces + if(match) { + lines.push({ from: line.from, to: line.from + match[0].length, insert: '' }); + } + } + if(lines.length > 0) view.dispatch({ changes: lines }); + return true; }; const makeBold = (view)=>{ @@ -197,8 +197,8 @@ const newPage = (view)=>{ }; export const customKeymap = keymap.of([ - { key: 'Tab', run: indentMore }, - { key: 'Shift-Tab', run: indentLess }, + { key: 'Shift-Tab', run: indentMore }, + { key: 'Mod-Shift-Tab', run: indentLess }, { key: 'Mod-b', run: makeBold }, { key: 'Mod-i', run: makeItalic }, { key: 'Mod-u', run: makeUnderline }, From 3992d71910d03c449ac738a4de406712d0be3853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 26 Mar 2026 17:49:33 +0100 Subject: [PATCH 24/56] page count widget --- client/components/codeEditor/codeEditor.jsx | 27 ++++++++++++++++++++- client/homebrew/editor/editor.less | 4 +-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 188bb020a..749793bc3 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -10,6 +10,7 @@ import { scrollPastEnd, Decoration, ViewPlugin, + WidgetType } from '@codemirror/view'; import { EditorState, Compartment } from '@codemirror/state'; import { foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language'; @@ -27,9 +28,24 @@ import { homebreweryFold, hbFolding } from './customFolding.js'; import { customHighlightStyle, tokenizeCustomMarkdown } from './customHighlight.js'; import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.js'; //only makes highlight for -const createHighlightPlugin = (renderer)=>{ +const createHighlightPlugin = (renderer, tab)=>{ const tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; +class countWidget extends WidgetType { + constructor(count) { + super(); + this.count = count; + } + toDOM() { + const span = document.createElement("span"); + span.className = "cm-page-count"; + span.textContent = this.count; + span.style.color = "#989898"; + return span; + } + ignoreEvent() { return true; } +} + return ViewPlugin.fromClass( class { constructor(view) { @@ -44,15 +60,24 @@ const createHighlightPlugin = (renderer)=>{ const decos = []; const tokens = tokenize(view.state.doc.toString()); + let pageCount = 1; tokens.forEach((tok)=>{ const line = view.state.doc.line(tok.line + 1); if(tok.from != null && tok.to != null && tok.from < tok.to) { + decos.push( Decoration.mark({ class: `cm-${tok.type}` }).range(line.from + tok.from, line.from + tok.to) + ); + } else { decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); + if(tok.type === 'pageLine') { + pageCount++; + line.from === 0 && pageCount--; + decos.push(Decoration.widget({ widget: new countWidget(pageCount), side: 2 }).range(line.to)); + } } }); diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 34358e5f8..ce583fc8b 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -15,11 +15,11 @@ border-top : #333399 solid 1px; } - .cm-editor-page-count { + .cm-page-count { float : right; color : grey; } - .cm-editor-snippet-count { + .cm-snippet-count { float : right; color : grey; } From d4c1eba3f389f62e960b0f231a59c8695e925666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 26 Mar 2026 23:44:49 +0100 Subject: [PATCH 25/56] emoji autocomplete --- .../codeEditor/autocompleteEmoji.js | 118 ++++++++---------- client/components/codeEditor/codeEditor.jsx | 30 ++--- client/homebrew/editor/editor.less | 24 +++- package-lock.json | 1 + package.json | 1 + 5 files changed, 90 insertions(+), 84 deletions(-) diff --git a/client/components/codeEditor/autocompleteEmoji.js b/client/components/codeEditor/autocompleteEmoji.js index fc64e7bbd..06bbcc577 100644 --- a/client/components/codeEditor/autocompleteEmoji.js +++ b/client/components/codeEditor/autocompleteEmoji.js @@ -1,3 +1,5 @@ +import { autocompletion } from '@codemirror/autocomplete'; + import diceFont from '@themes/fonts/iconFonts/diceFont.js'; import elderberryInn from '@themes/fonts/iconFonts/elderberryInn.js'; import fontAwesome from '@themes/fonts/iconFonts/fontAwesome.js'; @@ -10,75 +12,61 @@ const emojis = { ...gameIcons }; -const showAutocompleteEmoji = function(CodeMirror, editor) { - CodeMirror.commands.autocomplete = function(editor) { - editor.showHint({ - completeSingle : false, - hint : function(editor) { - const cursor = editor.getCursor(); - const line = cursor.line; - const lineContent = editor.getLine(line); - const start = lineContent.lastIndexOf(':', cursor.ch - 1) + 1; - const end = cursor.ch; - const currentWord = lineContent.slice(start, end); +const emojiCompletionList = (context)=>{ + const word = context.matchBefore(/:[^\s:]*/); + if(!word) return null; + const line = context.state.doc.lineAt(context.pos); + const textToCursor = line.text.slice(0, context.pos - line.from); - const list = Object.keys(emojis).filter(function(emoji) { - return emoji.toLowerCase().indexOf(currentWord.toLowerCase()) >= 0; - }).sort((a, b)=>{ - const lowerA = a.replace(/\d+/g, function(match) { // Temporarily convert any numbers in emoji string - return match.padStart(4, '0'); // to 4-digits, left-padded with 0's, to aid in - }).toLowerCase(); // sorting numbers, i.e., "d6, d10, d20", not "d10, d20, d6" - const lowerB = b.replace(/\d+/g, function(match) { // Also make lowercase for case-insensitive alpha sorting - return match.padStart(4, '0'); - }).toLowerCase(); + if(textToCursor.includes('{')) { + const curlyToCursor = textToCursor.slice(textToCursor.indexOf('{')); + const curlySpanRegex = /{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1$/g; + if(curlySpanRegex.test(curlyToCursor)) return null; + } - if(lowerA < lowerB) - return -1; - return 1; - }).map(function(emoji) { - return { - text : `${emoji}:`, // Text to output to editor when option is selected - render : function(element, self, data) { // How to display the option in the dropdown - const div = document.createElement('div'); - div.innerHTML = ` ${emoji}`; - element.appendChild(div); - } - }; - }); + const currentWord = word.text.slice(1); // remove ':' - return { - list : list.length ? list : [], - from : CodeMirror.Pos(line, start), - to : CodeMirror.Pos(line, end) - }; - } - }); + const options = Object.keys(emojis) + .filter((e)=>e.toLowerCase().includes(currentWord.toLowerCase())) + .sort((a, b)=>{ + const normalize = (str)=>str.replace(/\d+/g, (m)=>m.padStart(4, '0')).toLowerCase(); + return normalize(a) < normalize(b) ? -1 : 1; + }) + .map((e)=>({ + label : e, + apply : `${e}:`, + type : 'text', + info : ()=>{ + const div = document.createElement('div'); + div.innerHTML = ` ${e}`; + return div; + } + })); + + return { + from : word.from + 1, + options, }; - - editor.on('inputRead', function(instance, change) { - const cursor = editor.getCursor(); - const line = editor.getLine(cursor.line); - - // Get the text from the start of the line to the cursor - const textToCursor = line.slice(0, cursor.ch); - - // Do not autosuggest emojis in curly span/div/injector properties - if(line.includes('{')) { - const curlyToCursor = textToCursor.slice(textToCursor.indexOf(`{`)); - const curlySpanRegex = /{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1$/g; - - if(curlySpanRegex.test(curlyToCursor)) - return; - } - - // Check if the text ends with ':xyz' - if(/:[^\s:]+$/.test(textToCursor)) { - CodeMirror.commands.autocomplete(editor); - } - }); }; -export default { - showAutocompleteEmoji -}; \ No newline at end of file +export const autocompleteEmoji = autocompletion({ + override : [emojiCompletionList], + activateOnTyping : true, + addToOptions : [ + { + render(completion) { + const e = completion.label; + + const icon = document.createElement('i'); + icon.className = `emojiPreview ${emojis[e]}`; + + // append directly to a DocumentFragment to return a single node + const fragment = document.createDocumentFragment(); + fragment.appendChild(icon); + + return fragment; + } + } + ] +}); \ No newline at end of file diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 749793bc3..6b4506b01 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -18,6 +18,7 @@ import { defaultKeymap, history, historyField, undo, redo } from '@codemirror/co import { languages } from '@codemirror/language-data'; import { css } from '@codemirror/lang-css'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; +import { autocompleteEmoji } from './autocompleteEmoji.js'; import * as themes from '@uiw/codemirror-themes-all'; const themeCompartment = new Compartment(); @@ -31,20 +32,20 @@ import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './lega const createHighlightPlugin = (renderer, tab)=>{ const tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; -class countWidget extends WidgetType { - constructor(count) { - super(); - this.count = count; - } - toDOM() { - const span = document.createElement("span"); - span.className = "cm-page-count"; - span.textContent = this.count; - span.style.color = "#989898"; - return span; - } - ignoreEvent() { return true; } -} + class countWidget extends WidgetType { + constructor(count) { + super(); + this.count = count; + } + toDOM() { + const span = document.createElement('span'); + span.className = 'cm-page-count'; + span.textContent = this.count; + span.style.color = '#989898'; + return span; + } + ignoreEvent() { return true; } + } return ViewPlugin.fromClass( class { @@ -154,6 +155,7 @@ const CodeEditor = forwardRef( highlightActiveLine(), highlightActiveLineGutter(), highlightCompartment.of(combinedHighlight), + autocompleteEmoji, ]; }; diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index ce583fc8b..7caa47561 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -2,11 +2,11 @@ @import '@themes/codeMirror/customEditorStyles.less'; .editor { - position : relative; - width : 100%; - height : 100%; - container : editor / inline-size; - background:white; + position : relative; + width : 100%; + height : 100%; + container : editor / inline-size; + background : white; .codeEditor { height : calc(100% - 25px); .cm-editor { height : 100%; } @@ -14,6 +14,20 @@ background : #33333328; border-top : #333399 solid 1px; } + + .cm-tooltip-autocomplete { + + li { + display : flex; + gap : 10px; + align-items : center; + justify-content : flex-start; + + .cm-completionIcon { display : none; } + .cm-tooltip-autocomplete .cm-completionLabel { translate : 0 -2px; } + } + + } .cm-page-count { float : right; diff --git a/package-lock.json b/package-lock.json index 6d8207510..3330f5094 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@babel/preset-env": "^7.29.0", "@babel/preset-react": "^7.28.5", "@babel/runtime": "^7.28.6", + "@codemirror/autocomplete": "^6.20.1", "@codemirror/commands": "^6.10.3", "@codemirror/highlight": "^0.19.8", "@codemirror/lang-css": "^6.3.1", diff --git a/package.json b/package.json index 70dcbcb4a..86bfd4527 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "@babel/preset-env": "^7.29.0", "@babel/preset-react": "^7.28.5", "@babel/runtime": "^7.28.6", + "@codemirror/autocomplete": "^6.20.1", "@codemirror/commands": "^6.10.3", "@codemirror/highlight": "^0.19.8", "@codemirror/lang-css": "^6.3.1", From 06e15a04e92cbbdd70c46eca1bd6c30f99e271a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 26 Mar 2026 23:56:40 +0100 Subject: [PATCH 26/56] snippetline count, and disable highlights in css tab --- client/components/codeEditor/codeEditor.jsx | 13 +- client/homebrew/editor/editor.less | 163 ++++++++++---------- 2 files changed, 93 insertions(+), 83 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 6b4506b01..098c03281 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -39,7 +39,7 @@ const createHighlightPlugin = (renderer, tab)=>{ } toDOM() { const span = document.createElement('span'); - span.className = 'cm-page-count'; + span.className = 'cm-count'; span.textContent = this.count; span.style.color = '#989898'; return span; @@ -62,6 +62,7 @@ const createHighlightPlugin = (renderer, tab)=>{ const tokens = tokenize(view.state.doc.toString()); let pageCount = 1; + let snippetCount = 0; tokens.forEach((tok)=>{ const line = view.state.doc.line(tok.line + 1); @@ -74,11 +75,15 @@ const createHighlightPlugin = (renderer, tab)=>{ } else { decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); - if(tok.type === 'pageLine') { + if(tok.type === 'pageLine' && tab === "brewText") { pageCount++; line.from === 0 && pageCount--; decos.push(Decoration.widget({ widget: new countWidget(pageCount), side: 2 }).range(line.to)); } + if(tok.type === 'snippetLine' && tab === "brewSnippets") { + snippetCount++; + decos.push(Decoration.widget({ widget: new countWidget(snippetCount), side: 2 }).range(line.to)); + } } }); @@ -121,7 +126,7 @@ const CodeEditor = forwardRef( ? syntaxHighlighting(customHighlightStyle) : syntaxHighlighting(legacyCustomHighlightStyle); - const customHighlightPlugin = createHighlightPlugin(renderer); + const customHighlightPlugin = createHighlightPlugin(renderer, tab); const combinedHighlight = [ customHighlightPlugin, @@ -231,7 +236,7 @@ const CodeEditor = forwardRef( ? syntaxHighlighting(customHighlightStyle) : syntaxHighlighting(legacyCustomHighlightStyle); - const customHighlightPlugin = createHighlightPlugin(renderer); + const customHighlightPlugin = createHighlightPlugin(renderer, tab); view.dispatch({ effects : highlightCompartment.reconfigure([customHighlightPlugin, highlightExtension]), diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 7caa47561..4b2bd6017 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -10,95 +10,100 @@ .codeEditor { height : calc(100% - 25px); .cm-editor { height : 100%; } - .cm-pageLine, &.brewSnippets .cm-snippetLine { + &.brewSnippets .cm-snippetLine { background : #33333328; border-top : #333399 solid 1px; } - .cm-tooltip-autocomplete { + &.brewText, &.brewSnippets { - li { - display : flex; - gap : 10px; - align-items : center; - justify-content : flex-start; - - .cm-completionIcon { display : none; } - .cm-tooltip-autocomplete .cm-completionLabel { translate : 0 -2px; } + .cm-pageLine { + background : #33333328; + border-top : #333399 solid 1px; } - } + .cm-tooltip-autocomplete { + + li { + display : flex; + gap : 10px; + align-items : center; + justify-content : flex-start; + + .cm-completionIcon { display : none; } + .cm-tooltip-autocomplete .cm-completionLabel { translate : 0 -2px; } + } + } - .cm-page-count { - float : right; - color : grey; - } - .cm-snippet-count { - float : right; - color : grey; - } - .cm-columnSplit { - font-style : italic; - color : grey; - background-color : fade(#229999, 15%); - border-bottom : #229999 solid 1px; - } - .cm-define { - &:not(.term):not(.definition) { - font-weight : bold; - color : #949494; - background : #E5E5E5; - border-radius : 3px; + .cm-count { + float : right; + color : grey; } - &.term { color : rgb(96, 117, 143); } - &.definition { color : rgb(97, 57, 178); } - } - .cm-block:not(.cm-comment) { - font-weight : bold; - color : purple; - //font-style: italic; - } - .cm-inline-block:not(.cm-comment) { - font-weight : bold; - color : red; - //font-style: italic; - } - .cm-injection:not(.cm-comment) { - font-weight : bold; - color : green; - } - .cm-emoji:not(.cm-comment) { - padding-bottom : 1px; - margin-left : 2px; - font-weight : bold; - color : #360034; - outline : solid 2px #FF96FC; - outline-offset : -2px; - background : #FFC8FF; - border-radius : 6px; - } - .cm-superscript:not(.cm-comment) { - font-size : 0.9em; - font-weight : bold; - vertical-align : super; - color : goldenrod; - } - .cm-subscript:not(.cm-comment) { - font-size : 0.9em; - font-weight : bold; - vertical-align : sub; - color : rgb(123, 123, 15); - } - .cm-definitionList { - .cm-definitionTerm { color : rgb(96, 117, 143); } - .cm-definitionColon { - font-weight : bold; - color : #949494; - background : #E5E5E5; - border-radius : 3px; + .cm-columnSplit { + font-style : italic; + color : grey; + background-color : fade(#229999, 15%); + border-bottom : #229999 solid 1px; } - .cm-definitionDesc { color : rgb(97, 57, 178); } - } + .cm-define { + &:not(.term):not(.definition) { + font-weight : bold; + color : #949494; + background : #E5E5E5; + border-radius : 3px; + } + &.term { color : rgb(96, 117, 143); } + &.definition { color : rgb(97, 57, 178); } + } + .cm-block:not(.cm-comment) { + font-weight : bold; + color : purple; + //font-style: italic; + } + .cm-inline-block:not(.cm-comment) { + font-weight : bold; + color : red; + //font-style: italic; + } + .cm-injection:not(.cm-comment) { + font-weight : bold; + color : green; + } + .cm-emoji:not(.cm-comment) { + padding-bottom : 1px; + margin-left : 2px; + font-weight : bold; + color : #360034; + outline : solid 2px #FF96FC; + outline-offset : -2px; + background : #FFC8FF; + border-radius : 6px; + } + .cm-superscript:not(.cm-comment) { + font-size : 0.9em; + font-weight : bold; + vertical-align : super; + color : goldenrod; + } + .cm-subscript:not(.cm-comment) { + font-size : 0.9em; + font-weight : bold; + vertical-align : sub; + color : rgb(123, 123, 15); + } + .cm-definitionList { + .cm-definitionTerm { color : rgb(96, 117, 143); } + .cm-definitionColon { + font-weight : bold; + color : #949494; + background : #E5E5E5; + border-radius : 3px; + } + .cm-definitionDesc { color : rgb(97, 57, 178); } + } + + } + } .brewJump { From a00a3440fc5836a1aa3eea0a40b4678d5bdae7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 00:09:27 +0100 Subject: [PATCH 27/56] undo & redo --- client/components/codeEditor/customKeyMap.js | 3 +++ client/homebrew/editor/snippetbar/snippetbar.jsx | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/client/components/codeEditor/customKeyMap.js b/client/components/codeEditor/customKeyMap.js index 07dae0e6d..93760d7d0 100644 --- a/client/components/codeEditor/customKeyMap.js +++ b/client/components/codeEditor/customKeyMap.js @@ -1,4 +1,5 @@ import { keymap } from '@codemirror/view'; +import { undo, redo } from '@codemirror/commands'; const indentMore = (view)=>{ const { from, to } = view.state.selection.main; @@ -221,4 +222,6 @@ export const customKeymap = keymap.of([ { key: 'Shift-Mod-6', run: makeHeader(6) }, { key: 'Shift-Mod-Enter', run: newColumn }, { key: 'Mod-Enter', run: newPage }, + { key: 'Mod-z', run: undo }, //i think it may be unnecessary + { key: 'Mod-Shift-z', run: redo }, ]); diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index 2ecab8834..4af60b9bd 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -241,11 +241,11 @@ const Snippetbar = createReactClass({ { this.state.showHistory && this.renderHistoryItems() }
-
-
From 9a74a208847c7dbddf6a22e0d73b6606aded6c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 00:27:26 +0100 Subject: [PATCH 28/56] add search replace --- client/components/codeEditor/codeEditor.jsx | 6 +++--- package-lock.json | 12 ++++++++++++ package.json | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 098c03281..4391ff7f7 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -19,6 +19,7 @@ import { languages } from '@codemirror/language-data'; import { css } from '@codemirror/lang-css'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { autocompleteEmoji } from './autocompleteEmoji.js'; +import { searchKeymap, highlightSelectionMatches, search, openSearchPanel } from '@codemirror/search'; import * as themes from '@uiw/codemirror-themes-all'; const themeCompartment = new Compartment(); @@ -139,8 +140,7 @@ const CodeEditor = forwardRef( return [ history(), - keymap.of(defaultKeymap), - customKeymap, + keymap.of([...defaultKeymap, customKeymap, foldKeymap, ...searchKeymap]), updateListener, EditorView.lineWrapping, scrollPastEnd(), @@ -150,7 +150,6 @@ const CodeEditor = forwardRef( homebreweryFold, hbFolding, - keymap.of(foldKeymap), foldGutter({ openText : '▾', closedText : '▸' @@ -161,6 +160,7 @@ const CodeEditor = forwardRef( highlightActiveLineGutter(), highlightCompartment.of(combinedHighlight), autocompleteEmoji, + search(), ]; }; diff --git a/package-lock.json b/package-lock.json index 3330f5094..0a461307a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@codemirror/lang-markdown": "^6.5.0", "@codemirror/language": "^6.12.2", "@codemirror/language-data": "^6.5.2", + "@codemirror/search": "^6.6.0", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", @@ -2500,6 +2501,17 @@ "@codemirror/text": "^0.19.0" } }, + "node_modules/@codemirror/search": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, "node_modules/@codemirror/state": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", diff --git a/package.json b/package.json index 86bfd4527..5260bfb4f 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "@codemirror/lang-markdown": "^6.5.0", "@codemirror/language": "^6.12.2", "@codemirror/language-data": "^6.5.2", + "@codemirror/search": "^6.6.0", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.40.0", "@dmsnell/diff-match-patch": "^1.1.0", From 55d93a0e5f72c11e55361257c67a5e53b0040a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 00:27:38 +0100 Subject: [PATCH 29/56] remove unnecessary code --- client/homebrew/editor/editor.jsx | 171 ------------------------------ 1 file changed, 171 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 535e3cbf5..e452fccf9 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -72,7 +72,6 @@ const Editor = createReactClass({ componentDidMount : function() { - this.highlightCustomMarkdown(); document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys); @@ -98,7 +97,6 @@ const Editor = createReactClass({ componentDidUpdate : function(prevProps, prevState, snapshot) { - this.highlightCustomMarkdown(); if(prevProps.moveBrew !== this.props.moveBrew) this.brewJump(); @@ -160,175 +158,6 @@ const Editor = createReactClass({ }); }, - highlightCustomMarkdown : function(){ - if(!this.codeEditor.current?.codeMirror) return; - if((this.state.view === 'text') ||(this.state.view === 'snippet')) { - const codeMirror = this.codeEditor.current.codeMirror; - - codeMirror?.operation(()=>{ // Batch CodeMirror styling - - const foldLines = []; - - //reset custom text styles - const customHighlights = codeMirror?.getAllMarks().filter((mark)=>{ - // Record details of folded sections - if(mark.__isFold) { - const fold = mark.find(); - foldLines.push({ from: fold.from?.line, to: fold.to?.line }); - } - return !mark.__isFold; - }); //Don't undo code folding - - for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear(); - - let userSnippetCount = 1; // start snippet count from snippet 1 - let editorPageCount = 1; // start page count from page 1 - - const whichSource = this.state.view === 'text' ? this.props.brew.text : this.props.brew.snippets; - _.forEach(whichSource?.split('\n'), (line, lineNumber)=>{ - - const tabHighlight = this.state.view === 'text' ? 'pageLine' : 'snippetLine'; - const textOrSnip = this.state.view === 'text'; - - //reset custom line styles - codeMirror?.removeLineClass(lineNumber, 'background', 'pageLine'); - codeMirror?.removeLineClass(lineNumber, 'background', 'snippetLine'); - codeMirror?.removeLineClass(lineNumber, 'text'); - codeMirror?.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash'); - - // Don't process lines inside folded text - // If the current lineNumber is inside any folded marks, skip line styling - if(foldLines.some((fold)=>lineNumber >= fold.from && lineNumber <= fold.to)) - return; - - // Styling for \page breaks - if((this.props.renderer == 'legacy' && line.includes('\\page')) || - (this.props.renderer == 'V3' && line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3))) { - - if((lineNumber > 0) && (textOrSnip)) // Since \page is optional on first line of document, - editorPageCount += 1; // don't use it to increment page count; stay at 1 - else if(this.state.view !== 'text') userSnippetCount += 1; - - // add back the original class 'background' but also add the new class '.pageline' - codeMirror?.addLineClass(lineNumber, 'background', tabHighlight); - const pageCountElement = Object.assign(document.createElement('span'), { - className : 'editor-page-count', - textContent : textOrSnip ? editorPageCount : userSnippetCount - }); - codeMirror?.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement); - }; - - - // New CodeMirror styling for V3 renderer - if(this.props.renderer === 'V3') { - if(line.match(/^\\column(?:break)?$/)){ - codeMirror?.addLineClass(lineNumber, 'text', 'columnSplit'); - } - - // definition lists - if(line.includes('::')){ - if(/^:*$/.test(line) == true){ return; }; - const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error. - let match; - while ((match = regex.exec(line)) != null){ - codeMirror?.markText({ line: lineNumber, ch: match.indices[0][0] }, { line: lineNumber, ch: match.indices[0][1] }, { className: 'dl-highlight' }); - codeMirror?.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' }); - codeMirror?.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' }); - const ddIndex = match.indices[2][0]; - const colons = /::/g; - const colonMatches = colons.exec(match[2]); - if(colonMatches !== null){ - codeMirror?.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight' }); - } - } - } - - // Subscript & Superscript - if(line.includes('^')) { - let startIndex = line.indexOf('^'); - const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy; - const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy; - - while (startIndex >= 0) { - superRegex.lastIndex = subRegex.lastIndex = startIndex; - let isSuper = false; - const match = subRegex.exec(line) || superRegex.exec(line); - if(match) { - isSuper = !subRegex.lastIndex; - codeMirror?.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' }); - } - startIndex = line.indexOf('^', Math.max(startIndex + 1, subRegex.lastIndex, superRegex.lastIndex)); - } - } - - // Highlight injectors {style} - if(line.includes('{') && line.includes('}')){ - const regex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm; - let match; - while ((match = regex.exec(line)) != null) { - codeMirror?.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' }); - } - } - // Highlight inline spans {{content}} - if(line.includes('{{') && line.includes('}}')){ - const regex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g; - let match; - let blockCount = 0; - while ((match = regex.exec(line)) != null) { - if(match[0].startsWith('{')) { - blockCount += 1; - } else { - blockCount -= 1; - } - if(blockCount < 0) { - blockCount = 0; - continue; - } - codeMirror?.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' }); - } - } else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){ - // Highlight block divs {{\n Content \n}} - let endCh = line.length+1; - - const match = line.match(/^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/); - if(match) - endCh = match.index+match[0].length; - codeMirror?.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); - } - - // Emojis - if(line.match(/:[^\s:]+:/g)) { - let startIndex = line.indexOf(':'); - const emojiRegex = /:[^\s:]+:/gy; - - while (startIndex >= 0) { - emojiRegex.lastIndex = startIndex; - const match = emojiRegex.exec(line); - if(match) { - let tokens = Markdown.marked.lexer(match[0]); - tokens = tokens[0].tokens.filter((t)=>t.type == 'emoji'); - if(!tokens.length) - return; - - const startPos = { line: lineNumber, ch: match.index }; - const endPos = { line: lineNumber, ch: match.index + match[0].length }; - - // Iterate over conflicting marks and clear them - const marks = codeMirror?.findMarks(startPos, endPos); - marks.forEach(function(marker) { - if(!marker.__isFold) marker.clear(); - }); - codeMirror?.markText(startPos, endPos, { className: 'emoji' }); - } - startIndex = line.indexOf(':', Math.max(startIndex + 1, emojiRegex.lastIndex)); - } - } - } - }); - }); - } - }, - brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){ if(!window || !this.isText() || isJumping) return; From 9edb0e2fc6e0b6e57abe99c895ecf08c355959cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 11:59:56 +0100 Subject: [PATCH 30/56] fix brewjump functions --- client/components/codeEditor/codeEditor.jsx | 44 ++++++++++++++++++--- client/homebrew/editor/editor.jsx | 43 ++++++++------------ 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 4391ff7f7..4ebe2c7ee 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -76,12 +76,12 @@ const createHighlightPlugin = (renderer, tab)=>{ } else { decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); - if(tok.type === 'pageLine' && tab === "brewText") { + if(tok.type === 'pageLine' && tab === 'brewText') { pageCount++; line.from === 0 && pageCount--; decos.push(Decoration.widget({ widget: new countWidget(pageCount), side: 2 }).range(line.to)); } - if(tok.type === 'snippetLine' && tab === "brewSnippets") { + if(tok.type === 'snippetLine' && tab === 'brewSnippets') { snippetCount++; decos.push(Decoration.widget({ widget: new countWidget(snippetCount), side: 2 }).range(line.to)); } @@ -101,6 +101,8 @@ const CodeEditor = forwardRef( { value = '', onChange = ()=>{}, + onCursorChange = ()=>{}, + onViewChange = ()=>{}, language = '', tab = 'brewText', editorTheme = 'default', @@ -121,6 +123,18 @@ const CodeEditor = forwardRef( if(update.docChanged) { onChange(update.state.doc.toString()); } + if(update.selectionSet) { + const pos = update.state.selection.main.head; + const line = update.state.doc.lineAt(pos).number; + + onCursorChange(line); + } + if(update.viewportChanged) { + const { from } = update.view.viewport; + const line = update.state.doc.lineAt(from).number; + + onViewChange(line); + } }); const highlightExtension = renderer === 'V3' @@ -267,9 +281,29 @@ const CodeEditor = forwardRef( getCursorPosition : ()=>viewRef.current.state.selection.main.head, - setCursorPosition : (pos)=>{ - viewRef.current.dispatch({ selection: { anchor: pos } }); - viewRef.current.focus(); + getScrollTop : ()=>viewRef.current.scrollDOM.scrollTop, + + scrollToY : (y)=>{ + viewRef.current.scrollDOM.scrollTo({ top: y }); + }, + + getLineTop : (lineNumber)=>{ + const view = viewRef.current; + if(!view) return 0; + + const line = view.state.doc.line(lineNumber); + return view.coordsAtPos(line.from)?.top ?? 0; + }, + + setCursorToLine : (lineNumber)=>{ + const view = viewRef.current; + const line = view.state.doc.line(lineNumber); + + view.dispatch({ + selection : { anchor: line.from } + }); + + view.focus(); }, undo : ()=>undo(viewRef.current), diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index e452fccf9..f52153438 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -75,9 +75,6 @@ const Editor = createReactClass({ document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys); - this.codeEditor.current.codeMirror?.on('cursorActivity', (cm)=>{this.updateCurrentCursorPage(cm.getCursor());}); - this.codeEditor.current.codeMirror?.on('scroll', _.throttle(()=>{this.updateCurrentViewPage(this.codeEditor.current.getTopVisibleLine());}, 200)); - const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY); if(editorTheme) { this.setState({ @@ -130,15 +127,15 @@ const Editor = createReactClass({ } }, - updateCurrentCursorPage : function(cursor) { - const lines = this.props.brew.text.split('\n').slice(1, cursor.line + 1); + updateCurrentCursorPage : function(lineNumber) { + const lines = this.props.brew.text.split('\n').slice(0, lineNumber); const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); this.props.onCursorPageChange(currentPage); }, - updateCurrentViewPage : function(topScrollLine) { - const lines = this.props.brew.text.split('\n').slice(1, topScrollLine + 1); + updateCurrentViewPage : function(topLine) { + const lines = this.props.brew.text.split('\n').slice(0, topLine); const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); this.props.onViewPageChange(currentPage); @@ -205,51 +202,41 @@ const Editor = createReactClass({ const textSplit = this.props.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; const textString = this.props.brew.text.split(textSplit).slice(0, targetPage-1).join(textSplit); - const targetLine = textString.match('\n') ? textString.split('\n').length - 1 : -1; + const targetLine = textString.match('\n') ? textString.split('\n').length : 1; - let currentY = this.codeEditor.current.codeMirror?.getScrollInfo().top; - let targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, 'local', true); + const editor = this.codeEditor.current; + + let currentY = editor.getScrollTop(); + const targetY = editor.getLineTop(targetLine); let scrollingTimeout; const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs scrollingTimeout = setTimeout(()=>{ isJumping = false; - this.codeEditor.current.codeMirror?.off('scroll', checkIfScrollComplete); }, 150); // If 150 ms pass without a scroll event, assume scrolling is done }; isJumping = true; checkIfScrollComplete(); - if(this.codeEditor.current?.codeMirror) { - this.codeEditor.current.codeMirror?.on('scroll', checkIfScrollComplete); - } if(smooth) { //Scroll 1/10 of the way every 10ms until 1px off. const incrementalScroll = setInterval(()=>{ currentY += (targetY - currentY) / 10; - this.codeEditor.current.codeMirror?.scrollTo(null, currentY); + editor.scrollToY(currentY); - // Update target: target height is not accurate until within +-10 lines of the visible window - if(Math.abs(targetY - currentY > 100)) - targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, 'local', true); - - // End when close enough if(Math.abs(targetY - currentY) < 1) { - this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference - this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); - this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); + editor.scrollToY(targetY); + editor.setCursorToLine(targetLine); clearInterval(incrementalScroll); } }, 10); } else { - this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference - this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); - this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); + editor.scrollToY(targetY); + editor.setCursorToLine(targetLine); } }, - //Called when there are changes to the editor's dimensions update : function(){}, @@ -275,6 +262,8 @@ const Editor = createReactClass({ view={this.state.view} value={this.props.brew.text} onChange={this.props.onBrewChange('text')} + onCursorChange={(line)=>this.updateCurrentCursorPage(line)} + onViewChange={(line)=>this.updateCurrentViewPage(line)} editorTheme={this.state.editorTheme} renderer={this.props.brew.renderer} rerenderParent={this.rerenderParent} From c0b3a1940f77559c581800ac7f2cc25d993dcf9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 12:16:27 +0100 Subject: [PATCH 31/56] final fixes --- client/components/codeEditor/codeEditor.jsx | 6 ++++-- client/components/codeEditor/customHighlight.js | 8 +------- client/components/codeEditor/customKeyMap.js | 1 + client/components/codeEditor/legacyCustomHighlight.js | 6 ------ client/homebrew/editor/editor.jsx | 3 +-- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 4ebe2c7ee..899edb673 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -1,3 +1,4 @@ +/* eslint max-lines: ["error", { "max": 400 }] */ import './codeEditor.less'; import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react'; @@ -19,7 +20,7 @@ import { languages } from '@codemirror/language-data'; import { css } from '@codemirror/lang-css'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { autocompleteEmoji } from './autocompleteEmoji.js'; -import { searchKeymap, highlightSelectionMatches, search, openSearchPanel } from '@codemirror/search'; +import { searchKeymap, search } from '@codemirror/search'; import * as themes from '@uiw/codemirror-themes-all'; const themeCompartment = new Compartment(); @@ -32,7 +33,7 @@ import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './lega const createHighlightPlugin = (renderer, tab)=>{ const tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; - + /* eslint-disable no-restricted-syntax */ class countWidget extends WidgetType { constructor(count) { super(); @@ -47,6 +48,7 @@ const createHighlightPlugin = (renderer, tab)=>{ } ignoreEvent() { return true; } } + /* eslint-enable no-restricted-syntax */ return ViewPlugin.fromClass( class { diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index 88a219b30..5ad5d10d5 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -15,17 +15,13 @@ const customTags = { definitionList : 'definitionList', // .cm-definitionList definitionTerm : 'definitionTerm', // .cm-definitionTerm definitionDesc : 'definitionDesc', // .cm-definitionDesc - definitionColon : 'definitionColon',// .cm-definitionColon + definitionColon : 'definitionColon', // .cm-definitionColon }; export 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 }); @@ -92,7 +88,6 @@ export function tokenizeCustomMarkdown(text) { if(match) { const [full, term, colons, desc] = match; let offset = 0; - // Entire line as definitionList tokens.push({ line : lineNumber, @@ -131,7 +126,6 @@ export function tokenizeCustomMarkdown(text) { // 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; diff --git a/client/components/codeEditor/customKeyMap.js b/client/components/codeEditor/customKeyMap.js index 93760d7d0..8fa1d7792 100644 --- a/client/components/codeEditor/customKeyMap.js +++ b/client/components/codeEditor/customKeyMap.js @@ -1,3 +1,4 @@ +/* eslint max-lines: ["error", { "max": 300 }] */ import { keymap } from '@codemirror/view'; import { undo, redo } from '@codemirror/commands'; diff --git a/client/components/codeEditor/legacyCustomHighlight.js b/client/components/codeEditor/legacyCustomHighlight.js index 2777b3102..e011e15f5 100644 --- a/client/components/codeEditor/legacyCustomHighlight.js +++ b/client/components/codeEditor/legacyCustomHighlight.js @@ -1,7 +1,6 @@ import { HighlightStyle } from '@codemirror/language'; import { tags } from '@lezer/highlight'; -// Making the tokens const customTags = { pageLine : 'pageLine', // .cm-pageLine snippetLine : 'snippetLine', // .cm-snippetLine @@ -11,10 +10,6 @@ export function legacyTokenizeCustomMarkdown(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 }); @@ -31,4 +26,3 @@ export const legacyCustomHighlightStyle = HighlightStyle.define([ { tag: customTags.snippetLine, class: 'cm-snippetLine', color: '#0af' }, ]); - diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index f52153438..17b30b6ce 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -4,7 +4,6 @@ import React from 'react'; import createReactClass from 'create-react-class'; import _ from 'lodash'; import dedent from 'dedent'; -import Markdown from '@shared/markdown.js'; import CodeEditor from '../../components/codeEditor/codeEditor.jsx'; import SnippetBar from './snippetbar/snippetbar.jsx'; @@ -13,7 +12,7 @@ import MetadataEditor from './metadataEditor/metadataEditor.jsx'; const EDITOR_THEME_KEY = 'HB_editor_theme'; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; -const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/; +//const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/; const DEFAULT_STYLE_TEXT = dedent` /*=======--- Example CSS styling ---=======*/ /* Any CSS here will apply to your document! */ From ad9f4773b08ab658ed95b5ca05e7eaf77720496e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 13:13:36 +0100 Subject: [PATCH 32/56] default cm5 theme back --- client/components/codeEditor/codeEditor.jsx | 7 +- client/components/codeEditor/codeEditor.less | 14 ++++ .../components/codeEditor/customHighlight.js | 3 +- client/homebrew/editor/editor.less | 4 +- .../homebrew/editor/snippetbar/snippetbar.jsx | 5 +- themes/codeMirror/customThemes/default.js | 83 +++++++++++++++++++ 6 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 themes/codeMirror/customThemes/default.js diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 899edb673..3fe8e82ac 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -22,7 +22,10 @@ import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { autocompleteEmoji } from './autocompleteEmoji.js'; import { searchKeymap, search } from '@codemirror/search'; -import * as themes from '@uiw/codemirror-themes-all'; +import * as themesImport from '@uiw/codemirror-themes-all'; +import { defaultCM5Theme } from '@themes/codeMirror/customThemes/default.js'; + +const themes = { default: defaultCM5Theme, ...themesImport }; const themeCompartment = new Compartment(); const highlightCompartment = new Compartment(); @@ -170,11 +173,11 @@ const CodeEditor = forwardRef( openText : '▾', closedText : '▸' }), - themeCompartment.of(themeExtension), highlightActiveLine(), highlightActiveLineGutter(), highlightCompartment.of(combinedHighlight), + themeCompartment.of(themeExtension), autocompleteEmoji, search(), ]; diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less index f28f66140..eceaf403a 100644 --- a/client/components/codeEditor/codeEditor.less +++ b/client/components/codeEditor/codeEditor.less @@ -24,6 +24,20 @@ font-size: 16px; } + .cm-gutterElement span { + font-family : inherit; + font-weight : 600; + color : grey; + text-shadow : none; + } + + .cm-foldGutter { + cursor : pointer; + border-left : 1px solid #EEEEEE; + transition : background 0.1s; + &:hover { background : #DDDDDD; } + } + /* Flash animation for source moves */ .sourceMoveFlash .cm-line { animation-name: sourceMoveAnimation; diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index 5ad5d10d5..148615833 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -228,7 +228,8 @@ export function tokenizeCustomMarkdown(text) { } export const customHighlightStyle = HighlightStyle.define([ - { tag: tags.heading1, color: '#000', fontWeight: '700' }, + { tag: tags.heading, class: 'cm-header' }, + { tag: tags.heading1, class: 'cm-header cm-header-1' }, { tag: tags.keyword, color: '#07a' }, // example for your markdown headings { tag: customTags.pageLine, color: '#f0a' }, { tag: customTags.snippetLine, class: 'cm-snippetLine', color: '#0af' }, diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 4b2bd6017..56e14cf1c 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -9,7 +9,9 @@ background : white; .codeEditor { height : calc(100% - 25px); - .cm-editor { height : 100%; } + .cm-editor { height : 100%; + outline:none !important; + } &.brewSnippets .cm-snippetLine { background : #33333328; border-top : #333399 solid 1px; diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index 4af60b9bd..d7af14f49 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -24,7 +24,10 @@ const ThemeSnippets = { }; //import EditorThemes from '../../../../build/homebrew/codeMirror/editorThemes.json'; -import * as themes from '@uiw/codemirror-themes-all'; +import * as themesImport from '@uiw/codemirror-themes-all'; +import { defaultCM5Theme } from '@themes/codeMirror/customThemes/default.js'; + +const themes = { default: defaultCM5Theme, ...themesImport }; const EditorThemes = Object.entries(themes) .filter(([name, value]) => diff --git a/themes/codeMirror/customThemes/default.js b/themes/codeMirror/customThemes/default.js new file mode 100644 index 000000000..1fa1ddace --- /dev/null +++ b/themes/codeMirror/customThemes/default.js @@ -0,0 +1,83 @@ +// themes/codeMirror/customThemes/default.js +import { EditorView } from '@codemirror/view'; +import { Compartment } from '@codemirror/state'; + +export const themeCompartment = new Compartment(); + +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", + textShadow: + "#b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px", + 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": { color: "#219" }, + ".cm-number": { color: "#164" }, + ".cm-def": { color: "#00f" }, + ".cm-variable-2": { color: "#05a" }, + ".cm-variable-3, .cm-type": { color: "#085" }, + ".cm-comment": { color: "#a50" }, + ".cm-string": { color: "#a11" }, + ".cm-string-2": { color: "#f50" }, + ".cm-meta, .cm-qualifier": { color: "#555" }, + ".cm-builtin": { color: "#30a" }, + ".cm-bracket": { color: "#997" }, + ".cm-tag": { color: "#170" }, + ".cm-attribute": { color: "#00c" }, + ".cm-hr": { color: "#999" }, + ".cm-link": { color: "#00c", textDecoration: "underline" }, + ".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)" }, +}, { dark: false }); \ No newline at end of file From 98f73dd57cae95c25bb29991b2ac4e90a7480994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 13:29:31 +0100 Subject: [PATCH 33/56] good styling --- client/components/codeEditor/customHighlight.js | 17 +++++++++++++++-- client/homebrew/editor/editor.less | 6 +++--- themes/codeMirror/customThemes/default.js | 8 ++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index 148615833..38a5dbba8 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -22,6 +22,7 @@ export function tokenizeCustomMarkdown(text) { const tokens = []; const lines = text.split('\n'); + console.log(tags); lines.forEach((lineText, lineNumber)=>{ // --- Page / snippet lines --- if(/^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m.test(lineText)) tokens.push({ line: lineNumber, type: customTags.pageLine }); @@ -230,10 +231,22 @@ export function tokenizeCustomMarkdown(text) { export const customHighlightStyle = HighlightStyle.define([ { tag: tags.heading, class: 'cm-header' }, { tag: tags.heading1, class: 'cm-header cm-header-1' }, - { tag: tags.keyword, color: '#07a' }, // example for your markdown headings + { 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: customTags.pageLine, color: '#f0a' }, { tag: customTags.snippetLine, class: 'cm-snippetLine', color: '#0af' }, - { tag: customTags.inlineBlock, class: 'cm-inline-block', backgroundColor: '#fffae6' }, + { tag: customTags.inlineBlock, class: 'cm-inline-block' }, { 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' }, diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 56e14cf1c..a9d7383f9 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -60,16 +60,16 @@ .cm-block:not(.cm-comment) { font-weight : bold; color : purple; - //font-style: italic; } .cm-inline-block:not(.cm-comment) { font-weight : bold; - color : red; - //font-style: italic; + color : red ; + span { color : inherit } } .cm-injection:not(.cm-comment) { font-weight : bold; color : green; + span { color : inherit } } .cm-emoji:not(.cm-comment) { padding-bottom : 1px; diff --git a/themes/codeMirror/customThemes/default.js b/themes/codeMirror/customThemes/default.js index 1fa1ddace..d6974c255 100644 --- a/themes/codeMirror/customThemes/default.js +++ b/themes/codeMirror/customThemes/default.js @@ -62,18 +62,18 @@ export const defaultCM5Theme = EditorView.theme({ ".cm-atom": { color: "#219" }, ".cm-number": { color: "#164" }, ".cm-def": { color: "#00f" }, - ".cm-variable-2": { color: "#05a" }, + ".cm-list": { color: "#05a" }, ".cm-variable-3, .cm-type": { color: "#085" }, ".cm-comment": { color: "#a50" }, - ".cm-string": { color: "#a11" }, - ".cm-string-2": { color: "#f50" }, + ".cm-link": { color: "#00c", textDecoration: "underline" }, + ".cm-string": { color: "#a11", textDecoration: "none" }, + ".cm-string-2": { color: "#f50", textDecoration: "none" }, ".cm-meta, .cm-qualifier": { color: "#555" }, ".cm-builtin": { color: "#30a" }, ".cm-bracket": { color: "#997" }, ".cm-tag": { color: "#170" }, ".cm-attribute": { color: "#00c" }, ".cm-hr": { color: "#999" }, - ".cm-link": { color: "#00c", textDecoration: "underline" }, ".cm-negative": { color: "#d44" }, ".cm-positive": { color: "#292" }, ".cm-error, .cm-invalidchar": { color: "#f00" }, From 0842e398c493cc97d06994b93eb632c3f6580027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 16:10:45 +0100 Subject: [PATCH 34/56] most of the css highlights --- client/components/codeEditor/codeEditor.jsx | 16 ++++--- .../components/codeEditor/customHighlight.js | 45 ++++++++++++++++++- themes/codeMirror/customThemes/default.js | 8 ++-- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 3fe8e82ac..f1e95dca5 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -17,7 +17,7 @@ import { EditorState, Compartment } from '@codemirror/state'; 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 { css, cssLanguage } from '@codemirror/lang-css'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { autocompleteEmoji } from './autocompleteEmoji.js'; import { searchKeymap, search } from '@codemirror/search'; @@ -31,11 +31,17 @@ const highlightCompartment = new Compartment(); import { customKeymap } from './customKeyMap.js'; import { homebreweryFold, hbFolding } from './customFolding.js'; -import { customHighlightStyle, tokenizeCustomMarkdown } from './customHighlight.js'; +import { customHighlightStyle, tokenizeCustomMarkdown, tokenizeCustomCSS } from './customHighlight.js'; import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.js'; //only makes highlight for const createHighlightPlugin = (renderer, tab)=>{ - const tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; + let tokenize; + +if (tab === "brewStyles") { + tokenize = tokenizeCustomCSS; +} else { + tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; +} /* eslint-disable no-restricted-syntax */ class countWidget extends WidgetType { constructor(count) { @@ -153,7 +159,7 @@ const CodeEditor = forwardRef( highlightExtension, ]; - const languageExtension = language === 'css' ? css() : markdown({ base: markdownLanguage, codeLanguages: languages }); + const languageExtension = language === 'css' ? [css(), cssLanguage] : markdown({ base: markdownLanguage, codeLanguages: languages }); const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; @@ -178,7 +184,7 @@ const CodeEditor = forwardRef( highlightActiveLineGutter(), highlightCompartment.of(combinedHighlight), themeCompartment.of(themeExtension), - autocompleteEmoji, + ...(tab !== 'brewStyles' ? [autocompleteEmoji] : []), search(), ]; }; diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index 38a5dbba8..f13445720 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -16,13 +16,17 @@ const customTags = { definitionTerm : 'definitionTerm', // .cm-definitionTerm definitionDesc : 'definitionDesc', // .cm-definitionDesc definitionColon : 'definitionColon', // .cm-definitionColon + + //CSS + + variable: 'variable', + colorMark: 'colorMark', }; export function tokenizeCustomMarkdown(text) { const tokens = []; const lines = text.split('\n'); - console.log(tags); lines.forEach((lineText, lineNumber)=>{ // --- Page / snippet lines --- if(/^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m.test(lineText)) tokens.push({ line: lineNumber, type: customTags.pageLine }); @@ -228,6 +232,29 @@ export function tokenizeCustomMarkdown(text) { return tokens; } +export function tokenizeCustomCSS(text) { + const tokens = []; + const lines = text.split('\n'); + + lines.forEach((lineText, lineNumber)=>{ + + if(/--[a-zA-Z0-9-_]+/gm.test(lineText)) { + const varRegex =/--[a-zA-Z0-9-_]+/gm; + let match; + while ((match = varRegex.exec(lineText)) !== null) { + tokens.push({ + line : lineNumber, + from : match.index +1, + to : match.index + match.length[1] +1, + type : customTags.varProperty, + }); + } + } + }); + + return tokens; +} + export const customHighlightStyle = HighlightStyle.define([ { tag: tags.heading, class: 'cm-header' }, { tag: tags.heading1, class: 'cm-header cm-header-1' }, @@ -236,13 +263,27 @@ 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.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' }, { tag: customTags.pageLine, color: '#f0a' }, { tag: customTags.snippetLine, class: 'cm-snippetLine', color: '#0af' }, diff --git a/themes/codeMirror/customThemes/default.js b/themes/codeMirror/customThemes/default.js index d6974c255..49e663a58 100644 --- a/themes/codeMirror/customThemes/default.js +++ b/themes/codeMirror/customThemes/default.js @@ -46,8 +46,6 @@ export const defaultCM5Theme = EditorView.theme({ }, ".cm-foldmarker": { color: "blue", - textShadow: - "#b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px", fontFamily: "arial", lineHeight: "0.3", cursor: "pointer", @@ -59,16 +57,16 @@ export const defaultCM5Theme = EditorView.theme({ ".cm-em": { fontStyle: "italic" }, ".cm-quote": { color: "#090" }, ".cm-keyword": { color: "#708" }, - ".cm-atom": { color: "#219" }, + ".cm-atom, cm-value, cm-color": { color: "#219" }, ".cm-number": { color: "#164" }, ".cm-def": { color: "#00f" }, ".cm-list": { color: "#05a" }, - ".cm-variable-3, .cm-type": { color: "#085" }, + ".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": { color: "#555" }, + ".cm-meta, .cm-qualifier, .cm-class": { color: "#555" }, ".cm-builtin": { color: "#30a" }, ".cm-bracket": { color: "#997" }, ".cm-tag": { color: "#170" }, From 901f84bf34bf7b7a21604b9a966a7db1e4c63c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 16:14:14 +0100 Subject: [PATCH 35/56] that should do it --- client/homebrew/editor/editor.less | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index a9d7383f9..e57bbd44c 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -17,12 +17,21 @@ border-top : #333399 solid 1px; } + &.brewText .cm-pageLine { + background : #33333328; + border-top : #333399 solid 1px; + } + + &.brewSnippets { + .cm-pageLine { + background : #3e4e3e1b; + border-top : #3399423b solid 1px; + color:#777; + } + } + &.brewText, &.brewSnippets { - .cm-pageLine { - background : #33333328; - border-top : #333399 solid 1px; - } .cm-tooltip-autocomplete { From c996bee9327c3e54f29d9d22261cc83c453ec681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 16:16:46 +0100 Subject: [PATCH 36/56] fix snippet editor height --- client/homebrew/editor/editor.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 17b30b6ce..4deffa18a 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -313,7 +313,7 @@ const Editor = createReactClass({ editorTheme={this.state.editorTheme} renderer={this.props.brew.renderer} rerenderParent={this.rerenderParent} - style={{ height: `calc(100% -${this.state.snippetBarHeight}px)` }} /> + style={{ height: `calc(100% - 25px)` }} /> ; } }, From 7abea941965f7b0b39fc589636ce908327b7a771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 16:26:41 +0100 Subject: [PATCH 37/56] proper use of tab --- client/components/codeEditor/codeEditor.jsx | 4 +++- client/components/codeEditor/customKeyMap.js | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index f1e95dca5..e2ccd6631 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -165,7 +165,7 @@ const CodeEditor = forwardRef( return [ history(), - keymap.of([...defaultKeymap, customKeymap, foldKeymap, ...searchKeymap]), + updateListener, EditorView.lineWrapping, scrollPastEnd(), @@ -186,6 +186,8 @@ const CodeEditor = forwardRef( themeCompartment.of(themeExtension), ...(tab !== 'brewStyles' ? [autocompleteEmoji] : []), search(), + keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]), + customKeymap ]; }; diff --git a/client/components/codeEditor/customKeyMap.js b/client/components/codeEditor/customKeyMap.js index 8fa1d7792..518248633 100644 --- a/client/components/codeEditor/customKeyMap.js +++ b/client/components/codeEditor/customKeyMap.js @@ -2,6 +2,17 @@ import { keymap } from '@codemirror/view'; import { undo, redo } from '@codemirror/commands'; +const insertTabAtCursor = (view) => { + const { from } = view.state.selection.main; + + view.dispatch({ + changes: { from, insert: ' ' }, + selection: { anchor: from + 1 } + }); + + return true; +}; + const indentMore = (view)=>{ const { from, to } = view.state.selection.main; const lines = []; @@ -199,6 +210,7 @@ const newPage = (view)=>{ }; export const customKeymap = keymap.of([ + { key: 'Tab', run: insertTabAtCursor }, { key: 'Shift-Tab', run: indentMore }, { key: 'Mod-Shift-Tab', run: indentLess }, { key: 'Mod-b', run: makeBold }, From 4da270616d95822b1294d17ee9f6d00a4f25ef27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 18:50:46 +0100 Subject: [PATCH 38/56] multiple cursors --- client/components/codeEditor/codeEditor.jsx | 32 ++++++++++++------- .../components/codeEditor/customHighlight.js | 7 ++-- client/components/codeEditor/customKeyMap.js | 16 ++++------ 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index e2ccd6631..64717e546 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -11,7 +11,8 @@ import { scrollPastEnd, Decoration, ViewPlugin, - WidgetType + WidgetType, + drawSelection, } from '@codemirror/view'; import { EditorState, Compartment } from '@codemirror/state'; import { foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language'; @@ -37,11 +38,11 @@ import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './lega const createHighlightPlugin = (renderer, tab)=>{ let tokenize; -if (tab === "brewStyles") { - tokenize = tokenizeCustomCSS; -} else { - tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; -} + if(tab === 'brewStyles') { + tokenize = tokenizeCustomCSS; + } else { + tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; + } /* eslint-disable no-restricted-syntax */ class countWidget extends WidgetType { constructor(count) { @@ -187,7 +188,9 @@ const CodeEditor = forwardRef( ...(tab !== 'brewStyles' ? [autocompleteEmoji] : []), search(), keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]), - customKeymap + customKeymap, + drawSelection(), + EditorState.allowMultipleSelections.of(true), ]; }; @@ -282,16 +285,23 @@ const CodeEditor = forwardRef( injectText : (text)=>{ const view = viewRef.current; - const { from, to } = view.state.selection.main; + const changes = view.state.selection.ranges.map((range)=>({ + from : range.from, + to : range.to, + insert : text + })); + + const newRanges = view.state.selection.ranges.map((range)=>({ + anchor : range.from + text.length + })); view.dispatch({ - changes : { from, to, insert: text }, - selection : { anchor: from + text.length }, + changes, + selection : { ranges: newRanges } }); view.focus(); }, - getCursorPosition : ()=>viewRef.current.state.selection.main.head, getScrollTop : ()=>viewRef.current.scrollDOM.scrollTop, diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index f13445720..3f62d6c9c 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -17,10 +17,9 @@ const customTags = { definitionDesc : 'definitionDesc', // .cm-definitionDesc definitionColon : 'definitionColon', // .cm-definitionColon - //CSS + //CSS - variable: 'variable', - colorMark: 'colorMark', + variable : 'variable', }; export function tokenizeCustomMarkdown(text) { @@ -237,7 +236,7 @@ export function tokenizeCustomCSS(text) { const lines = text.split('\n'); lines.forEach((lineText, lineNumber)=>{ - + if(/--[a-zA-Z0-9-_]+/gm.test(lineText)) { const varRegex =/--[a-zA-Z0-9-_]+/gm; let match; diff --git a/client/components/codeEditor/customKeyMap.js b/client/components/codeEditor/customKeyMap.js index 518248633..1c7560144 100644 --- a/client/components/codeEditor/customKeyMap.js +++ b/client/components/codeEditor/customKeyMap.js @@ -2,15 +2,13 @@ import { keymap } from '@codemirror/view'; import { undo, redo } from '@codemirror/commands'; -const insertTabAtCursor = (view) => { - const { from } = view.state.selection.main; - - view.dispatch({ - changes: { from, insert: ' ' }, - selection: { anchor: from + 1 } - }); - - return true; +const insertTabAtCursor = (view)=>{ + const { from } = view.state.selection.main; + view.dispatch({ + changes : { from, insert: ' ' }, + selection : { anchor: from + 1 } + }); + return true; }; const indentMore = (view)=>{ 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 39/56] 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 From 1f2a329553fcb07d95030a784bd59e09623b8d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 19:49:00 +0100 Subject: [PATCH 40/56] darkbrewery --- client/components/codeEditor/codeEditor.jsx | 3 +- client/components/codeEditor/codeEditor.less | 2 +- client/homebrew/editor/editor.less | 8 +- .../homebrew/editor/snippetbar/snippetbar.jsx | 3 +- .../codeMirror/customThemes/darkbrewery.css | 134 ------------------ themes/codeMirror/customThemes/darkbrewery.js | 114 +++++++++++++++ themes/codeMirror/customThemes/default.js | 6 +- 7 files changed, 125 insertions(+), 145 deletions(-) delete mode 100644 themes/codeMirror/customThemes/darkbrewery.css create mode 100644 themes/codeMirror/customThemes/darkbrewery.js diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 2067d370b..bc9702019 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -25,8 +25,9 @@ import { searchKeymap, search } from '@codemirror/search'; import * as themesImport from '@uiw/codemirror-themes-all'; import defaultCM5Theme from '@themes/codeMirror/customThemes/default.js'; +import darkbrewery from '../../../themes/codeMirror/customThemes/darkbrewery.js'; -const themes = { default: defaultCM5Theme, ...themesImport }; +const themes = { default: defaultCM5Theme, darkbrewery, ...themesImport }; const themeCompartment = new Compartment(); const highlightCompartment = new Compartment(); diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less index eceaf403a..6415753ae 100644 --- a/client/components/codeEditor/codeEditor.less +++ b/client/components/codeEditor/codeEditor.less @@ -15,7 +15,7 @@ } } -.codeEditor { +:where(.codeEditor) { font-family: monospace; height: 100%; width:100%; diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index e57bbd44c..5dea50966 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -1,13 +1,13 @@ @import '@sharedStyles/core.less'; @import '@themes/codeMirror/customEditorStyles.less'; -.editor { +:where(.editor) { position : relative; width : 100%; height : 100%; container : editor / inline-size; background : white; - .codeEditor { + :where(.codeEditor) { height : calc(100% - 25px); .cm-editor { height : 100%; outline:none !important; @@ -17,7 +17,7 @@ border-top : #333399 solid 1px; } - &.brewText .cm-pageLine { + :where(&.brewText) .cm-pageLine { background : #33333328; border-top : #333399 solid 1px; } @@ -30,7 +30,7 @@ } } - &.brewText, &.brewSnippets { + &:where(.brewText), &.brewSnippets { .cm-tooltip-autocomplete { diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index 12185f504..8b7a52824 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -26,8 +26,9 @@ 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 darkbrewery from '../../../../themes/codeMirror/customThemes/darkbrewery.js'; -const themes = { default: defaultCM5Theme, ...themesImport }; +const themes = { default: defaultCM5Theme, darkbrewery, ...themesImport }; const EditorThemes = Object.entries(themes) .filter(([name, value]) => diff --git a/themes/codeMirror/customThemes/darkbrewery.css b/themes/codeMirror/customThemes/darkbrewery.css deleted file mode 100644 index ec043c5ba..000000000 --- a/themes/codeMirror/customThemes/darkbrewery.css +++ /dev/null @@ -1,134 +0,0 @@ -/*stylelint-disable*/ -.editor .snippetBar { - color: white; - background-color: #2F393C; - .dropdown { - background-color: #2F393C; - } - .editors { - border-color: #ccc; - } -} -/* Main BG color and normal text color */ -.CodeMirror { - --bg: #293134; - --highlight: #bcbcbc; - color: #91A6AA; - background: var(--bg); - .cm-scroller { - .cm-gutters { - border-right: 1px solid #555; - background: var(--bg); - .cm-gutter { - background-color: var(--bg); - &.CodeMirror-foldgutter { - cursor: pointer; - border-left: 1px solid #555; - transition: background 0.1s; - &:hover { - background: #555; - } - } - } - } - .cm-content { - /* Line numbers*/ - .CodeMirror-linenumber.cm-gutter-elt { - background-color: var(--bg); - color: #81969A; - } - /* Blinking cursor */ - .CodeMirror-cursor { - border-left: 1px solid #E0E2E4; - } - .pageLine { - color: #000000; - background: #000000; - border-bottom: 1px solid #FFFFFF; - } - .CodeMirror-code .CodeMirror-line { - &.columnSplit { - font-style: italic; - color: inherit; - background-color: #1F5763; - border-bottom: #229999 solid 1px; - } - /*syntax*/ - .cm-header { - font-weight: bold; - color: #C51B1B; - -webkit-text-stroke-width: 0.1px; - -webkit-text-stroke-color: #000000; - } - .cm-strong { - color: #309DD2; - } - .cm-em { - /*italics*/ - } - .cm-link { - color: #DD6300; - } - .cm-string { - color: #AA8261; - } - /* @import */ - .cm-def { - color: #2986CC; - } - /* Bullets and such */ - .cm-variable-2 { - color: #3CBF30; - } - .block:not(.cm-comment) { - color: #E3E3E3; - } - .inline-block { - color: #E3E3E3; - } - .cm-tag { - color: #E3FF00; - } - .cm-attribute { - color: #E3FF00; - } - .cm-atom { - color: #c1939a; - } - .cm-number { - color: #2986CC; - } - .cm-property:not(.cm-error) ~ .cm-variable { - color:#9e1f9e; - } - .cm-qualifier { - color: #EE1919; - } - .cm-comment { - color: #BBC700; - } - .cm-keyword { - color: white; - } - .cm-error { - color: #C50202; - } - .CodeMirror-foldmarker { - color: #F0FF00; - } - .cm-builtin { - color: #FFFFFF; - } - .dt-highlight { - background: #ffffff14; - } - .dl-colon-highlight { - background: #ccc; - } - .dl-highlight.dd-highlight { - color: #b5858d; - } - } - } - } -} \ No newline at end of file diff --git a/themes/codeMirror/customThemes/darkbrewery.js b/themes/codeMirror/customThemes/darkbrewery.js new file mode 100644 index 000000000..4bc1aadd8 --- /dev/null +++ b/themes/codeMirror/customThemes/darkbrewery.js @@ -0,0 +1,114 @@ +import { EditorView } from '@codemirror/view'; + + +export default EditorView.theme({ + '&' : { + backgroundColor : '#293134', + color : '#91a6aa', + }, + '.cm-content' : { + padding : '4px 0', + fontFamily : 'monospace', + fontSize : '13px', + lineHeight : '1', + }, + '.cm-line' : { + padding : '0 4px', + }, + '.cm-gutters' : { + borderRight : '1px solid #555', + backgroundColor : '#293134', + whiteSpace : 'nowrap', + }, + '.cm-foldGutter' : { + borderLeft : '1px solid #555', + backgroundColor : '#293134', + }, + '.cm-foldGutter:hover' : { + backgroundColor : '#555', + }, + '.cm-gutterElement' : { + color: '#81969a', + }, + '.cm-linenumber' : { + padding : '0 3px 0 5px', + minWidth : '20px', + textAlign : 'right', + color : '#999', + whiteSpace : 'nowrap', + }, + '.cm-cursor' : { + borderLeft : '1px solid #E0E2E4', + }, + '.cm-fat-cursor' : { + width : 'auto', + backgroundColor : '#7e7', + caretColor : 'transparent', + }, + '.cm-activeLine' : { + backgroundColor : '#868c9323', + }, + '.cm-gutterElement.cm-activeLineGutter' : { + backgroundColor : '#868c9323', + }, + '.cm-activeLine' : { + backgroundColor : '#868c9323', + }, + '.cm-selected' : { + backgroundColor : '#d7d4f0', + }, + '.cm-pageLine' : { + backgroundColor: '#7ca97c', + color: '#000', + fontWeight: 'bold', + letterSpacing: '.5px', + borderTop: '1px solid #ff0', + }, + '.cm-columnSplit' : { + backgroundColor: '#7ca97c', + color: 'black', + fontWeight: 'bold', + letterSpacing: '1px', + borderBottom: '1px solid #ff0', + }, + '.cm-line.cm-block, .cm-line .cm-inline-block' : { + color: '#E3E3E3', + }, + '.cm-definitionList .cm-definitionTerm' : { + color: '#E3E3E3', + }, + '.cm-definitionList .cm-definitionColon' : { + backgroundColor: '#0000', + color: '#e3FF00', + }, + '.cm-definitionList .cm-definitionDesc' : { + color: '#b5858d', + }, + + // Semantic classes + '.cm-header' : { color: '#C51B1B', fontWeight: 'bold' }, + '.cm-strong' : { color: '#309dd2', fontWeight: 'bold' }, + '.cm-em' : { fontStyle: 'italic' }, + '.cm-keyword' : { color: '#fff' }, + '.cm-atom, cm-value, cm-color' : { color: '#c1939a' }, + '.cm-number' : { color: '#2986cc' }, + '.cm-def' : { color: '#2986cc' }, + '.cm-list' : { color: '#3cbf30' }, + '.cm-variable, .cm-type' : { color: '#085' }, + '.cm-comment' : { color: '#bbc700' }, + '.cm-link' : { color: '#DD6300', textDecoration: 'underline' }, + '.cm-string' : { color: '#AA8261', textDecoration: 'none' }, + '.cm-string-2' : { color: '#f50', textDecoration: 'none' }, + '.cm-meta, .cm-qualifier, .cm-class' : { color: '#19ee2b' }, + '.cm-builtin' : { color: '#fff' }, + '.cm-bracket' : { color: '#997' }, + '.cm-tag, .cm-attribute' : { color: '#e3ff00' }, + '.cm-hr' : { color: '#999' }, + '.cm-negative' : { color: '#d44' }, + '.cm-positive' : { color: '#292' }, + '.cm-error, .cm-invalidchar' : { color: '#c50202' }, + '.cm-matchingbracket' : { color: '#0b0' }, + '.cm-nonmatchingbracket' : { color: '#a22' }, + '.cm-matchingtag' : { backgroundColor: 'rgba(255, 150, 0, 0.3)' }, + '.cm-quote' : { color: '#090' }, +}, { dark: true }); \ No newline at end of file diff --git a/themes/codeMirror/customThemes/default.js b/themes/codeMirror/customThemes/default.js index 305271405..5c65d94f1 100644 --- a/themes/codeMirror/customThemes/default.js +++ b/themes/codeMirror/customThemes/default.js @@ -1,4 +1,3 @@ -// themes/codeMirror/customThemes/default.js import { EditorView } from '@codemirror/view'; //This theme is made of the base css for the codemirror 5 editor @@ -37,7 +36,7 @@ export default EditorView.theme({ backgroundColor : '#7e7', caretColor : 'transparent', }, - '.cm-activeline-background' : { + '.cm-activeLine' : { backgroundColor : '#e8f2ff', }, '.cm-selected' : { @@ -50,7 +49,6 @@ export default EditorView.theme({ cursor : 'pointer', }, - // Semantic classes '.cm-header' : { color: 'blue', fontWeight: 'bold' }, '.cm-strong' : { fontWeight: 'bold' }, '.cm-em' : { fontStyle: 'italic' }, @@ -75,6 +73,6 @@ export default EditorView.theme({ '.cm-error, .cm-invalidchar' : { color: '#f00' }, '.cm-matchingbracket' : { color: '#0b0' }, '.cm-nonmatchingbracket' : { color: '#a22' }, - '.cm-matchingtag' : { backgroundColor: 'rgba(255, 150, 0, 0.3)' }, + '.cm-matchingtag' : { backgroundColor: '#ff96004d' }, '.cm-quote' : { color: '#090' }, }, { dark: false }); \ No newline at end of file From 94080ba9520eaf7109d17d36b2864919f9360df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 19:53:49 +0100 Subject: [PATCH 41/56] file org update --- client/components/codeEditor/codeEditor.jsx | 4 +- .../homebrew/editor/snippetbar/snippetbar.jsx | 4 +- themes/codeMirror/customEditorStyles.less | 83 ------------------- .../{customThemes => }/darkbrewery.js | 58 ++++++------- .../{customThemes => }/darkvision.css | 5 ++ .../codeMirror/{customThemes => }/default.js | 0 6 files changed, 38 insertions(+), 116 deletions(-) delete mode 100644 themes/codeMirror/customEditorStyles.less rename themes/codeMirror/{customThemes => }/darkbrewery.js (74%) rename themes/codeMirror/{customThemes => }/darkvision.css (91%) rename themes/codeMirror/{customThemes => }/default.js (100%) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index bc9702019..5ecd0c91b 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -24,8 +24,8 @@ 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 darkbrewery from '../../../themes/codeMirror/customThemes/darkbrewery.js'; +import defaultCM5Theme from '@themes/codeMirror/default.js'; +import darkbrewery from '@themes/codeMirror/darkbrewery.js'; const themes = { default: defaultCM5Theme, darkbrewery, ...themesImport }; const themeCompartment = new Compartment(); diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index 8b7a52824..3d489de4a 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -25,8 +25,8 @@ 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 darkbrewery from '../../../../themes/codeMirror/customThemes/darkbrewery.js'; +import defaultCM5Theme from '@themes/codeMirror/default.js'; +import darkbrewery from '@themes/codeMirror/darkbrewery.js'; const themes = { default: defaultCM5Theme, darkbrewery, ...themesImport }; diff --git a/themes/codeMirror/customEditorStyles.less b/themes/codeMirror/customEditorStyles.less deleted file mode 100644 index b6e95d14b..000000000 --- a/themes/codeMirror/customEditorStyles.less +++ /dev/null @@ -1,83 +0,0 @@ -.editor .codeEditor .cm-editor { - // Themes with dark backgrounds - &.cm-s-3024-night, - &.cm-s-abbott, - &.cm-s-abcdef, - &.cm-s-ambiance, - &.cm-s-ayu-dark, - &.cm-s-ayu-mirage, - &.cm-s-base16-dark, - &.cm-s-bespin, - &.cm-s-blackboard, - &.cm-s-cobalt, - &.cm-s-colorforth, - &.cm-s-darcula, - &.cm-s-dracula, - &.cm-s-duotone-dark, - &.cm-s-erlang-dark, - &.cm-s-gruvbox-dark, - &.cm-s-hopscotch, - &.cm-s-icecoder, - &.cm-s-isotope, - &.cm-s-lesser-dark, - &.cm-s-liquibyte, - &.cm-s-lucario, - &.cm-s-material, - &.cm-s-material-darker, - &.cm-s-material-ocean, - &.cm-s-material-palenight, - &.cm-s-mbo, - &.cm-s-midnight, - &.cm-s-monokai, - &.cm-s-moxer, - &.cm-s-night, - &.cm-s-nord, - &.cm-s-oceanic-next, - &.cm-s-panda-syntax, - &.cm-s-paraiso-dark, - &.cm-s-pastel-on-dark, - &.cm-s-railscasts, - &.cm-s-rubyblue, - &.cm-s-seti, - &.cm-s-shadowfox, - &.cm-s-the-matrix, - &.cm-s-tomorrow-night-bright, - &.cm-s-tomorrow-night-eighties, - &.cm-s-twilight, - &.cm-s-vibrant-ink, - &.cm-s-xq-dark, - &.cm-s-yonce, - &.cm-s-zenburn { - .CodeMirror-code { - .block:not(.cm-comment) { color : magenta; } - .columnSplit { - color : black; - background-color : rgba(35,153,153,0.5); - } - .pageLine { - background-color : rgba(255,255,255,0.5); - & ~ pre.CodeMirror-line { color : black; } - } - } - } - // Themes with light backgrounds - &.cm-s-default, - &.cm-s-3024-day, - &.cm-s-ambiance-mobile, - &.cm-s-base16-light, - &.cm-s-duotone-light, - &.cm-s-eclipse, - &.cm-s-elegant, - &.cm-s-juejin, - &.cm-s-neat, - &.cm-s-neo, - &.cm-s-paraiso-lightm - &.cm-s-solarized, - &.cm-s-ssms, - &.cm-s-ttcn, - &.cm-s-xq-light, - &.cm-s-yeti { - // Future styling for themes with light backgrounds - --dummyVar : 'currently unused'; - } -} diff --git a/themes/codeMirror/customThemes/darkbrewery.js b/themes/codeMirror/darkbrewery.js similarity index 74% rename from themes/codeMirror/customThemes/darkbrewery.js rename to themes/codeMirror/darkbrewery.js index 4bc1aadd8..4dca1e466 100644 --- a/themes/codeMirror/customThemes/darkbrewery.js +++ b/themes/codeMirror/darkbrewery.js @@ -20,15 +20,15 @@ export default EditorView.theme({ backgroundColor : '#293134', whiteSpace : 'nowrap', }, - '.cm-foldGutter' : { - borderLeft : '1px solid #555', + '.cm-foldGutter' : { + borderLeft : '1px solid #555', backgroundColor : '#293134', }, - '.cm-foldGutter:hover' : { - backgroundColor : '#555', + '.cm-foldGutter:hover' : { + backgroundColor : '#555', }, - '.cm-gutterElement' : { - color: '#81969a', + '.cm-gutterElement' : { + color : '#81969a', }, '.cm-linenumber' : { padding : '0 3px 0 5px', @@ -48,41 +48,41 @@ export default EditorView.theme({ '.cm-activeLine' : { backgroundColor : '#868c9323', }, - '.cm-gutterElement.cm-activeLineGutter' : { + '.cm-gutterElement.cm-activeLineGutter' : { backgroundColor : '#868c9323', }, - '.cm-activeLine' : { + '.cm-activeLine' : { backgroundColor : '#868c9323', }, '.cm-selected' : { backgroundColor : '#d7d4f0', }, - '.cm-pageLine' : { - backgroundColor: '#7ca97c', - color: '#000', - fontWeight: 'bold', - letterSpacing: '.5px', - borderTop: '1px solid #ff0', + '.cm-pageLine' : { + backgroundColor : '#7ca97c', + color : '#000', + fontWeight : 'bold', + letterSpacing : '.5px', + borderTop : '1px solid #ff0', }, - '.cm-columnSplit' : { - backgroundColor: '#7ca97c', - color: 'black', - fontWeight: 'bold', - letterSpacing: '1px', - borderBottom: '1px solid #ff0', + '.cm-columnSplit' : { + backgroundColor : '#7ca97c', + color : 'black', + fontWeight : 'bold', + letterSpacing : '1px', + borderBottom : '1px solid #ff0', }, - '.cm-line.cm-block, .cm-line .cm-inline-block' : { - color: '#E3E3E3', + '.cm-line.cm-block, .cm-line .cm-inline-block' : { + color : '#E3E3E3', }, - '.cm-definitionList .cm-definitionTerm' : { - color: '#E3E3E3', + '.cm-definitionList .cm-definitionTerm' : { + color : '#E3E3E3', }, - '.cm-definitionList .cm-definitionColon' : { - backgroundColor: '#0000', - color: '#e3FF00', + '.cm-definitionList .cm-definitionColon' : { + backgroundColor : '#0000', + color : '#e3FF00', }, - '.cm-definitionList .cm-definitionDesc' : { - color: '#b5858d', + '.cm-definitionList .cm-definitionDesc' : { + color : '#b5858d', }, // Semantic classes diff --git a/themes/codeMirror/customThemes/darkvision.css b/themes/codeMirror/darkvision.css similarity index 91% rename from themes/codeMirror/customThemes/darkvision.css rename to themes/codeMirror/darkvision.css index 40a17ec2b..c88455c91 100644 --- a/themes/codeMirror/customThemes/darkvision.css +++ b/themes/codeMirror/darkvision.css @@ -1,3 +1,8 @@ +/*This document is old, from back when Codemirror was version 5, +if someone wants to update it, feel free, it needs to be like default.js or darkbrewery.js +Then imported in snippetbar.jsx and codeEditor.jsx. +*/ + .CodeMirror { background: #0C0C0C; color: #B9BDB6; diff --git a/themes/codeMirror/customThemes/default.js b/themes/codeMirror/default.js similarity index 100% rename from themes/codeMirror/customThemes/default.js rename to themes/codeMirror/default.js From 82aa5b76eaf3e747aae70a434cebc21bd9451645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 27 Mar 2026 20:00:01 +0100 Subject: [PATCH 42/56] erase reference to old file --- client/homebrew/editor/editor.less | 1 - 1 file changed, 1 deletion(-) diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 5dea50966..aaa8798fc 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -1,5 +1,4 @@ @import '@sharedStyles/core.less'; -@import '@themes/codeMirror/customEditorStyles.less'; :where(.editor) { position : relative; From 2dda9c98042cc5887b06407b1dbd96a898514e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sat, 28 Mar 2026 20:09:45 +0100 Subject: [PATCH 43/56] remove cm themes build system --- vitePlugins/generateAssetsPlugin.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vitePlugins/generateAssetsPlugin.js b/vitePlugins/generateAssetsPlugin.js index a5ebfc916..749cc5636 100644 --- a/vitePlugins/generateAssetsPlugin.js +++ b/vitePlugins/generateAssetsPlugin.js @@ -61,12 +61,6 @@ export function generateAssetsPlugin(isDev = false) { await fs.copy('./themes/fonts', `${buildDir}/fonts`); await fs.copy('./themes/assets', `${buildDir}/assets`); await fs.copy('./client/icons', `${buildDir}/icons`); - - // Compile CodeMirror editor themes - await fs.outputFile( - `${buildDir}/homebrew/codeMirror/editorThemes.json`, - JSON.stringify(['light', 'dark'], null, 2) - ); }, }; } From 9d2ff401fd0ef448374b997dc9c2c88237cee637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 00:20:15 +0100 Subject: [PATCH 44/56] add some comments --- client/components/codeEditor/autocompleteEmoji.js | 5 ++++- client/components/codeEditor/customFolding.js | 2 +- client/components/codeEditor/customHighlight.js | 13 +------------ .../components/codeEditor/legacyCustomHighlight.js | 5 ----- 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/client/components/codeEditor/autocompleteEmoji.js b/client/components/codeEditor/autocompleteEmoji.js index 06bbcc577..b305664c5 100644 --- a/client/components/codeEditor/autocompleteEmoji.js +++ b/client/components/codeEditor/autocompleteEmoji.js @@ -43,6 +43,10 @@ const emojiCompletionList = (context)=>{ return div; } })); + //Label is the text in the list, comes with an icon that just + //renders example text "abc", hid that with css because i didn't see other choice + //Apply is the text that is set when the choice is selected + //Info is the tooltip return { from : word.from + 1, @@ -61,7 +65,6 @@ export const autocompleteEmoji = autocompletion({ const icon = document.createElement('i'); icon.className = `emojiPreview ${emojis[e]}`; - // append directly to a DocumentFragment to return a single node const fragment = document.createDocumentFragment(); fragment.appendChild(icon); diff --git a/client/components/codeEditor/customFolding.js b/client/components/codeEditor/customFolding.js index f758fbb44..b0ce67106 100644 --- a/client/components/codeEditor/customFolding.js +++ b/client/components/codeEditor/customFolding.js @@ -1,7 +1,7 @@ import { foldService, codeFolding } from '@codemirror/language'; const pageFoldExtension = [ - foldService.of((state, lineStart)=>{ + foldService.of((state, lineStart)=>{ //tells where to fold const doc = state.doc; const matcher = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index 4b63de802..73acdf0f2 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -253,7 +253,7 @@ export function tokenizeCustomCSS(text) { return tokens; } - +//assign classes to tags provided by lezer, not unlike the function above export const customHighlightStyle = HighlightStyle.define([ { tag: tags.heading, class: 'cm-header' }, { tag: tags.heading1, class: 'cm-header cm-header-1' }, @@ -285,17 +285,6 @@ export const customHighlightStyle = HighlightStyle.define([ { 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' }, - { tag: customTags.inlineBlock, class: 'cm-inline-block' }, - { 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' }, ]); diff --git a/client/components/codeEditor/legacyCustomHighlight.js b/client/components/codeEditor/legacyCustomHighlight.js index 84b0fcb24..8e1ef5d66 100644 --- a/client/components/codeEditor/legacyCustomHighlight.js +++ b/client/components/codeEditor/legacyCustomHighlight.js @@ -50,10 +50,5 @@ export const legacyCustomHighlightStyle = HighlightStyle.define([ { 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' }, ]); From 83406479a7381471acba47c44051c3bfaf85412b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 00:20:35 +0100 Subject: [PATCH 45/56] fix historysize to be more stable --- client/components/codeEditor/codeEditor.jsx | 31 +- package-lock.json | 479 ++++++++++---------- 2 files changed, 262 insertions(+), 248 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 5ecd0c91b..6c199605d 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -16,7 +16,7 @@ import { } from '@codemirror/view'; import { EditorState, Compartment } from '@codemirror/state'; import { foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language'; -import { defaultKeymap, history, historyField, undo, redo } from '@codemirror/commands'; +import { defaultKeymap, history, undo, redo, undoDepth, redoDepth } from '@codemirror/commands'; import { languages } from '@codemirror/language-data'; import { css, cssLanguage } from '@codemirror/lang-css'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; @@ -37,6 +37,10 @@ import { customHighlightStyle, tokenizeCustomMarkdown, tokenizeCustomCSS } from import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.js'; const createHighlightPlugin = (renderer, tab)=>{ + //this function takes the custom tokens created in the tokenize function in customhighlight files + //takes the tokens defined by that function and assigns classes to them + //it also creates page number and snippet number widgets + let tokenize; if(tab === 'brewStyles') { @@ -74,19 +78,14 @@ const createHighlightPlugin = (renderer, tab)=>{ buildDecorations(view) { const decos = []; const tokens = tokenize(view.state.doc.toString()); - let pageCount = 1; let snippetCount = 0; + tokens.forEach((tok)=>{ const line = view.state.doc.line(tok.line + 1); if(tok.from != null && tok.to != null && tok.from < tok.to) { - - decos.push( - Decoration.mark({ class: `cm-${tok.type}` }).range(line.from + tok.from, line.from + tok.to) - - ); - + decos.push(Decoration.mark({ class: `cm-${tok.type}` }).range(line.from + tok.from, line.from + tok.to)); } else { decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); if(tok.type === 'pageLine' && tab === 'brewText') { @@ -324,15 +323,15 @@ const CodeEditor = forwardRef( undo : ()=>undo(viewRef.current), redo : ()=>redo(viewRef.current), - historySize : ()=>{ - const view = viewRef.current; - if(!view) return { done: 0, undone: 0 }; + historySize : () => { + const view = viewRef.current; + if (!view) return { done: 0, undone: 0 }; - const h = view.state.field(historyField, false); - if(!h) return { done: 0, undone: 0 }; - - return { done: h.done.length, undone: h.undone.length }; - }, + return { + done: undoDepth(view.state), + undone: redoDepth(view.state), + }; +}, focus : ()=>viewRef.current.focus(), })); diff --git a/package-lock.json b/package-lock.json index 0a461307a..cb1e032fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2030,13 +2030,13 @@ } }, "node_modules/@cacheable/utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.4.0.tgz", - "integrity": "sha512-PeMMsqjVq+bF0WBsxFBxr/WozBJiZKY0rUojuaCoIaKnEl3Ju1wfEwS+SV1DU/cSe8fqHIPiYJFif8T3MVt4cQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==", "dev": true, "license": "MIT", "dependencies": { - "hashery": "^1.5.0", + "hashery": "^1.5.1", "keyv": "^5.6.0" } }, @@ -2639,9 +2639,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", - "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", + "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", "dev": true, "funding": [ { @@ -4503,9 +4503,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true, "license": "MIT" }, @@ -4543,16 +4543,16 @@ } }, "node_modules/@stylistic/stylelint-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-5.0.1.tgz", - "integrity": "sha512-NaVwCNVZ2LyPA3TnUwvjO9c6P6VUjgRB8UP8SOW+cAOJBVqPPuOIDawsvvtql/LhkuR3JuTdGvr/RM3dUl8l2Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-5.1.0.tgz", + "integrity": "sha512-TFvKCbJUEWUYCD+rDv45qhnStO6nRtbBngaCblS2JGh8c95S3jJi3fIotfF6EDo4IVM15UPa65WP+kp6GNvXRA==", "dev": true, "license": "MIT", "dependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", - "postcss": "^8.5.6", + "postcss": "^8.5.8", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0", "style-search": "^0.1.0" @@ -4561,7 +4561,7 @@ "node": ">=20.19.0" }, "peerDependencies": { - "stylelint": "^17.0.0" + "stylelint": "^17.6.0" } }, "node_modules/@tybys/wasm-util": { @@ -4905,426 +4905,426 @@ } }, "node_modules/@uiw/codemirror-theme-abcdef": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-abcdef/-/codemirror-theme-abcdef-4.25.8.tgz", - "integrity": "sha512-oT9Ho+X4Ty2qlpYEc7YjHps2rwqvU2FnhZxngbk2uY/P/83kTS1QNq1DygRgxfp1g2aer7ZQk1pguzDS8GSXtA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-abcdef/-/codemirror-theme-abcdef-4.25.9.tgz", + "integrity": "sha512-F6bZcm20N3r4ZeCMdyjjII/fYHqE17sbRk6pFWfU+NPxe522A/uaRKpEaBK/iDwYqpKZgI3XUz7j3KcYzA99Mg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-abyss": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-abyss/-/codemirror-theme-abyss-4.25.8.tgz", - "integrity": "sha512-CXjPQxSv2rnG778B0S9WbTsxelxvrdRqe54Kbl+Y7U725KCu8TUm/toGSvq8YaV5uzoEcOU8NBCcQBZQKu+EZg==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-abyss/-/codemirror-theme-abyss-4.25.9.tgz", + "integrity": "sha512-zcMHX3abHsaV+IRhnHeWA5aYTP/9HTk/MR5Zh3pfwASv8YMsQlcjBva8vEZULV9pJDferW/9GXbKbbPdmceJeg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-androidstudio": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-androidstudio/-/codemirror-theme-androidstudio-4.25.8.tgz", - "integrity": "sha512-dKSvP0eF29/7WbIT6vC8Jg625a1+ktUBmDZWQ5HsVhgAk3Jlv3hekKsE4raXH0Hy1Gr+Djz36TGVS7fl/pl+1g==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-androidstudio/-/codemirror-theme-androidstudio-4.25.9.tgz", + "integrity": "sha512-HPIWpEC9ElhpJ2NZUKB6z+eStQzFDrkIGW9pTJxYHSCv2Los7FgD/R6eGqjTS4LVlBf9FR+KU/5E6dLT8DQHlw==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-andromeda": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-andromeda/-/codemirror-theme-andromeda-4.25.8.tgz", - "integrity": "sha512-qzp0c7+PzaNqtKskvcplkNxNKtB9nRtDTy8kpaKKjcrj6fMpj0hu0yfUxPXhnZCKBwQjtszuCxAcxkuNTkDsFQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-andromeda/-/codemirror-theme-andromeda-4.25.9.tgz", + "integrity": "sha512-JSqK8/sVFbFfTyv/okaT4c8suulf9zasqd4YBuTSkPZo+Sd/50blxMSVe5IWwDSiW5hkiupb7FC2IP1siHhncw==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-atomone": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-atomone/-/codemirror-theme-atomone-4.25.8.tgz", - "integrity": "sha512-gt3uSsI9KWYRku5rMz6opUOtH73A12jX/T9/C70rf7z5jclP5I2OWU1uGzMzE+Zf9gDou6F/HVJqQmVMcD2fKQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-atomone/-/codemirror-theme-atomone-4.25.9.tgz", + "integrity": "sha512-EXG/+p+Y9j/StU2yAtz/+JZj/8WaSGqwjsad79CSBgpHrSU0ERzv4urYWXgEmLTKKkFimwTigy7qOJlLAwkN2A==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-aura": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-aura/-/codemirror-theme-aura-4.25.8.tgz", - "integrity": "sha512-FwY++XuhQU/sKZN1AKxDvu44JM0dEBSxUIvR1Wr5p8MpNY0ctHC9Y3yb1pEM4Dysmf98zTVg7wajafHhg6HXZg==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-aura/-/codemirror-theme-aura-4.25.9.tgz", + "integrity": "sha512-cJyInS81wh0lWYs1XDiyFSxCCXrJ+4qifBsDHSYELdLgbnr441T3Kr6a9lyUobtL4DZVaIaCKE9rajrFdJIeAw==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-basic": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-basic/-/codemirror-theme-basic-4.25.8.tgz", - "integrity": "sha512-EvY7I9RoT8HL1IynuL/nndPmCBcrciq3P0RdBabA9fsKJ8ThviJSWxlYY2NziYs0prlEZdorfC8KHfyfWSu/LQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-basic/-/codemirror-theme-basic-4.25.9.tgz", + "integrity": "sha512-40x+anangMmPziZSeEcg6P5YDLn7fF1ioS5VxEPXMGUTbikv0au4PXVNsf7CtP0VwO4MmGt87zZI6rQIexEP3w==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-bbedit": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-bbedit/-/codemirror-theme-bbedit-4.25.8.tgz", - "integrity": "sha512-uzU7tiMGtf3j7IjMdNRq7Ewi3SBOG8wM5EILE9CHs2CLpitOxlJMxWUOg59folxVS2Vvnd783CqsBXOEeUpZ9A==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-bbedit/-/codemirror-theme-bbedit-4.25.9.tgz", + "integrity": "sha512-SGXQ0tLsqcRvxXCrdeU/MiQ3liNKvr8DCxaSt4N5LP7EPGO94ebuvba0F+H/3LpeJJrn5Xq0FuhaPlMYJ10RXg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-bespin": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-bespin/-/codemirror-theme-bespin-4.25.8.tgz", - "integrity": "sha512-SHAR/qWYxA6MQYytje8Y9t5Fdp/7o3VA+FFvqoKbqTlZArtS8MelVayri5/zCvpdwet2igl+LvbQZ/zK9jlsTA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-bespin/-/codemirror-theme-bespin-4.25.9.tgz", + "integrity": "sha512-Zr35B1FpM+VMIoHot397GP/dQBWkFz6SlFqf3JSX6wlwgy2d4ot3YF9fBglGkM3C3ITmkBBQRnlvELwke+dXBg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-console": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-console/-/codemirror-theme-console-4.25.8.tgz", - "integrity": "sha512-zypacwtTAAax6QMst+c2BKtQsj6/+or+8lCmxCWcUMAgD2XXwXZlca0jLfCWwg/2jC26sxMRentPYul5/zQL1w==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-console/-/codemirror-theme-console-4.25.9.tgz", + "integrity": "sha512-vhN9QKStneKyiNzu+DuA5JOss9WfzecuDjvmEYApQL9zvRmNUAP6La0C2vpZCji1Y23OAFZUJvTU+eKbept3cw==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" } }, "node_modules/@uiw/codemirror-theme-copilot": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-copilot/-/codemirror-theme-copilot-4.25.8.tgz", - "integrity": "sha512-gjszhGBwaecw9fNcqIL5hrEQgpQdfEHdu8MU4VIjg6VGnribdeZYMrqs4DiZj1I0e7dNzlb+4AopLzw5RtmJyA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-copilot/-/codemirror-theme-copilot-4.25.9.tgz", + "integrity": "sha512-MLBXBEp+jDQC+BbFUQxxwsOKvhbCsIpIjwBgNfR4KKKQxD6tF6u+CE7ERcrRWJ6cCV2lDrs1IZRZGPQCSpHMIA==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-darcula": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-darcula/-/codemirror-theme-darcula-4.25.8.tgz", - "integrity": "sha512-oggD67Ag2k4HULP5Hr3GBfoW3KaCprrOgU2TkC3qfLiwRyBdxZ4/8EFPHJFNSiPA5OBPugRn0q1/0eHOf9aDNw==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-darcula/-/codemirror-theme-darcula-4.25.9.tgz", + "integrity": "sha512-lrex1DXg/mx2BX1UtnyFlat7w6c3RyE5GMvyR8uPfXNAXMUEKjYxNRdUuQ9WGlOMzQZ3x+UbKnUZd/r6AmXwsw==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-dracula": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-dracula/-/codemirror-theme-dracula-4.25.8.tgz", - "integrity": "sha512-1AZSkk6FE+bt/6elp4cm02/yxYyZAxPJ6zk9ZC1P68/g4Haf4vWUuuL1xqOHroI1HlSvrM7cmlnxQFYS+JavEg==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-dracula/-/codemirror-theme-dracula-4.25.9.tgz", + "integrity": "sha512-0VTnpPCHPc+7LqYsQOX6nvW32XiiT+O6kJjReUbV7Eio3vPHsb+b9P4DKhz4AAvIIYMxmHkMuautHKuWktFXSg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-duotone": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-duotone/-/codemirror-theme-duotone-4.25.8.tgz", - "integrity": "sha512-f6CkWEil087ocEDlnCjSoOIAVsOF7bAMQRWhVBqE51iAD4DbuAxKzHkvEa58i8PrQPohPHUfUS9yTi0n4DY1WQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-duotone/-/codemirror-theme-duotone-4.25.9.tgz", + "integrity": "sha512-6IPZncdrtcgnU1EtQ1/IzaULZ+Jw5uAeVeQCae+rFBnW/m6Q8nWB8+iVnk8kCevgjT5ScZmRd9h4yqtSeJbUwQ==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-eclipse": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-eclipse/-/codemirror-theme-eclipse-4.25.8.tgz", - "integrity": "sha512-Un6yA6uJ46Y7MKdTtOkekJsMYCjzK3v7EI/+FDvTgIU9bvil9ZIcuGicP+mKioHyfztyx5TBx3HrbhoC7ybkdQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-eclipse/-/codemirror-theme-eclipse-4.25.9.tgz", + "integrity": "sha512-0pT0vRyLAotj5UjIZbHSmsZ8oz7l8IU5bhx5p7MDrTOdi73ZjyTsG4YsDzSXndERnfgkBbZJrlZiExBkXnhtUA==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-github": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.25.8.tgz", - "integrity": "sha512-kTZsf2PmOCHBuudB50BX1kAODmErzks6lxxhbHj76KQ+p4eizu0giowV1LoyY/FCLLqRjIe77tE31hvo8agasg==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.25.9.tgz", + "integrity": "sha512-AGpTamNiySKNzq3Jc7QjpwgQRVaHUaBtmOKiUDghYSfEGjsc5uW4NUW70sSU3BnkGv+lCTUnF3175KM24BWZbw==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-gruvbox-dark": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-gruvbox-dark/-/codemirror-theme-gruvbox-dark-4.25.8.tgz", - "integrity": "sha512-vo3rbo0R1niTg0BumjG6/vYYyxcpLORmcfqHaA2jj3XFvIZEz6xMQyLFsW1iGzU9kUevIEGvWw2CKebwa0UeDA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-gruvbox-dark/-/codemirror-theme-gruvbox-dark-4.25.9.tgz", + "integrity": "sha512-9qIa1z4zwubN2kHAs+lJvdrmMMMf69JeyVPAwSoNaImL8wUQ/J3291qcfuoZjv8RsqSzrKTgxqLHtkAhB7xcwg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-kimbie": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-kimbie/-/codemirror-theme-kimbie-4.25.8.tgz", - "integrity": "sha512-Zy4DSIgDEyW/ctfmnJT6DJ+GPF16a30x4k2i3NaItUuuzA4S64kYlRcekptLUgjRijOFYMZD6IlhrTTM+wRDmA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-kimbie/-/codemirror-theme-kimbie-4.25.9.tgz", + "integrity": "sha512-zLjT7MkotuT07rx4ZPZOM1/H+sa+kCmJr5BDu2ASNpF7Sj4w0cTNcAyxKHj+N6LcgIM8PICxqB97CJhlurNTBA==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-material": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-material/-/codemirror-theme-material-4.25.8.tgz", - "integrity": "sha512-VlSkd2ZgQ9YXkW0x3fPssyngrPVhy6XAx84wWO55yU/VQHGIHxTr1/0gF2eEcKOy4M/7aQ4EtYzAVVJlSvrUoA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-material/-/codemirror-theme-material-4.25.9.tgz", + "integrity": "sha512-6f2x+gmj2hHagqy6VkpnPbK7SWyP6kKruGgqpyIy09/f9pAUCqkW8mRY5ZEr28tA+YEGQaSY0Z2IBCHl8OKJog==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-monokai": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-monokai/-/codemirror-theme-monokai-4.25.8.tgz", - "integrity": "sha512-Y+Vfa23rj95f6iuD4IGYQuSwjdhqVtfw3vGWHX2bcS7/h/m/GdCsdtmMVwuMy+uCwTJAmOdMxPJh8a5E1f6UaQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-monokai/-/codemirror-theme-monokai-4.25.9.tgz", + "integrity": "sha512-qKWRZOGpBCasZJdYU+SsXd92TjncF3QYHpraCPe29bxN22jeIxi2UC4MCuJHwa8hHljHOCSdx1XG/GuUMn7XiQ==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-monokai-dimmed": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-monokai-dimmed/-/codemirror-theme-monokai-dimmed-4.25.8.tgz", - "integrity": "sha512-wTRj/OKnULUhjYMvf/HGfuOA1DDBpLIh+vjBP+/SN/3ZCElKgy2AqVXw86D2JYrb3MeEwIITL2s5CovnZfpjaA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-monokai-dimmed/-/codemirror-theme-monokai-dimmed-4.25.9.tgz", + "integrity": "sha512-6/Z9tF4UFngaXifAKC4DI2l61G3rtcWOxvCwgs5zzNVMTciUI+Bl/K7eCvjf2y0LfLmK8Ovob8ODDBcVgwzp5g==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-noctis-lilac": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-noctis-lilac/-/codemirror-theme-noctis-lilac-4.25.8.tgz", - "integrity": "sha512-ry0yLvGtDrmctDCrq3Dqwk55bBKLEf1ZUcNjxK8M5HKfkCBga18z5n6cQfqizpoEHaM9uvwzaN+7VhJeXOHyZQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-noctis-lilac/-/codemirror-theme-noctis-lilac-4.25.9.tgz", + "integrity": "sha512-HXjQutWsVYfiBM6ze4SomXmSJNzYYJ/fUYJ3TJLhnp5cjIPNBsMsgOAaWp3L64xUqqorb0+1y6kdmUKxTEp6rQ==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-nord": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-nord/-/codemirror-theme-nord-4.25.8.tgz", - "integrity": "sha512-acHLqao6dT+4xtTUH1YzdUx1NjNsvNSSYYxrB/S5MaCrmFDRSRqWCjXpFdhR0EEidghm9GzQGyzBhIT+99kuMA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-nord/-/codemirror-theme-nord-4.25.9.tgz", + "integrity": "sha512-5c568xmMidwICADxACB1zIhKoEgqbdVrdeOUZ2p5pE6NNKGR4ATzk9OSqhvr1ZhZPNOktxqSLLRzihFaZG0bDQ==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-okaidia": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-okaidia/-/codemirror-theme-okaidia-4.25.8.tgz", - "integrity": "sha512-zk1kJ+oRLXBPv6sipQz7ILaQc4LLEvRMUCeBynF1RRubcA2/BTKjKE/++QikSLw4ckmniBCqKSv62PcSZWiThQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-okaidia/-/codemirror-theme-okaidia-4.25.9.tgz", + "integrity": "sha512-lIJFUs/ws0prQz+dVo5ZIp0o6vxW7p6nf8iRFETN5S3KA3nJUR2cTF6u8mYLFwHMrFs2eReRsFyH94wjmuPWvg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-quietlight": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-quietlight/-/codemirror-theme-quietlight-4.25.8.tgz", - "integrity": "sha512-exClw88eGUgTta8rLRlIJA4sjS6VoHKi1yFsIhqOXMrfgFhMtxNvZdIYPzePv+JsGizRJF/8d8KMMCmBl7i6ag==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-quietlight/-/codemirror-theme-quietlight-4.25.9.tgz", + "integrity": "sha512-BWFcFb3WHTCVROkjExh/TMMTJ5SNcDafaVEIwneKypiHoTJoIY6RlSRBj6GA3O5IgKdrGmhje87s0Gx2OLIndg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-red": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-red/-/codemirror-theme-red-4.25.8.tgz", - "integrity": "sha512-dVhxhR+QO//soEVrVrSnK2OSy/7pxu/P77LFLe1/PwWNmFM3u/OHfPmc4NTo2B/xManT0AhIcO47/SRcCcjHRA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-red/-/codemirror-theme-red-4.25.9.tgz", + "integrity": "sha512-pSOs2ByCVGJXbABhfTEU4TlRh/Wa9BJlDUa219iq1jO3AUDUM/LIPNLhmQvMtOituMX8WKJprspBrDcveXsisg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-solarized": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-solarized/-/codemirror-theme-solarized-4.25.8.tgz", - "integrity": "sha512-/KWcpUx+hXaiWGWCSzpFQiwZ0gXNKTI009t9/uEYmlPJnh0Mc8GZA6rgJH/8xuvqCaf20hGEZaoJr2l36PO0JA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-solarized/-/codemirror-theme-solarized-4.25.9.tgz", + "integrity": "sha512-axUgU9+3JKXW83F+te454qcyTmQAm0+2Fxv0yoegiH6bdl7DjFq/lNVGGZtLwN47AQCj2Qwrheeet2t3GbY9VQ==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-sublime": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-sublime/-/codemirror-theme-sublime-4.25.8.tgz", - "integrity": "sha512-OGExIcxt3gYGlPvaXx0DhgVdEFz8nJgMqeVBYJMcB4BlqrYQb+okLyraWoohBvxWf+vj1YbUvyFJi/DDEkIlPQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-sublime/-/codemirror-theme-sublime-4.25.9.tgz", + "integrity": "sha512-/Ha1K3P0sqFWrsYtCu6Uih/t8C73dVY6m5rObjCnnokr//kOusKwlwt1fJiEFdIcSKlH2WBIvW5tb75tcYitnw==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-tokyo-night": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night/-/codemirror-theme-tokyo-night-4.25.8.tgz", - "integrity": "sha512-jADC+TOj8FOSkvqPpw8CYa0IP07WWf12ASg1vtirC74iKcwhZE4a0XzvRnYAKHhxAzpCmw4vivLGK/KJT/as5A==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night/-/codemirror-theme-tokyo-night-4.25.9.tgz", + "integrity": "sha512-NkSqguMpzRjsRBbTIfOrGS35tQkE3K8AAetZHlbRZC7fnI52RreZ11X41cOYrc/Dapt8xqUPlhlvclymGFgy8g==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-tokyo-night-day": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night-day/-/codemirror-theme-tokyo-night-day-4.25.8.tgz", - "integrity": "sha512-FuNoKoF1DNxoWjLObXD51d30Zb+5c8Ki4Av0RtL7SxgGqbIklGmM9UGVqz0PTE+N7EAQOL/W+HQpyunclMtKCQ==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night-day/-/codemirror-theme-tokyo-night-day-4.25.9.tgz", + "integrity": "sha512-1ziFletBO6tfRtX4FVWij1wYIf95uYi54dgnMz5CXe4A4u710rJ3uS3C4ijlnclRbwHjNTqtrMWNuicKDBMsPg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-tokyo-night-storm": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night-storm/-/codemirror-theme-tokyo-night-storm-4.25.8.tgz", - "integrity": "sha512-ktBsbSiJI+Lyy/LV5PupNud82A/LU22aTu00QaV8L2cKfv8Expe45UwS4d+O5ijhAZZPLMgnNkkDEinSslA57Q==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night-storm/-/codemirror-theme-tokyo-night-storm-4.25.9.tgz", + "integrity": "sha512-qz8Vg+ze12TuLk+fqwx3oga3H6rDE+81PpKMGLfbI1BwPDgg7GZGTGrWZoN1Bpf6EV0dA4WO8K6lbzFhlS6S1Q==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-tomorrow-night-blue": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tomorrow-night-blue/-/codemirror-theme-tomorrow-night-blue-4.25.8.tgz", - "integrity": "sha512-qMfgfz7bdZsFqR8Xj905kkKEzqxrMBgyJgpBlhs57Cix84N1bN6GdhRcbqlnJJxwFBmB/uFtC+lyiPysqlIUfA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tomorrow-night-blue/-/codemirror-theme-tomorrow-night-blue-4.25.9.tgz", + "integrity": "sha512-iG2wCXO/rkJIrvW7rJY7Ehh4yushw8X4vQnstjArxofR6uNrE9ay3Ut7M0cxrwY7z8YIU5f7NQFODE/h3HNmVA==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-vscode": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.25.8.tgz", - "integrity": "sha512-J4fNBFBQK1R1VV4nY+gJ4RRzGDUufe3JDtbeZVehjx34OAbqux8GsD08CTMQReyxJr5DHXcQ/eszQKbESP1J6Q==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.25.9.tgz", + "integrity": "sha512-9KTnScHTSk97yGnyNYvDm6QZuBCdbO1OzMQ5bHtoBSPSVtH0LjY3bS6CXsBagb22v8OLPx/XwrBYOjKFp409CQ==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-white": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-white/-/codemirror-theme-white-4.25.8.tgz", - "integrity": "sha512-BzECjiT18zY1bqdjOmXE9dTHLfZfVJeQ0DHdN/MhV/GU9n6kHv6qh5zB84uBfYtVTcDRBTw8vNiBwH7NYWuzGg==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-white/-/codemirror-theme-white-4.25.9.tgz", + "integrity": "sha512-75PHfVejBvgF1EbponpEOgND/T6MJYZ673aODPuR7mKPZNfn8649qOSrp7wvMN/NEZ+W5CxV3U7tb9MQWPcM4A==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-theme-xcode": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-xcode/-/codemirror-theme-xcode-4.25.8.tgz", - "integrity": "sha512-x8qlXckT4ZcvSEkxpqwjgu87ZnNBkAGoap03pMNTz/cnRcNY6ZulCXGz7ZQejTOuiWowjmCwjkIGTpEbDj2rzA==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-xcode/-/codemirror-theme-xcode-4.25.9.tgz", + "integrity": "sha512-sMiDpOiW0iiNsLyqL1Vx6wZKOSoVUNfmWbBDtaYzlkRcKzkyJQp68cPIq5VG8Mhl2z+PX5cPbOA0nZEegNLicA==", "license": "MIT", "dependencies": { - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-themes": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.8.tgz", - "integrity": "sha512-U6ZSO9A+nsN8zvNddtwhxxpi33J9okb4Li9HdhAItApKjYM22IgC8XSpGfs+ABGfsp1u6NhDSfBR9vAh3oTWXg==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.9.tgz", + "integrity": "sha512-DAHKb/L9ELwjY4nCf/MP/mIllHOn4GQe7RR4x8AMJuNeh9nGRRoo1uPxrxMmUL/bKqe6kDmDbIZ2AlhlqyIJuw==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -5341,47 +5341,47 @@ } }, "node_modules/@uiw/codemirror-themes-all": { - "version": "4.25.8", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes-all/-/codemirror-themes-all-4.25.8.tgz", - "integrity": "sha512-YacF3Wxkj/T9m8hNjlUfhGK9OzaRsn4wK7G18F5j9HEK02oCw2EnPWxmsrqBY2USounUmYU05OAYtFz1vwrBFw==", + "version": "4.25.9", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes-all/-/codemirror-themes-all-4.25.9.tgz", + "integrity": "sha512-OVcGb6dkgJ8NgcHFvSQkRLHHIRswZhBKK0XZZzRVMxDnCIXfmnDfeChNoKjuzwBr+C0jS7UAAqrWbcqrLj3mhg==", "license": "MIT", "dependencies": { - "@uiw/codemirror-theme-abcdef": "4.25.8", - "@uiw/codemirror-theme-abyss": "4.25.8", - "@uiw/codemirror-theme-androidstudio": "4.25.8", - "@uiw/codemirror-theme-andromeda": "4.25.8", - "@uiw/codemirror-theme-atomone": "4.25.8", - "@uiw/codemirror-theme-aura": "4.25.8", - "@uiw/codemirror-theme-basic": "4.25.8", - "@uiw/codemirror-theme-bbedit": "4.25.8", - "@uiw/codemirror-theme-bespin": "4.25.8", - "@uiw/codemirror-theme-console": "4.25.8", - "@uiw/codemirror-theme-copilot": "4.25.8", - "@uiw/codemirror-theme-darcula": "4.25.8", - "@uiw/codemirror-theme-dracula": "4.25.8", - "@uiw/codemirror-theme-duotone": "4.25.8", - "@uiw/codemirror-theme-eclipse": "4.25.8", - "@uiw/codemirror-theme-github": "4.25.8", - "@uiw/codemirror-theme-gruvbox-dark": "4.25.8", - "@uiw/codemirror-theme-kimbie": "4.25.8", - "@uiw/codemirror-theme-material": "4.25.8", - "@uiw/codemirror-theme-monokai": "4.25.8", - "@uiw/codemirror-theme-monokai-dimmed": "4.25.8", - "@uiw/codemirror-theme-noctis-lilac": "4.25.8", - "@uiw/codemirror-theme-nord": "4.25.8", - "@uiw/codemirror-theme-okaidia": "4.25.8", - "@uiw/codemirror-theme-quietlight": "4.25.8", - "@uiw/codemirror-theme-red": "4.25.8", - "@uiw/codemirror-theme-solarized": "4.25.8", - "@uiw/codemirror-theme-sublime": "4.25.8", - "@uiw/codemirror-theme-tokyo-night": "4.25.8", - "@uiw/codemirror-theme-tokyo-night-day": "4.25.8", - "@uiw/codemirror-theme-tokyo-night-storm": "4.25.8", - "@uiw/codemirror-theme-tomorrow-night-blue": "4.25.8", - "@uiw/codemirror-theme-vscode": "4.25.8", - "@uiw/codemirror-theme-white": "4.25.8", - "@uiw/codemirror-theme-xcode": "4.25.8", - "@uiw/codemirror-themes": "4.25.8" + "@uiw/codemirror-theme-abcdef": "4.25.9", + "@uiw/codemirror-theme-abyss": "4.25.9", + "@uiw/codemirror-theme-androidstudio": "4.25.9", + "@uiw/codemirror-theme-andromeda": "4.25.9", + "@uiw/codemirror-theme-atomone": "4.25.9", + "@uiw/codemirror-theme-aura": "4.25.9", + "@uiw/codemirror-theme-basic": "4.25.9", + "@uiw/codemirror-theme-bbedit": "4.25.9", + "@uiw/codemirror-theme-bespin": "4.25.9", + "@uiw/codemirror-theme-console": "4.25.9", + "@uiw/codemirror-theme-copilot": "4.25.9", + "@uiw/codemirror-theme-darcula": "4.25.9", + "@uiw/codemirror-theme-dracula": "4.25.9", + "@uiw/codemirror-theme-duotone": "4.25.9", + "@uiw/codemirror-theme-eclipse": "4.25.9", + "@uiw/codemirror-theme-github": "4.25.9", + "@uiw/codemirror-theme-gruvbox-dark": "4.25.9", + "@uiw/codemirror-theme-kimbie": "4.25.9", + "@uiw/codemirror-theme-material": "4.25.9", + "@uiw/codemirror-theme-monokai": "4.25.9", + "@uiw/codemirror-theme-monokai-dimmed": "4.25.9", + "@uiw/codemirror-theme-noctis-lilac": "4.25.9", + "@uiw/codemirror-theme-nord": "4.25.9", + "@uiw/codemirror-theme-okaidia": "4.25.9", + "@uiw/codemirror-theme-quietlight": "4.25.9", + "@uiw/codemirror-theme-red": "4.25.9", + "@uiw/codemirror-theme-solarized": "4.25.9", + "@uiw/codemirror-theme-sublime": "4.25.9", + "@uiw/codemirror-theme-tokyo-night": "4.25.9", + "@uiw/codemirror-theme-tokyo-night-day": "4.25.9", + "@uiw/codemirror-theme-tokyo-night-storm": "4.25.9", + "@uiw/codemirror-theme-tomorrow-night-blue": "4.25.9", + "@uiw/codemirror-theme-vscode": "4.25.9", + "@uiw/codemirror-theme-white": "4.25.9", + "@uiw/codemirror-theme-xcode": "4.25.9", + "@uiw/codemirror-themes": "4.25.9" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" @@ -6191,9 +6191,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.10", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", - "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", + "version": "2.10.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", + "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -6246,9 +6246,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -7145,9 +7145,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.323", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.323.tgz", - "integrity": "sha512-oQm+FxbazvN2WICCbvJgj3IYPKV8awip57+W5VP+Aatk4kFU4pDYCPHZOX22Z27zpw8uttBehEqgK+VTJAYrVw==", + "version": "1.5.328", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", + "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", "license": "ISC" }, "node_modules/emittery": { @@ -8526,9 +8526,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -8630,9 +8630,9 @@ } }, "node_modules/globby": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.1.tgz", - "integrity": "sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.0.tgz", + "integrity": "sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -11091,9 +11091,9 @@ } }, "node_modules/mongoose": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.3.2.tgz", - "integrity": "sha512-4hMJA4vLAeNG6LnDwJImPYM+uJMVw+sFs7jtS/91OKOT7rzv19ytAAeswtAdWoinVBUVrzmClL/ZoPhcU5RL8w==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.3.3.tgz", + "integrity": "sha512-sfv5LOIPWeN5o/281kp4Rx9ZnuXb0g8CtvBTi7trYQs2PYYx8LWXegXxG3ar7VEns1o+d4h9LI/Dtc7dTTyYmA==", "license": "MIT", "dependencies": { "kareem": "3.2.0", @@ -11111,6 +11111,22 @@ "url": "https://opencollective.com/mongoose" } }, + "node_modules/mongoose/node_modules/gcp-metadata": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", + "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/mongoose/node_modules/mongodb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz", @@ -11831,9 +11847,9 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz", + "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==", "license": "MIT", "funding": { "type": "opencollective", @@ -13276,9 +13292,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.5.0.tgz", - "integrity": "sha512-o/NS6zhsPZFmgUm5tXX4pVNg1XDOZSlucLdf2qow/lVn4JIyzZIQ5b3kad1ugqUj3GSIgr2u5lQw7X8rjqw33g==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.6.0.tgz", + "integrity": "sha512-tokrsMIVAR9vAQ/q3UVEr7S0dGXCi7zkCezPRnS2kqPUulvUh5Vgfwngrk4EoAoW7wnrThqTdnTFN5Ra7CaxIg==", "dev": true, "funding": [ { @@ -13295,7 +13311,7 @@ "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-syntax-patches-for-csstree": "^1.0.29", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", "@csstools/selector-resolve-nested": "^4.0.0", @@ -13314,7 +13330,6 @@ "html-tags": "^5.1.0", "ignore": "^7.0.5", "import-meta-resolve": "^4.2.0", - "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "mathml-tag-names": "^4.0.0", "meow": "^14.1.0", @@ -13329,7 +13344,7 @@ "supports-hyperlinks": "^4.4.0", "svg-tags": "^1.0.0", "table": "^6.9.0", - "write-file-atomic": "^7.0.0" + "write-file-atomic": "^7.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" @@ -13414,14 +13429,14 @@ } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.21", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.21.tgz", - "integrity": "sha512-2u7cJfSf7Th7NxEk/VzQjnPoglok2YCsevS7TSbJjcDQWJPbqUUnSYtriHSvtnq+fRZHy1s0ugk4ApnQyhPGoQ==", + "version": "6.1.22", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.22.tgz", + "integrity": "sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug==", "dev": true, "license": "MIT", "dependencies": { - "cacheable": "^2.3.3", - "flatted": "^3.4.1", + "cacheable": "^2.3.4", + "flatted": "^3.4.2", "hookified": "^1.15.0" } }, @@ -14025,9 +14040,9 @@ } }, "node_modules/undici": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz", - "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", "dev": true, "license": "MIT", "engines": { From 8b347bafbc7e170d767427296248c525aad5ff74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 00:21:30 +0100 Subject: [PATCH 46/56] lint --- .../components/codeEditor/autocompleteEmoji.js | 2 +- client/components/codeEditor/codeEditor.jsx | 18 +++++++++--------- .../components/codeEditor/customHighlight.js | 2 +- .../codeEditor/legacyCustomHighlight.js | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/components/codeEditor/autocompleteEmoji.js b/client/components/codeEditor/autocompleteEmoji.js index b305664c5..7268dcc73 100644 --- a/client/components/codeEditor/autocompleteEmoji.js +++ b/client/components/codeEditor/autocompleteEmoji.js @@ -43,7 +43,7 @@ const emojiCompletionList = (context)=>{ return div; } })); - //Label is the text in the list, comes with an icon that just + //Label is the text in the list, comes with an icon that just //renders example text "abc", hid that with css because i didn't see other choice //Apply is the text that is set when the choice is selected //Info is the tooltip diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 6c199605d..0c06170d6 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -174,7 +174,7 @@ const CodeEditor = forwardRef( highlightActiveLine(), highlightActiveLineGutter(), - highlightCompartment.of([customHighlightPlugin,highlightExtension]), + highlightCompartment.of([customHighlightPlugin, highlightExtension]), themeCompartment.of(themeExtension), ...(tab !== 'brewStyles' ? [autocompleteEmoji] : []), search(), @@ -323,15 +323,15 @@ const CodeEditor = forwardRef( undo : ()=>undo(viewRef.current), redo : ()=>redo(viewRef.current), - historySize : () => { - const view = viewRef.current; - if (!view) return { done: 0, undone: 0 }; + historySize : ()=>{ + const view = viewRef.current; + if(!view) return { done: 0, undone: 0 }; - return { - done: undoDepth(view.state), - undone: redoDepth(view.state), - }; -}, + return { + done : undoDepth(view.state), + undone : redoDepth(view.state), + }; + }, focus : ()=>viewRef.current.focus(), })); diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index 73acdf0f2..a91f25281 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -269,7 +269,7 @@ export const customHighlightStyle = HighlightStyle.define([ { 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' }, diff --git a/client/components/codeEditor/legacyCustomHighlight.js b/client/components/codeEditor/legacyCustomHighlight.js index 8e1ef5d66..cccb6647b 100644 --- a/client/components/codeEditor/legacyCustomHighlight.js +++ b/client/components/codeEditor/legacyCustomHighlight.js @@ -34,7 +34,7 @@ export const legacyCustomHighlightStyle = HighlightStyle.define([ { 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' }, From eb99af2735dea81b4588f836d95f4e4e34a2aa71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 00:33:07 +0100 Subject: [PATCH 47/56] tab size changed to 2 --- client/components/codeEditor/codeEditor.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less index 6415753ae..5fba31f08 100644 --- a/client/components/codeEditor/codeEditor.less +++ b/client/components/codeEditor/codeEditor.less @@ -19,6 +19,10 @@ font-family: monospace; height: 100%; width:100%; + + .cm-content { + tab-size:2 !important; + } @media screen and (pointer: coarse) { font-size: 16px; From 0fcf6dfe899005633865994fe52536472d93788d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 00:49:08 +0100 Subject: [PATCH 48/56] revert tab function to live behavior --- client/components/codeEditor/codeEditor.jsx | 6 ++++++ client/components/codeEditor/customKeyMap.js | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 0c06170d6..ad816492c 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -22,6 +22,11 @@ import { css, cssLanguage } from '@codemirror/lang-css'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { autocompleteEmoji } from './autocompleteEmoji.js'; import { searchKeymap, search } from '@codemirror/search'; +import {closeBrackets} from "@codemirror/autocomplete" + +const customClose = closeBrackets({ + brackets: ["()", "[]", "{{}}"] +}) import * as themesImport from '@uiw/codemirror-themes-all'; import defaultCM5Theme from '@themes/codeMirror/default.js'; @@ -182,6 +187,7 @@ const CodeEditor = forwardRef( customKeymap, drawSelection(), EditorState.allowMultipleSelections.of(true), + customClose, ]; }; diff --git a/client/components/codeEditor/customKeyMap.js b/client/components/codeEditor/customKeyMap.js index 81cb88e25..60a0bb60d 100644 --- a/client/components/codeEditor/customKeyMap.js +++ b/client/components/codeEditor/customKeyMap.js @@ -209,8 +209,8 @@ const newPage = (view)=>{ export default keymap.of([ { key: 'Tab', run: insertTabAtCursor }, - { key: 'Shift-Tab', run: indentMore }, - { key: 'Mod-Shift-Tab', run: indentLess }, + //{ key: 'Shift-Tab', run: indentMore }, + { key: 'Shift-Tab', run: indentLess }, { key: 'Mod-b', run: makeBold }, { key: 'Mod-i', run: makeItalic }, { key: 'Mod-u', run: makeUnderline }, From 239029e1967cef2740356d872965ba63e802a04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 00:59:31 +0100 Subject: [PATCH 49/56] autto close tags --- client/components/codeEditor/codeEditor.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index ad816492c..6188994ac 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -20,13 +20,12 @@ import { defaultKeymap, history, undo, redo, undoDepth, redoDepth } from '@codem import { languages } from '@codemirror/language-data'; import { css, cssLanguage } from '@codemirror/lang-css'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; +import {html} from "@codemirror/lang-html" import { autocompleteEmoji } from './autocompleteEmoji.js'; import { searchKeymap, search } from '@codemirror/search'; import {closeBrackets} from "@codemirror/autocomplete" -const customClose = closeBrackets({ - brackets: ["()", "[]", "{{}}"] -}) +const customClose = closeBrackets({ brackets: ["()", "[]", "{{}}"] }); import * as themesImport from '@uiw/codemirror-themes-all'; import defaultCM5Theme from '@themes/codeMirror/default.js'; @@ -160,7 +159,7 @@ const CodeEditor = forwardRef( const customHighlightPlugin = createHighlightPlugin(renderer, tab); - const languageExtension = language === 'css' ? [css(), cssLanguage] : markdown({ base: markdownLanguage, codeLanguages: languages }); + const languageExtension = language === 'css' ? css() : [markdown({ base: markdownLanguage, codeLanguages: languages }), html({ autoCloseTags: true })]; const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; return [ From 8442fdfe0a98442adc280baf1a456b3ba30eba39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 01:02:34 +0100 Subject: [PATCH 50/56] make sure selection is visible --- themes/codeMirror/default.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/themes/codeMirror/default.js b/themes/codeMirror/default.js index 5c65d94f1..0625ded76 100644 --- a/themes/codeMirror/default.js +++ b/themes/codeMirror/default.js @@ -37,7 +37,10 @@ export default EditorView.theme({ caretColor : 'transparent', }, '.cm-activeLine' : { - backgroundColor : '#e8f2ff', + backgroundColor : '#becee374', + }, + '.cm-gutterElement.cm-activeLineGutter' : { + backgroundColor : '#becee374', }, '.cm-selected' : { backgroundColor : '#d7d4f0', From 2af063ac88a3e7e7091933a726424d125e19d157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 01:03:18 +0100 Subject: [PATCH 51/56] add dropcursor extension --- client/components/codeEditor/codeEditor.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 6188994ac..210ead6dd 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -13,6 +13,7 @@ import { ViewPlugin, WidgetType, drawSelection, + dropCursor, } from '@codemirror/view'; import { EditorState, Compartment } from '@codemirror/state'; import { foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language'; @@ -187,6 +188,7 @@ const CodeEditor = forwardRef( drawSelection(), EditorState.allowMultipleSelections.of(true), customClose, + dropCursor(), ]; }; From 1d02fb95659a6ec72c7dd3512db227dcf4b1a8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 01:12:21 +0100 Subject: [PATCH 52/56] simplify page count --- client/components/codeEditor/codeEditor.jsx | 20 ++------------------ client/homebrew/editor/editor.less | 5 +++-- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 210ead6dd..9c97ba0e3 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -53,22 +53,6 @@ const createHighlightPlugin = (renderer, tab)=>{ } else { tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; } - /* eslint-disable no-restricted-syntax */ - class countWidget extends WidgetType { - constructor(count) { - super(); - this.count = count; - } - toDOM() { - const span = document.createElement('span'); - span.className = 'cm-count'; - span.textContent = this.count; - span.style.color = '#989898'; - return span; - } - ignoreEvent() { return true; } - } - /* eslint-enable no-restricted-syntax */ return ViewPlugin.fromClass( class { @@ -96,11 +80,11 @@ const createHighlightPlugin = (renderer, tab)=>{ if(tok.type === 'pageLine' && tab === 'brewText') { pageCount++; line.from === 0 && pageCount--; - decos.push(Decoration.widget({ widget: new countWidget(pageCount), side: 2 }).range(line.to)); + decos.push( Decoration.line({ attributes: { "data-page-number": pageCount }}).range(line.from)); } if(tok.type === 'snippetLine' && tab === 'brewSnippets') { snippetCount++; - decos.push(Decoration.widget({ widget: new countWidget(snippetCount), side: 2 }).range(line.to)); + decos.push(Decoration.line({ attributes: { "data-page-number": pageCount }}).range(line.from)); } } }); diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index aaa8798fc..7503749fc 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -45,8 +45,9 @@ } } - .cm-count { - float : right; + .cm-pageLine[data-page-number]::after { + content:attr(data-page-number); + float:right; color : grey; } .cm-columnSplit { From fb426755749eab3c3b0aff043768011cb20f009a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 01:36:37 +0100 Subject: [PATCH 53/56] fold all and unfold all restored --- client/components/codeEditor/codeEditor.jsx | 23 ++++++++++++++----- client/components/codeEditor/customFolding.js | 2 +- client/homebrew/editor/editor.jsx | 13 +++++------ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 9c97ba0e3..1dde9eaf5 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -16,17 +16,17 @@ import { dropCursor, } from '@codemirror/view'; import { EditorState, Compartment } from '@codemirror/state'; -import { foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language'; +import { foldAll as foldAllCmd, unfoldAll as unfoldAllCmd, foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language'; import { defaultKeymap, history, undo, redo, undoDepth, redoDepth } from '@codemirror/commands'; import { languages } from '@codemirror/language-data'; import { css, cssLanguage } from '@codemirror/lang-css'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; -import {html} from "@codemirror/lang-html" +import { html } from '@codemirror/lang-html'; import { autocompleteEmoji } from './autocompleteEmoji.js'; import { searchKeymap, search } from '@codemirror/search'; -import {closeBrackets} from "@codemirror/autocomplete" +import { closeBrackets } from '@codemirror/autocomplete'; -const customClose = closeBrackets({ brackets: ["()", "[]", "{{}}"] }); +const customClose = closeBrackets({ brackets: ['()', '[]', '{{}}'] }); import * as themesImport from '@uiw/codemirror-themes-all'; import defaultCM5Theme from '@themes/codeMirror/default.js'; @@ -80,11 +80,11 @@ const createHighlightPlugin = (renderer, tab)=>{ if(tok.type === 'pageLine' && tab === 'brewText') { pageCount++; line.from === 0 && pageCount--; - decos.push( Decoration.line({ attributes: { "data-page-number": pageCount }}).range(line.from)); + decos.push(Decoration.line({ attributes: { 'data-page-number': pageCount } }).range(line.from)); } if(tok.type === 'snippetLine' && tab === 'brewSnippets') { snippetCount++; - decos.push(Decoration.line({ attributes: { "data-page-number": pageCount }}).range(line.from)); + decos.push(Decoration.line({ attributes: { 'data-page-number': pageCount } }).range(line.from)); } } }); @@ -324,6 +324,17 @@ const CodeEditor = forwardRef( }; }, + foldAll : ()=>{ + const view = viewRef.current; + if(!view) return; + view.dispatch(foldAllCmd(view)); + }, + unfoldAll : ()=>{ + const view = viewRef.current; + if(!view) return; + view.dispatch(unfoldAllCmd(view)); + }, + focus : ()=>viewRef.current.focus(), })); diff --git a/client/components/codeEditor/customFolding.js b/client/components/codeEditor/customFolding.js index b0ce67106..ea9087c03 100644 --- a/client/components/codeEditor/customFolding.js +++ b/client/components/codeEditor/customFolding.js @@ -8,7 +8,7 @@ const pageFoldExtension = [ 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; + if(!matcher.test(prevLineText)) return null; let endLine = startLine.number; while (endLine < doc.lines && !matcher.test(doc.line(endLine + 1).text)) { diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 4deffa18a..0916ac0d7 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -330,14 +330,13 @@ const Editor = createReactClass({ return this.codeEditor.current?.undo(); }, - foldCode : function(){ - return this.codeEditor.current?.foldAllCode(); - }, - - unfoldCode : function(){ - return this.codeEditor.current?.unfoldAllCode(); - }, +foldCode: function() { + return this.codeEditor.current?.foldAll(); +}, +unfoldCode: function() { + return this.codeEditor.current?.unfoldAll(); +}, render : function(){ return (
From b53abb5f9f536f952500c253031c9748532afbf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 01:46:51 +0100 Subject: [PATCH 54/56] change highlight when changing tabs --- client/components/codeEditor/codeEditor.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 1dde9eaf5..c326a807e 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -253,7 +253,7 @@ const CodeEditor = forwardRef( view.dispatch({ effects : highlightCompartment.reconfigure([customHighlightPlugin, highlightExtension]), }); - }, [renderer]); + }, [renderer, tab]); useImperativeHandle(ref, ()=>({ getValue : ()=>viewRef.current.state.doc.toString(), From d2ca8473f3c61cc1fc7d44c72432e06fca1c468e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 01:53:34 +0100 Subject: [PATCH 55/56] small changes --- client/components/codeEditor/customHighlight.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index a91f25281..d0522520a 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -85,7 +85,7 @@ export function tokenizeCustomMarkdown(text) { return; //if line only has colons, stops } - const singleLineRegex = /^([^:\n]*\S)(::)([^\n]*)$/dmy; + const singleLineRegex = /^([^:\n]*\S)\s*(::)([^\n]*)$/dmy; const match = singleLineRegex.exec(lineText); @@ -253,6 +253,8 @@ export function tokenizeCustomCSS(text) { return tokens; } + +console.log(tags); //assign classes to tags provided by lezer, not unlike the function above export const customHighlightStyle = HighlightStyle.define([ { tag: tags.heading, class: 'cm-header' }, @@ -269,6 +271,8 @@ export const customHighlightStyle = HighlightStyle.define([ { tag: tags.strong, class: 'cm-strong' }, { tag: tags.emphasis, class: 'cm-em' }, { tag: tags.quote, class: 'cm-quote' }, + { tag: tags.comment, class: 'cm-comment' }, + { tag: tags.monospace, class: 'cm-comment' }, //css tags @@ -284,7 +288,7 @@ export const customHighlightStyle = HighlightStyle.define([ { tag: tags.paren, class: 'cm-paren' }, { tag: tags.variableName, class: 'cm-variable' }, { tag: tags.invalid, class: 'cm-error' }, - { tag: tags.comment, class: 'cm-comment' }, + ]); From 4aa9408358e5a5d702cf7db2fb17bbbba105c25f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Sun, 29 Mar 2026 01:59:21 +0100 Subject: [PATCH 56/56] proper highlights for weird def lists --- .../components/codeEditor/customHighlight.js | 86 +++++++++---------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index d0522520a..596434a9c 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -79,53 +79,52 @@ export function tokenizeCustomMarkdown(text) { } } - // --- inline definition lists --- - if(/::/.test(lineText)) { - if(/^:*$/.test(lineText) == true) { - return; //if line only has colons, stops + const singleLineRegex = /^([^:\n]*\S)(\s*)(::)([^\n]*)$/dmy; + + const match = singleLineRegex.exec(lineText); + + if(match) { + const [full, term, spaces, colons, desc] = match; + + let offset = 0; + + tokens.push({ + line : lineNumber, + type : customTags.definitionList, + }); + + // Term + tokens.push({ + line : lineNumber, + type : customTags.definitionTerm, + from : offset, + to : offset + term.length, + }); + offset += term.length; + + // Spaces before :: + if(spaces) { + offset += spaces.length; } - const singleLineRegex = /^([^:\n]*\S)\s*(::)([^\n]*)$/dmy; + // :: colons + tokens.push({ + line : lineNumber, + type : customTags.definitionColon, + from : offset, + to : offset + colons.length, + }); + offset += colons.length; - const match = singleLineRegex.exec(lineText); + // Definition + tokens.push({ + line : lineNumber, + type : customTags.definitionDesc, + from : offset, + to : offset + desc.length, + }); - 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; - } + return; } // multiline def list @@ -254,7 +253,6 @@ export function tokenizeCustomCSS(text) { return tokens; } -console.log(tags); //assign classes to tags provided by lezer, not unlike the function above export const customHighlightStyle = HighlightStyle.define([ { tag: tags.heading, class: 'cm-header' },