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] 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`); }, }; }