diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 2204679a6..8915c39dd 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -66,10 +66,6 @@ updates:
- dependency-name: "@babel/preset-react"
versions:
- 7.13.13
- - dependency-name: codemirror
- versions:
- - 5.59.3
- - 5.60.0
- dependency-name: classnames
versions:
- 2.3.0
diff --git a/changelog.md b/changelog.md
index 22af73b80..136f6346a 100644
--- a/changelog.md
+++ b/changelog.md
@@ -93,6 +93,18 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
+### Saturday 4/20/2026 - v3.22.0
+
+{{taskList
+##### 5e-Cleric
+* [x] Major update to editor framework (Codemirror 6)
+Fixes issues [#3511](https://github.com/naturalcrit/homebrewery/issues/3511), [#4590](https://github.com/naturalcrit/homebrewery/issues/4590), [#4563](https://github.com/naturalcrit/homebrewery/issues/4653), [#4655](https://github.com/naturalcrit/homebrewery/issues/4655)
+* [x] Fix to Admin page tab names
+
+##### G-Ambatte
+* [x] Fix white page crash on certain browsers
+}}
+
### Saturday 4/04/2026 - v3.21.0
{{taskList
diff --git a/client/components/codeEditor/autocompleteEmoji.js b/client/components/codeEditor/autocompleteEmoji.js
index fc64e7bbd..309668884 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,65 @@ 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;
+ }
+ }));
+ //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,
+ options,
+ filter : false,
};
-
- 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]}`;
+
+ const fragment = document.createDocumentFragment();
+ fragment.appendChild(icon);
+
+ return fragment;
+ }
+ }
+ ]
+});
\ No newline at end of file
diff --git a/client/components/codeEditor/close-tag.js b/client/components/codeEditor/close-tag.js
deleted file mode 100644
index 84cf62169..000000000
--- a/client/components/codeEditor/close-tag.js
+++ /dev/null
@@ -1,48 +0,0 @@
-const autoCloseCurlyBraces = function(CodeMirror, cm, typingClosingBrace) {
- const ranges = cm.listSelections(), replacements = [];
- for (let i = 0; i < ranges.length; i++) {
- if(!ranges[i].empty()) return CodeMirror.Pass;
- const pos = ranges[i].head, line = cm.getLine(pos.line), tok = cm.getTokenAt(pos);
- if(!typingClosingBrace && (tok.type == 'string' || tok.string.charAt(0) != '{' || tok.start != pos.ch - 1))
- return CodeMirror.Pass;
- else if(typingClosingBrace) {
- let hasUnclosedBraces = false, index = -1;
- do {
- index = line.indexOf('{{', index + 1);
- if(index !== -1 && line.indexOf('}}', index + 1) === -1) {
- hasUnclosedBraces = true;
- break;
- }
- } while (index !== -1);
- if(!hasUnclosedBraces) return CodeMirror.Pass;
- }
-
- replacements[i] = typingClosingBrace ? {
- text : '}}',
- newPos : CodeMirror.Pos(pos.line, pos.ch + 2)
- } : {
- text : '{}}',
- newPos : CodeMirror.Pos(pos.line, pos.ch + 1)
- };
- }
-
- for (let i = ranges.length - 1; i >= 0; i--) {
- const info = replacements[i];
- cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, '+insert');
- const sel = cm.listSelections().slice(0);
- sel[i] = {
- head : info.newPos,
- anchor : info.newPos
- };
- cm.setSelections(sel);
- }
-};
-
-export default {
- autoCloseCurlyBraces : function(CodeMirror, codeMirror) {
- const map = { name: 'autoCloseCurlyBraces' };
- map[`'{'`] = function(cm) { return autoCloseCurlyBraces(CodeMirror, cm); };
- map[`'}'`] = function(cm) { return autoCloseCurlyBraces(CodeMirror, cm, true); };
- codeMirror?.addKeyMap(map);
- }
-};
\ No newline at end of file
diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx
index 32b3a2012..3c75d52b0 100644
--- a/client/components/codeEditor/codeEditor.jsx
+++ b/client/components/codeEditor/codeEditor.jsx
@@ -1,494 +1,457 @@
-/* eslint-disable max-lines */
+/* eslint max-lines: ["error", { "max": 400 }] */
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 : ()=>{},
- onReady : ()=>{},
- enableFolding : true,
- editorTheme : 'default'
- };
- },
+import {
+ EditorView,
+ keymap,
+ lineNumbers,
+ highlightActiveLineGutter,
+ highlightActiveLine,
+ scrollPastEnd,
+ Decoration,
+ ViewPlugin,
+ drawSelection,
+ dropCursor,
+ rectangularSelection,
+ crosshairCursor,
+} from '@codemirror/view';
+import { EditorState, Compartment, StateEffect, StateField } from '@codemirror/state';
+import { foldAll as foldAllCmd, unfoldAll as unfoldAllCmd, foldGutter, foldKeymap, foldEffect, foldState, syntaxHighlighting } from '@codemirror/language';
+import { defaultKeymap, history, undo, redo, undoDepth, redoDepth } from '@codemirror/commands';
+import { languages } from '@codemirror/language-data';
+import { css } 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';
- getInitialState : function() {
- return {
- docs : {}
- };
- },
+const autoCloseBrackets = closeBrackets({ brackets: ['()', '[]', '{{}}'] });
- editor : React.createRef(null),
+import defaultCM5Theme from '@themes/codeMirror/default.js';
+import darkbrewery from '@themes/codeMirror/darkbrewery.js';
+import cm5Themes from 'codemirror-5-themes';
- async componentDidMount() {
- CodeMirror = (await import('codemirror')).default;
- this.CodeMirror = CodeMirror;
+const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
+const themeCompartment = new Compartment();
+const highlightCompartment = new Compartment();
- await import('codemirror/mode/gfm/gfm.js');
- await import('codemirror/mode/css/css.js');
- await import('codemirror/mode/javascript/javascript.js');
+import { generalKeymap, markdownKeymap } from './customKeyMaps.js';
+import foldOnPages from './customFolding.js';
+import { customHighlightStyle, tokenizeCustomMarkdown, tokenizeCustomCSS } from './customHighlight.js';
+import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.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';
+const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
+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
- // 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);
+ let tokenize;
- 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 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;
- // }
- });
- this.props.onReady?.(this.codeMirror);
- // Add custom behaviors (auto-close curlies and auto-complete emojis)
- closeTag.autoCloseCurlyBraces(CodeMirror, this.codeMirror);
- autoCompleteEmoji.showAutocompleteEmoji(CodeMirror, this.codeMirror);
-
- // 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();
- },
-
- // Use for GFM tabs that use common hot-keys
- isGFM : function() {
- console.log(this.props.tab);
- if( this.props.tab === 'brewText' || this.props.tab === 'brewSnippets') return true;
- return false;
- },
-
- isBrewText : function() {
- if(this.props.tab === 'brewText') 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() {
- console.log('hello');
- if(!this.isGFM()) return;
- console.log(this.isGFM());
- 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 });
- };
- },
-
- 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 });
- }
- },
-
- 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();
-
- 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`;
- }
- };
- },
- //----------------------//
-
- render : function(){
- return <>
-
-
- >;
+ if(tab === 'brewStyles') {
+ tokenize = tokenizeCustomCSS;
+ } else {
+ 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());
+ 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));
+ } else {
+ decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from));
+ if(tok.type === 'pageLine' && tab === 'brewText') {
+ pageCount++;
+ line.from === 0 && pageCount--;
+ 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': snippetCount } }).range(line.from));
+ }
+ }
+ });
+
+ decos.sort((a, b)=>a.from - b.from || a.to - b.to);
+ return Decoration.set(decos);
+ }
+ },
+ { decorations: (v)=>v.decorations }
+ );
+};
+
+const setProgrammaticCursorLine = StateEffect.define();
+
+const programmaticCursorLineField = StateField.define({
+ create() {
+ return Decoration.none;
+ },
+ update(decorations, transitionState) {
+ //deco is the decoratiions object
+ //tr is the transition state object, tr.effects is an array of stateEffects
+ //seems to be the easiest way of setting a class programatically only when called
+ for (const effects of transitionState.effects) {
+ if(effects.is(setProgrammaticCursorLine)) {
+ const pos = effects.value;
+ if(pos == null) return Decoration.none;
+ const line = transitionState.state.doc.lineAt(pos);
+
+ return Decoration.set([
+ Decoration.line({
+ class : 'sourceMoveFlash'
+ }).range(line.from)
+ ]);
+ }
+ }
+ return decorations;
+ },
+ provide : (decorationSet)=>EditorView.decorations.from(decorationSet)
});
-export default CodeEditor;
+const CodeEditor = forwardRef(
+ (
+ {
+ language = '',
+ tab = 'brewText',
+ view,
+ value = '',
+ onChange = ()=>{},
+ onCursorChange = ()=>{},
+ onViewChange = ()=>{},
+ editorTheme = 'default',
+ style,
+ renderer,
+ ...props
+ },
+ ref,
+ )=>{
+ const editorRef = useRef(null);
+ const viewRef = useRef(null);
+ const docsRef = useRef({});
+ const tabRef = useRef(tab);
+ const prevTabRef = useRef(tab);
+ const scrollRef = useRef({});
+ const foldsRef = useRef({});
+ const pageMap = useRef([]);
+ const recomputePages = (doc)=>{
+ if(tab !== 'brewText') return;
+ const pages = [0];
+ const text = doc.toString();
+ let offset = 0;
+
+ for (const line of text.split('\n')) {
+ if(PAGEBREAK_REGEX_V3.test(line)) {
+ pages.push(offset);
+ }
+ offset += line.length + 1;
+ }
+
+ pageMap.current = pages;
+ };
+
+ const findPageFromPos = (pos)=>{
+ const pages = pageMap.current;
+ let page = 1;
+
+ for (let i = 1; i < pages.length; i++) {
+ if(pos >= pages[i]) page = i + 1;
+ }
+
+ return page;
+ };
+
+ const getFoldRanges = (state)=>{
+ const folds = [];
+ state.field(foldState, false)?.between(0, state.doc.length, (from, to)=>{
+ folds.push({ from, to });
+ });
+ return folds;
+ };
+
+ const createExtensions = ({ onChange, language, editorTheme })=>{
+ const setEventListeners = EditorView.updateListener.of((update)=>{
+ if(update.docChanged) {
+ recomputePages(update.state.doc);
+ onChange(update.state.doc.toString());
+ }
+ if(update.selectionSet) {
+ const pos = update.state.selection.main.head;
+ const page = findPageFromPos(pos);
+ onCursorChange(page);
+ }
+ });
+
+ const highlightExtension = renderer === 'V3'
+ ? syntaxHighlighting(customHighlightStyle)
+ : syntaxHighlighting(legacyCustomHighlightStyle);
+
+ const customHighlightPlugin = createHighlightPlugin(renderer, tab);
+
+ const languageExtension = language === 'css' ? css() : [markdown({ base: markdownLanguage, codeLanguages: languages }), html({ autoCloseTags: true })];
+ const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : themes[editorTheme] || themes['default'];
+
+ return [
+ EditorView.lineWrapping,
+ setEventListeners,
+ languageExtension,
+ autoCloseBrackets,
+ lineNumbers(),
+ scrollPastEnd(),
+ search(),
+ history(), //allows for undo and redo
+ ...(tab !== 'brewStyles' ? [autocompleteEmoji] : []),
+
+ //folding
+ foldOnPages,
+ foldGutter({
+ openText : '▾',
+ closedText : '▸'
+ }),
+
+ //highlights
+ highlightCompartment.of([customHighlightPlugin, highlightExtension]),
+ themeCompartment.of(themeExtension),
+ highlightActiveLine(),
+ highlightActiveLineGutter(),
+
+ //keyboard shortcut
+ keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]),
+ generalKeymap,
+ ...(tab !== 'brewStyles' ? [markdownKeymap] : []),
+
+ //multiple cursors and selections
+ drawSelection(),
+ rectangularSelection(),
+ crosshairCursor(),
+ EditorState.allowMultipleSelections.of(true),
+ dropCursor(),
+ programmaticCursorLineField,
+ ];
+ };
+
+ useEffect(()=>{
+ if(!editorRef.current) return;
+
+ const state = EditorState.create({
+ doc : value,
+ extensions : createExtensions({ onChange, language, editorTheme }),
+ });
+
+ recomputePages(state.doc);
+
+ viewRef.current = new EditorView({
+ state,
+ parent : editorRef.current,
+ });
+
+ const view = viewRef.current;
+
+ let ticking = false;
+
+ const handleScroll = ()=>{
+ if(ticking) return;
+
+ ticking = true;
+ requestAnimationFrame(()=>{
+ const top = view.scrollDOM.scrollTop;
+ scrollRef.current[tabRef.current] = top;
+ const block = view.lineBlockAtHeight(top);
+ const page = findPageFromPos(block.from);
+ onViewChange(page);
+ ticking = false;
+ });
+ };
+
+ view.scrollDOM.addEventListener('scroll', handleScroll);
+
+ docsRef.current[tab] = state;
+
+ return ()=>{
+ view.scrollDOM.removeEventListener('scroll', handleScroll);
+ viewRef.current?.destroy();
+ };
+ }, []);
+
+ const restoreFolds = (view, folds)=>{
+ if(!folds?.length) return;
+
+ view.dispatch({
+ effects : folds.map((f)=>foldEffect.of(f))
+ });
+ };
+
+ useEffect(()=>{
+ const view = viewRef.current;
+ if(!view) return;
+
+ tabRef.current = tab;
+ const prevTab = prevTabRef.current;
+
+ foldsRef.current[prevTab] = getFoldRanges(view.state);
+
+ if(prevTab !== tab) {
+ docsRef.current[prevTab] = view.state;
+
+ let nextState = docsRef.current[tab];
+
+ if(!nextState) {
+ nextState = EditorState.create({
+ doc : value,
+ extensions : createExtensions({ onChange, language, editorTheme }),
+ });
+ }
+
+ view.setState(nextState);
+ restoreFolds(view, foldsRef.current[tab]);
+
+ const savedScroll = scrollRef.current[tab];
+
+ if(savedScroll != null) {
+ requestAnimationFrame(()=>{
+ view.scrollDOM.scrollTop = savedScroll;
+ });
+ }
+
+ prevTabRef.current = tab;
+ }
+ view.focus();
+ }, [tab]);
+
+ 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]);
+
+ useEffect(()=>{
+ //rebuild theme extension on theme change
+ const view = viewRef.current;
+ if(!view) return;
+
+ const themeExtension = Array.isArray(themes[editorTheme])? themes[editorTheme]: themes[editorTheme] || themes['default'];
+
+ view.dispatch({
+ effects : themeCompartment.reconfigure(themeExtension),
+ });
+ }, [editorTheme]);
+
+ useEffect(()=>{
+ //rebuild syntax highlight when changing tab or renderer
+ const view = viewRef.current;
+ if(!view) return;
+
+ const highlightExtension =renderer === 'V3'
+ ? syntaxHighlighting(customHighlightStyle)
+ : syntaxHighlighting(legacyCustomHighlightStyle);
+
+ const customHighlightPlugin = createHighlightPlugin(renderer, tab);
+
+ view.dispatch({
+ effects : highlightCompartment.reconfigure([customHighlightPlugin, highlightExtension]),
+ });
+ }, [renderer, tab]);
+
+ useImperativeHandle(ref, ()=>({
+
+ injectText : (text)=>{
+ const view = viewRef.current;
+
+
+ view.dispatch(
+ view.state.replaceSelection(text)
+ );
+ view.focus();
+ },
+ getCursorPosition : ()=>viewRef.current.state.selection.main.head,
+
+ scrollToPage : (pageNumber, smooth = true)=>{
+ const view = viewRef.current;
+ if(!view) return;
+
+ const pos = pageMap.current[pageNumber - 1] ?? 0;
+
+ view.dispatch({
+ selection : { anchor: pos },
+ effects : [setProgrammaticCursorLine.of(pos), EditorView.scrollIntoView(pos, { y: 'start' })],
+ });
+
+ view.focus();
+
+ setTimeout(()=>{
+ view.dispatch({
+ effects : setProgrammaticCursorLine.of(null)
+ });
+ }, 400);
+ },
+
+ undo : ()=>undo(viewRef.current),
+ redo : ()=>redo(viewRef.current),
+
+ historySize : ()=>{
+ const view = viewRef.current;
+ if(!view) return { done: 0, undone: 0 };
+
+ return {
+ done : undoDepth(view.state),
+ undone : redoDepth(view.state),
+ };
+ },
+
+ foldAll : ()=>{
+ const view = viewRef.current;
+ if(!view) return;
+
+ const doc = view.state.doc;
+ const pages = pageMap.current;
+
+ const effects = pages.map((start, i)=>{
+ const next = pages[i + 1] || doc.length;
+ const from = i ? doc.line(doc.lineAt(start).number + 1).from : 0;
+ const to = doc.line(doc.lineAt(next).number).from - 1;
+
+ return to > from ? foldEffect.of({ from, to }) : null;
+ }).filter(Boolean);
+
+ view.dispatch({ effects });
+ },
+ unfoldAll : ()=>{
+ const view = viewRef.current;
+ if(!view) return;
+ view.dispatch(unfoldAllCmd(view));
+ },
+
+ focus : ()=>viewRef.current.focus(),
+ }));
+
+ return
;
+ },
+);
+
+export default CodeEditor;
\ No newline at end of file
diff --git a/client/components/codeEditor/codeEditor.less b/client/components/codeEditor/codeEditor.less
index 89d0c9497..4ca374291 100644
--- a/client/components/codeEditor/codeEditor.less
+++ b/client/components/codeEditor/codeEditor.less
@@ -1,60 +1,179 @@
-@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
+// 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 {
+:where(.codeEditor) {
+ width : 100%;
+ height : calc(100% - 25px);
+ font-family : monospace;
+
+ .cm-editor {
+ height : 100%;
+ outline : none !important;
+ }
+
+ &.brewSnippets .cm-snippetLine,
+ :where(&.brewText) .cm-pageLine {
+ background : #33333328;
+ border-top : #333399 solid 1px;
+ }
+
+ &.brewSnippets {
+ .cm-pageLine {
+ color : #777777;
+ background : #3E4E3E1B;
+ border-top : #3399423B solid 1px;
+ }
+ }
+
+ &:where(.brewText), &.brewSnippets {
+
+
+ .cm-pageLine[data-page-number]::after {
+ float : right;
+ color : grey;
+ content : attr(data-page-number);
+ }
+ .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;
+ }
+ &.term { color : rgb(96, 117, 143); }
+ &.definition { color : rgb(97, 57, 178); }
+ }
+ .cm-block:not(.cm-comment) {
+ font-weight : bold;
+ color : purple;
+ }
+ .cm-inline-block,
+ .cm-define .cm-inline-block {
+ font-weight : bold;
+ color : red;
+ span:not(.cm-comment) { color : inherit; }
+ }
+ .cm-injection:not(.cm-comment) {
+ font-weight : bold;
+ color : green;
+ span { color : inherit; }
+ }
+ .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-strikethrough {
+ text-decoration: line-through;
+ }
+
+ .cm-definitionList {
+ .cm-definitionTerm { color : rgb(96, 117, 143); }
+ .cm-definitionColon:not(:has(.cm-comment)) {
+ font-weight : bold;
+ color : #949494;
+ background : #E5E5E5;
+ border-radius : 3px;
+ }
+ .cm-definitionDesc { color : rgb(97, 57, 178); }
+ }
+
+ .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-content { tab-size : 2 !important; }
+
@media screen and (pointer : coarse) {
font-size : 16px;
}
- .CodeMirror-foldmarker {
+
+ .cm-gutterElement span {
font-family : inherit;
font-weight : 600;
color : grey;
text-shadow : none;
}
- .CodeMirror-foldgutter {
+ .cm-foldGutter {
cursor : pointer;
border-left : 1px solid #EEEEEE;
transition : background 0.1s;
&:hover { background : #DDDDDD; }
}
- .sourceMoveFlash .CodeMirror-line {
+ /* Flash animation for source moves */
+ .cm-line.sourceMoveFlash {
animation-name : sourceMoveAnimation;
animation-duration : 0.4s;
}
- .CodeMirror-search-field {
- width:25em !important;
- outline:1px inset #00000055 !important;
+ /* 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
+}
diff --git a/client/components/codeEditor/customFolding.js b/client/components/codeEditor/customFolding.js
new file mode 100644
index 000000000..49cb449e7
--- /dev/null
+++ b/client/components/codeEditor/customFolding.js
@@ -0,0 +1,46 @@
+import { foldService, codeFolding } from '@codemirror/language';
+
+const foldOnPages = [
+ foldService.of((state, lineStart)=>{ //tells where to fold
+ 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(!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;
+
+ 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(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 ? '...' : ''} ↦`;
+ },
+ 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 foldOnPages;
\ No newline at end of file
diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js
new file mode 100644
index 000000000..622f8a3bf
--- /dev/null
+++ b/client/components/codeEditor/customHighlight.js
@@ -0,0 +1,311 @@
+import { HighlightStyle } from '@codemirror/language';
+import { tags } from '@lezer/highlight';
+
+// Making the tokens
+const customTags = {
+ 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
+ strikethrough : 'strikethrough', // .cm-strikethrough
+
+ //CSS
+
+ variable : 'variable',
+};
+
+export function tokenizeCustomMarkdown(text) {
+ const tokens = [];
+ const lines = text.split('\n');
+
+ //tokens without a `from` or `to` are interpreted by the custom plugin as line tokens
+
+ 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 });
+
+ // --- 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)) {
+ 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),
+ );
+ }
+ }
+
+ // --- Strikethrough ---
+ if(/\~/.test(lineText)) {
+ const strikethroughRegex = /~(?!\s)(.+?)(? 0 && lineText.trim().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})/gmd;
+ let match;
+ while ((match = injectionRegex.exec(lineText)) !== null) {
+ tokens.push({
+ line : lineNumber,
+ from : match.indices[1][0],
+ to : match.indices[1][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 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;
+}
+
+//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' },
+ { 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' },
+ { tag: tags.comment, class: 'cm-comment' },
+ { tag: tags.monospace, class: 'cm-comment' },
+
+ //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' },
+
+]);
+
+
+
diff --git a/client/components/codeEditor/customKeyMaps.js b/client/components/codeEditor/customKeyMaps.js
new file mode 100644
index 000000000..db3edf47f
--- /dev/null
+++ b/client/components/codeEditor/customKeyMaps.js
@@ -0,0 +1,204 @@
+/* eslint max-lines: ["error", { "max": 300 }] */
+import { keymap } from '@codemirror/view';
+import { undo, redo, indentMore, deleteLine } from '@codemirror/commands';
+import { Prec } from '@codemirror/state';
+
+const insertTab = (view)=>{
+ const { from, to } = view.state.selection.main;
+
+ view.dispatch({
+ changes : { from, to, insert: ' ' },
+ selection : { anchor: from + 2 }
+ });
+
+ 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 wrapSelection = (prefix, suffix)=>(view)=>{
+ const changes = [];
+
+ for (const range of view.state.selection.ranges) {
+ const { from, to } = range;
+ const selected = view.state.doc.sliceString(from, to);
+
+ let text;
+
+ if(from === to) { text = prefix + suffix; } else if(selected.startsWith(prefix) && selected.endsWith(suffix)) {
+ text = selected.slice(prefix.length, -suffix.length);
+ } else {text = `${prefix}${selected}${suffix}`;}
+
+ changes.push({ from, to, insert: text });
+ }
+
+ view.dispatch({
+ changes
+ });
+
+ return true;
+};
+
+const makeNbsp = (view)=>{
+ const { from } = view.state.selection.main;
+
+ const prev2 = from >= 2
+ ? view.state.doc.sliceString(from - 2, from)
+ : '';
+
+ const insert = (prev2 === ':>' || prev2 === '>>') ? '>' : ':>';
+
+ view.dispatch({
+ changes : { from, to: from, insert },
+ selection : { anchor: from + insert.length },
+ });
+
+ 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 generalKeymap = Prec.high(keymap.of([
+ { key: 'Tab', run: insertTab },
+ { key: 'Mod-z', run: undo }, //i think it may be unnecessary
+ { key: 'Mod-Shift-z', run: redo },
+ { key: 'Mod-y', run: redo },
+ { key: 'Mod-d', run: deleteLine },
+]));
+
+export const markdownKeymap = Prec.highest(keymap.of([
+ //{ key: 'Shift-Tab', run: indentMore },
+ { key: 'Shift-Tab', run: indentLess },
+ { key: 'Mod-b', run: wrapSelection('**', '**') }, // makeBold
+ { key: 'Mod-i', run: wrapSelection('*', '*') }, // makeItalic
+ { key: 'Mod-u', run: wrapSelection('
', '') }, // makeUnderline
+ { key: 'Shift-Mod-=', run: wrapSelection('^', '^') }, // makeSuper
+ { key: 'Mod-=', run: wrapSelection('^^', '^^') }, // 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: 'Mod-Enter', run: newPage },
+ { key: 'Shift-Mod-Enter', run: newColumn },
+]));
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;
- });
- }
-};
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;
- });
- }
-};
diff --git a/client/components/codeEditor/legacyCustomHighlight.js b/client/components/codeEditor/legacyCustomHighlight.js
new file mode 100644
index 000000000..cccb6647b
--- /dev/null
+++ b/client/components/codeEditor/legacyCustomHighlight.js
@@ -0,0 +1,54 @@
+import { HighlightStyle } from '@codemirror/language';
+import { tags } from '@lezer/highlight';
+
+const customTags = {
+ pageLine : 'pageLine', // .cm-pageLine
+ snippetLine : 'snippetLine', // .cm-snippetLine
+};
+
+export function legacyTokenizeCustomMarkdown(text) {
+ const tokens = [];
+ const lines = text.split('\n');
+
+ 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.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' },
+]);
+
diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx
index efdfce5c9..202c1a375 100644
--- a/client/homebrew/brewRenderer/brewRenderer.jsx
+++ b/client/homebrew/brewRenderer/brewRenderer.jsx
@@ -135,6 +135,7 @@ const BrewRenderer = (props)=>{
const mainRef = useRef(null);
const pagesRef = useRef(null);
+ const urlRef = useRef('');
if(props.renderer == 'legacy') {
rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY);
@@ -272,12 +273,7 @@ const BrewRenderer = (props)=>{
const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
scrollToHash(window.location.hash);
- navigation.addEventListener('navigate', (e)=>{
- if(e.hashChange && e.destination.sameDocument){
- const dest = e.destination.url.slice(e.destination.url.indexOf('#'));
- scrollToHash(dest);
- }
- });
+ window.addEventListener('hashchange', ()=>scrollToHash(window.location.hash));
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
renderPages(); //Make sure page is renderable before showing
diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx
index ced40f48f..017cb7933 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';
@@ -12,8 +11,22 @@ 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\ .*$/;
+import defaultCM5Theme from '@themes/codeMirror/default.js';
+import darkbrewery from '@themes/codeMirror/darkbrewery.js';
+import cm5Themes from 'codemirror-5-themes';
+
+const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
+
+const EditorThemes = Object.entries(themes)
+ .filter(([name, value])=>Array.isArray(value) &&
+ !name.endsWith('Init') &&
+ !name.endsWith('Style')
+ )
+ .map(([name])=>name);
+
+
+//const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
+//const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
const DEFAULT_STYLE_TEXT = dedent`
/*=======--- Example CSS styling ---=======*/
/* Any CSS here will apply to your document! */
@@ -30,6 +43,7 @@ const DEFAULT_SNIPPET_TEXT = dedent`
This snippet is accessible in the brew tab, and will be inherited if the brew is used as a theme.
`;
let isJumping = false;
+let jumpSource = null;
const Editor = createReactClass({
displayName : 'Editor',
@@ -72,15 +86,15 @@ const Editor = createReactClass({
componentDidMount : function() {
- this.highlightCustomMarkdown();
- document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys);
+ const brewRenderer = document.getElementById('BrewRenderer');
+ brewRenderer.onload = ()=>brewRenderer.contentDocument?.addEventListener('keydown', this.handleControlKeys);
document.addEventListener('keydown', this.handleControlKeys);
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
- if(editorTheme) {
- this.setState({
- editorTheme : editorTheme
- });
+ if(editorTheme && EditorThemes.includes(editorTheme)) {
+ this.setState({ editorTheme });
+ } else {
+ this.setState({ editorTheme: 'default' });
}
const snippetBar = document.querySelector('.editor > .snippetBar');
if(!snippetBar) return;
@@ -95,7 +109,6 @@ const Editor = createReactClass({
componentDidUpdate : function(prevProps, prevState, snapshot) {
- this.highlightCustomMarkdown();
if(prevProps.moveBrew !== this.props.moveBrew)
this.brewJump();
@@ -129,22 +142,16 @@ const Editor = createReactClass({
}
},
- 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);
+ updateCurrentCursorPage : function(pageNumber) {
+ this.props.onCursorPageChange(pageNumber);
},
- 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);
+ updateCurrentViewPage : function(pageNumber) {
+ this.props.onViewPageChange(pageNumber);
},
handleInject : function(injectText){
- this.codeEditor.current?.injectText(injectText, false);
+ this.codeEditor.current?.injectText(injectText);
},
handleViewChange : function(newView){
@@ -153,181 +160,12 @@ const Editor = createReactClass({
this.setState({
view : newView
}, ()=>{
- this.codeEditor.current?.codeMirror?.focus();
+ this.codeEditor.current?.focus();
});
},
- 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)
+ if(!window || !this.isText() || isJumping || jumpSource === 'source')
return;
// Get current brewRenderer scroll position and calculate target position
@@ -340,11 +178,13 @@ const Editor = createReactClass({
clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs
scrollingTimeout = setTimeout(()=>{
isJumping = false;
+ jumpSource = null;
brewRenderer.removeEventListener('scroll', checkIfScrollComplete);
}, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done
};
isJumping = true;
+ jumpSource = 'brew';
checkIfScrollComplete();
brewRenderer.addEventListener('scroll', checkIfScrollComplete);
@@ -368,54 +208,17 @@ const Editor = createReactClass({
},
sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){
- if(!this.isText() || isJumping)
+ if(!this.isText() || isJumping || jumpSource === 'brew')
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 editor = this.codeEditor.current;
+ if(!editor) return;
+ jumpSource = 'source';
- let currentY = this.codeEditor.current.codeMirror?.getScrollInfo().top;
- 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
- 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);
-
- // 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');
- 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.scrollToPage(targetPage);
+ setTimeout(()=>{
+ jumpSource = null;
+ }, 200);
},
//Called when there are changes to the editor's dimensions
@@ -433,29 +236,6 @@ const Editor = createReactClass({
this.forceUpdate();
},
- //temporary fix until cm6 comes next update
- attachCodeMirrorListeners : function(cm) {
- if(!cm) return;
- // detach previous (important on remount / view switch)
- if(this._cm) {
- this._cm.off('cursorActivity', this._onCursor);
- this._cm.off('scroll', this._onScroll);
- }
-
- this._cm = cm;
-
- this._onCursor = ()=>{
- this.updateCurrentCursorPage(cm.getCursor());
- };
-
- this._onScroll = _.throttle(()=>{
- const topLine = cm.lineAtHeight(cm.getScrollInfo().top, 'local');
- this.updateCurrentViewPage(topLine);
- }, 200);
-
- cm.on('cursorActivity', this._onCursor);
- cm.on('scroll', this._onScroll);
- },
renderEditor : function(){
if(this.isText()){
return <>
@@ -466,10 +246,11 @@ const Editor = createReactClass({
view={this.state.view}
value={this.props.brew.text}
onChange={this.props.onBrewChange('text')}
+ onCursorChange={(page)=>this.updateCurrentCursorPage(page)}
+ onViewChange={(page)=>this.updateCurrentViewPage(page)}
editorTheme={this.state.editorTheme}
- rerenderParent={this.rerenderParent}
- style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}
- onReady={this.attachCodeMirrorListeners}/>
+ renderer={this.props.brew.renderer}
+ style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}/>
>;
}
if(this.isStyle()){
@@ -481,19 +262,16 @@ const Editor = createReactClass({
view={this.state.view}
value={this.props.brew.style ?? DEFAULT_STYLE_TEXT}
onChange={this.props.onBrewChange('style')}
- enableFolding={true}
editorTheme={this.state.editorTheme}
- rerenderParent={this.rerenderParent}
- style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}
- onReady={this.attachCodeMirrorListeners}/>
+ renderer={this.props.brew.renderer}
+ style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}/>
>;
}
if(this.isMeta()){
return <>
+ style={{ display: 'none' }}/>
+ style={{ height: `calc(100% - 25px)` }}/>
>;
}
},
@@ -533,14 +311,13 @@ const Editor = createReactClass({
return this.codeEditor.current?.undo();
},
- foldCode : function(){
- return this.codeEditor.current?.foldAllCode();
+ foldCode : function() {
+ return this.codeEditor.current?.foldAll();
},
- unfoldCode : function(){
- return this.codeEditor.current?.unfoldAllCode();
+ unfoldCode : function() {
+ return this.codeEditor.current?.unfoldAll();
},
-
render : function(){
return (
@@ -570,4 +347,4 @@ const Editor = createReactClass({
}
});
-export default Editor;
+export default Editor;
\ No newline at end of file
diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less
index 3851b50c5..a55fad852 100644
--- a/client/homebrew/editor/editor.less
+++ b/client/homebrew/editor/editor.less
@@ -1,90 +1,11 @@
@import '@sharedStyles/core.less';
-@import '@themes/codeMirror/customEditorStyles.less';
-.editor {
- position : relative;
- width : 100%;
- height : 100%;
- container : editor / inline-size;
- background:white;
- .codeEditor {
- height : calc(100% - 25px);
- .CodeMirror { height : 100%; }
- .pageLine, .snippetLine {
- background : #33333328;
- border-top : #333399 solid 1px;
- }
- .editor-page-count {
- float : right;
- color : grey;
- }
- .editor-snippet-count {
- float : right;
- color : grey;
- }
- .columnSplit {
- font-style : italic;
- color : grey;
- background-color : fade(#229999, 15%);
- border-bottom : #229999 solid 1px;
- }
- .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); }
- }
- .block:not(.cm-comment) {
- font-weight : bold;
- color : purple;
- //font-style: italic;
- }
- .inline-block:not(.cm-comment) {
- font-weight : bold;
- color : red;
- //font-style: italic;
- }
- .injection:not(.cm-comment) {
- font-weight : bold;
- color : green;
- }
- .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;
- }
- .superscript:not(.cm-comment) {
- font-size : 0.9em;
- font-weight : bold;
- vertical-align : super;
- color : goldenrod;
- }
- .subscript:not(.cm-comment) {
- font-size : 0.9em;
- font-weight : bold;
- vertical-align : sub;
- color : rgb(123, 123, 15);
- }
- .dl-highlight {
- &.dl-colon-highlight {
- font-weight : bold;
- color : #949494;
- background : #E5E5E5;
- border-radius : 3px;
- }
- &.dt-highlight { color : rgb(96, 117, 143); }
- &.dd-highlight { color : rgb(97, 57, 178); }
- }
- }
+:where(.editor) {
+ position : relative;
+ width : 100%;
+ height : 100%;
+ container : editor / inline-size;
+ background : white;
.brewJump {
position : absolute;
diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx
index 304664ff5..ac9c7943a 100644
--- a/client/homebrew/editor/snippetbar/snippetbar.jsx
+++ b/client/homebrew/editor/snippetbar/snippetbar.jsx
@@ -23,7 +23,25 @@ const ThemeSnippets = {
V3_Blank : V3_Blank,
};
-import EditorThemes from '../../../../build/homebrew/codeMirror/editorThemes.json';
+import defaultCM5Theme from '@themes/codeMirror/default.js';
+import darkbrewery from '@themes/codeMirror/darkbrewery.js';
+import cm5Themes from 'codemirror-5-themes';
+
+const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
+
+const themeNames = Object.entries(themes)
+ .filter(([name, value])=>Array.isArray(value) &&
+ !name.endsWith('Init') &&
+ !name.endsWith('Style')
+ )
+ .map(([name])=>name);
+
+const EditorThemes = [
+ 'default',
+ ...themeNames
+ .filter((name)=>name !== 'default')
+ .sort((a, b)=>a.localeCompare(b))
+];
const execute = function(val, props){
if(_.isFunction(val)) return val(props);
@@ -151,7 +169,7 @@ const Snippetbar = createReactClass({
this.props.updateEditorTheme(e.target.value);
this.setState({
- showThemeSelector : false,
+ themeSelector : false,
});
},
@@ -232,11 +250,11 @@ const Snippetbar = createReactClass({
{ this.state.showHistory && this.renderHistoryItems() }
-
-
diff --git a/client/homebrew/navbar/share.navitem.jsx b/client/homebrew/navbar/share.navitem.jsx
index d0c659e2c..e329a4560 100644
--- a/client/homebrew/navbar/share.navitem.jsx
+++ b/client/homebrew/navbar/share.navitem.jsx
@@ -17,7 +17,7 @@ const getRedditLink = (brew)=>{
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(brew.title.toWellFormed())}&text=${encodeURIComponent(text)}`;
};
-export default ({ brew })=>(
+export default ({ brew, currentPage })=>(
share
@@ -28,6 +28,12 @@ export default ({ brew })=>(
{navigator.clipboard.writeText(`${global.config.baseUrl}/share/${getShareId(brew)}`);}}>
copy url
+ {currentPage > 1 &&
+ {navigator.clipboard.writeText(`${global.config.baseUrl}/share/${getShareId(brew)}#p${currentPage}`);}}>
+ copy url (page {currentPage})
+ }
post to reddit
diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx
index 176158e2c..a7c6e4595 100644
--- a/client/homebrew/pages/editPage/editPage.jsx
+++ b/client/homebrew/pages/editPage/editPage.jsx
@@ -90,7 +90,7 @@ const EditPage = (props)=>{
const handleControlKeys = (e)=>{
if(!(e.ctrlKey || e.metaKey)) return;
- if(e.keyCode === 83) trySaveRef.current(true);
+ if(e.keyCode === 83) trySaveRef.current(true, true, saveGoogle);
if(e.keyCode === 80) printCurrentBrew();
if([83, 80].includes(e.keyCode)) {
e.stopPropagation();
@@ -118,13 +118,9 @@ const EditPage = (props)=>{
const hasChange = !_.isEqual(currentBrew, lastSavedBrew.current);
setUnsavedChanges(hasChange);
- if(autoSaveEnabled) trySave(false, hasChange);
+ if(autoSaveEnabled) trySave(false, hasChange, saveGoogle);
}, [currentBrew]);
- useEffect(()=>{
- trySave(true);
- }, [saveGoogle]);
-
const handleSplitMove = ()=>{
editorRef.current?.update();
};
@@ -183,11 +179,13 @@ const EditPage = (props)=>{
};
const toggleGoogleStorage = ()=>{
+ const newSaveGoogle = !saveGoogle;
setSaveGoogle((prev)=>!prev);
setError(null);
+ trySave(true, true, newSaveGoogle);
};
- const trySave = (immediate = false, hasChanges = true)=>{
+ const trySave = (immediate = false, hasChanges = true, saveToGoogle = false)=>{
clearTimeout(saveTimeout.current);
if(isSaving) return;
if(!hasChanges && !immediate) return;
@@ -196,7 +194,7 @@ const EditPage = (props)=>{
saveTimeout.current = setTimeout(async ()=>{
setIsSaving(true);
setError(null);
- await save(currentBrew, saveGoogle)
+ await save(currentBrew, saveToGoogle)
.catch((err)=>{
setError(err);
});
@@ -216,7 +214,7 @@ const EditPage = (props)=>{
const brewToSave = {
...brew,
text : brew.text.normalize('NFC'),
- pageCount : ((brew.renderer === 'legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1,
+ pageCount : ((brew.renderer === 'legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/gm)) || []).length + 1,
patches : stringifyPatches(makePatches(encodeURI(lastSavedBrew.current.text.normalize('NFC')), encodeURI(brew.text.normalize('NFC')))),
hash : await md5(lastSavedBrew.current.text.normalize('NFC')),
textBin : undefined,
@@ -314,7 +312,7 @@ const EditPage = (props)=>{
// #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges)
- return trySave(true)} color='blue' icon='fas fa-save'>save now;
+ return trySave(true, true, saveGoogle)} color='blue' icon='fas fa-save'>save now;
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(autoSaveEnabled)
@@ -365,7 +363,7 @@ const EditPage = (props)=>{
-
+
diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx
index 7ddb05c0b..270ca89a0 100644
--- a/client/homebrew/pages/newPage/newPage.jsx
+++ b/client/homebrew/pages/newPage/newPage.jsx
@@ -156,7 +156,7 @@ const NewPage = (props)=>{
const updatedBrew = { ...currentBrew };
splitTextStyleAndMetadata(updatedBrew);
- const pageRegex = updatedBrew.renderer === 'legacy' ? /\\page/g : /^\\page$/gm;
+ const pageRegex = updatedBrew.renderer === 'legacy' ? /\\page/g : /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/gm;
updatedBrew.pageCount = (updatedBrew.text.match(pageRegex) || []).length + 1;
const res = await request
diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx
index 093fc8965..8df241d7b 100644
--- a/client/homebrew/pages/sharePage/sharePage.jsx
+++ b/client/homebrew/pages/sharePage/sharePage.jsx
@@ -92,6 +92,19 @@ const SharePage = (props)=>{
clone to new
+ {navigator.clipboard.writeText(`${global.config.baseUrl}/share/${processShareId()}`);}}>
+ copy url
+
+ {currentBrewRendererPageNum > 1 &&
+ {navigator.clipboard.writeText(`${global.config.baseUrl}/share/${processShareId()}#p${currentBrewRendererPageNum}`);}}>
+ copy url (page {currentBrewRendererPageNum})
+ }
>
)}
diff --git a/package-lock.json b/package-lock.json
index b67bcb18f..3ea1e35ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,27 +1,39 @@
{
"name": "homebrewery",
- "version": "3.21.0",
+ "version": "3.22.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "homebrewery",
- "version": "3.21.0",
+ "version": "3.22.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.29.0",
"@babel/plugin-transform-runtime": "^7.29.0",
- "@babel/preset-env": "^7.29.2",
+ "@babel/preset-env": "^7.29.5",
"@babel/preset-react": "^7.28.5",
"@babel/runtime": "^7.29.2",
+ "@codemirror/autocomplete": "^6.20.2",
+ "@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/search": "^6.6.0",
+ "@codemirror/state": "^6.6.0",
+ "@codemirror/view": "^6.43.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",
"classnames": "^2.5.1",
- "codemirror": "^5.65.6",
+ "codemirror-5-themes": "^1.5.1",
"cookie-parser": "^1.4.7",
"core-js": "^3.49.0",
"cors": "^2.8.5",
@@ -29,9 +41,9 @@
"dedent": "^1.7.1",
"express": "^5.1.0",
"express-async-handler": "^1.2.0",
- "express-static-gzip": "3.0.0",
+ "express-static-gzip": "3.0.1",
"fflate": "^0.8.2",
- "fs-extra": "^11.3.3",
+ "fs-extra": "^11.3.5",
"hash-wasm": "^4.12.0",
"idb-keyval": "^6.2.2",
"js-yaml": "^4.1.1",
@@ -50,31 +62,31 @@
"marked-variables": "^1.0.5",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^9.3.3",
- "nanoid": "5.1.7",
+ "mongoose": "^9.6.2",
+ "nanoid": "5.1.11",
"nconf": "^0.13.0",
"node": "^25.9.0",
- "react": "^19.2.4",
- "react-dom": "^19.2.4",
+ "react": "^19.2.6",
+ "react-dom": "^19.2.6",
"react-frame-component": "^5.3.2",
- "react-router": "^7.14.0",
+ "react-router": "^7.15.1",
"sanitize-filename": "1.6.4",
"superagent": "^10.2.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^5.0.1",
- "babel-jest": "^30.3.0",
+ "babel-jest": "^30.4.1",
"babel-plugin-transform-import-meta": "^2.3.3",
"eslint": "9.7",
"eslint-plugin-jest": "^29.15.1",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.4.0",
- "jest": "^30.3.0",
+ "jest": "^30.4.2",
"jest-expect-message": "^1.1.3",
"jsdom": "^28.1.0",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
- "stylelint": "^17.6.0",
+ "stylelint": "^17.11.1",
"stylelint-config-recess-order": "^7.7.0",
"stylelint-config-recommended": "^18.0.0",
"supertest": "^7.1.4",
@@ -165,9 +177,9 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
- "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz",
+ "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -178,7 +190,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -537,6 +548,22 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": {
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz",
+ "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
"node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz",
@@ -1247,9 +1274,9 @@
}
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz",
- "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==",
+ "version": "7.29.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
+ "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.28.6",
@@ -1771,18 +1798,19 @@
}
},
"node_modules/@babel/preset-env": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz",
- "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==",
+ "version": "7.29.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz",
+ "integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==",
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.29.0",
+ "@babel/compat-data": "^7.29.3",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5",
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
+ "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6",
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
@@ -1814,7 +1842,7 @@
"@babel/plugin-transform-member-expression-literals": "^7.27.1",
"@babel/plugin-transform-modules-amd": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
- "@babel/plugin-transform-modules-systemjs": "^7.29.0",
+ "@babel/plugin-transform-modules-systemjs": "^7.29.4",
"@babel/plugin-transform-modules-umd": "^7.27.1",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0",
"@babel/plugin-transform-new-target": "^7.27.1",
@@ -2011,7 +2039,6 @@
"integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@keyv/serialize": "^1.1.1"
}
@@ -2037,6 +2064,498 @@
"@keyv/serialize": "^1.1.1"
}
},
+ "node_modules/@codemirror/autocomplete": {
+ "version": "6.20.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.2.tgz",
+ "integrity": "sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ==",
+ "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/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",
+ "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-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",
+ "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-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",
+ "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-jinja": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.1.tgz",
+ "integrity": "sha512-P5kyHLObzjtbGj16h+hyvZTxJhSjBEeSx4wMjbnAf3b0uwTy2+F0zGjMZL4PQOm/mh2eGZ5xUDVZXgwP783Nsw==",
+ "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.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",
+ "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/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.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",
+ "@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.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz",
+ "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==",
+ "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/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",
+ "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/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/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/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.43.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.0.tgz",
+ "integrity": "sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA==",
+ "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",
@@ -2058,9 +2577,9 @@
}
},
"node_modules/@csstools/css-calc": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz",
- "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz",
+ "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==",
"dev": true,
"funding": [
{
@@ -2125,7 +2644,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.19.0"
},
@@ -2134,9 +2652,9 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "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==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.4.tgz",
+ "integrity": "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==",
"dev": true,
"funding": [
{
@@ -2174,7 +2692,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.19.0"
}
@@ -2256,9 +2773,9 @@
"license": "Apache-2.0"
},
"node_modules/@emnapi/core": {
- "version": "1.9.2",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
- "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -2268,9 +2785,9 @@
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.9.2",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
- "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -2976,17 +3493,17 @@
}
},
"node_modules/@jest/console": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz",
- "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz",
+ "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "30.3.0",
+ "@jest/types": "30.4.1",
"@types/node": "*",
"chalk": "^4.1.2",
- "jest-message-util": "30.3.0",
- "jest-util": "30.3.0",
+ "jest-message-util": "30.4.1",
+ "jest-util": "30.4.1",
"slash": "^3.0.0"
},
"engines": {
@@ -2994,38 +3511,39 @@
}
},
"node_modules/@jest/core": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz",
- "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==",
+ "version": "30.4.2",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz",
+ "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/console": "30.3.0",
- "@jest/pattern": "30.0.1",
- "@jest/reporters": "30.3.0",
- "@jest/test-result": "30.3.0",
- "@jest/transform": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/console": "30.4.1",
+ "@jest/pattern": "30.4.0",
+ "@jest/reporters": "30.4.1",
+ "@jest/test-result": "30.4.1",
+ "@jest/transform": "30.4.1",
+ "@jest/types": "30.4.1",
"@types/node": "*",
"ansi-escapes": "^4.3.2",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
"exit-x": "^0.2.2",
+ "fast-json-stable-stringify": "^2.1.0",
"graceful-fs": "^4.2.11",
- "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.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",
+ "jest-changed-files": "30.4.1",
+ "jest-config": "30.4.2",
+ "jest-haste-map": "30.4.1",
+ "jest-message-util": "30.4.1",
+ "jest-regex-util": "30.4.0",
+ "jest-resolve": "30.4.1",
+ "jest-resolve-dependencies": "30.4.2",
+ "jest-runner": "30.4.2",
+ "jest-runtime": "30.4.2",
+ "jest-snapshot": "30.4.1",
+ "jest-util": "30.4.1",
+ "jest-validate": "30.4.1",
+ "jest-watcher": "30.4.1",
+ "pretty-format": "30.4.1",
"slash": "^3.0.0"
},
"engines": {
@@ -3041,9 +3559,9 @@
}
},
"node_modules/@jest/diff-sequences": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz",
- "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==",
+ "version": "30.4.0",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz",
+ "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3051,39 +3569,39 @@
}
},
"node_modules/@jest/environment": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz",
- "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz",
+ "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/fake-timers": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/fake-timers": "30.4.1",
+ "@jest/types": "30.4.1",
"@types/node": "*",
- "jest-mock": "30.3.0"
+ "jest-mock": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/expect": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz",
- "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz",
+ "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "expect": "30.3.0",
- "jest-snapshot": "30.3.0"
+ "expect": "30.4.1",
+ "jest-snapshot": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/expect-utils": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz",
- "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz",
+ "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3094,18 +3612,18 @@
}
},
"node_modules/@jest/fake-timers": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz",
- "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz",
+ "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "30.3.0",
- "@sinonjs/fake-timers": "^15.0.0",
+ "@jest/types": "30.4.1",
+ "@sinonjs/fake-timers": "^15.4.0",
"@types/node": "*",
- "jest-message-util": "30.3.0",
- "jest-mock": "30.3.0",
- "jest-util": "30.3.0"
+ "jest-message-util": "30.4.1",
+ "jest-mock": "30.4.1",
+ "jest-util": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
@@ -3122,47 +3640,47 @@
}
},
"node_modules/@jest/globals": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz",
- "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz",
+ "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/environment": "30.3.0",
- "@jest/expect": "30.3.0",
- "@jest/types": "30.3.0",
- "jest-mock": "30.3.0"
+ "@jest/environment": "30.4.1",
+ "@jest/expect": "30.4.1",
+ "@jest/types": "30.4.1",
+ "jest-mock": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/pattern": {
- "version": "30.0.1",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
- "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "version": "30.4.0",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
+ "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
- "jest-regex-util": "30.0.1"
+ "jest-regex-util": "30.4.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/reporters": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz",
- "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz",
+ "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^0.2.3",
- "@jest/console": "30.3.0",
- "@jest/test-result": "30.3.0",
- "@jest/transform": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/console": "30.4.1",
+ "@jest/test-result": "30.4.1",
+ "@jest/transform": "30.4.1",
+ "@jest/types": "30.4.1",
"@jridgewell/trace-mapping": "^0.3.25",
"@types/node": "*",
"chalk": "^4.1.2",
@@ -3175,9 +3693,9 @@
"istanbul-lib-report": "^3.0.0",
"istanbul-lib-source-maps": "^5.0.0",
"istanbul-reports": "^3.1.3",
- "jest-message-util": "30.3.0",
- "jest-util": "30.3.0",
- "jest-worker": "30.3.0",
+ "jest-message-util": "30.4.1",
+ "jest-util": "30.4.1",
+ "jest-worker": "30.4.1",
"slash": "^3.0.0",
"string-length": "^4.0.2",
"v8-to-istanbul": "^9.0.1"
@@ -3195,9 +3713,9 @@
}
},
"node_modules/@jest/schemas": {
- "version": "30.0.5",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
- "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
+ "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3208,13 +3726,13 @@
}
},
"node_modules/@jest/snapshot-utils": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz",
- "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz",
+ "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "30.3.0",
+ "@jest/types": "30.4.1",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
"natural-compare": "^1.4.0"
@@ -3239,14 +3757,14 @@
}
},
"node_modules/@jest/test-result": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz",
- "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz",
+ "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/console": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/console": "30.4.1",
+ "@jest/types": "30.4.1",
"@types/istanbul-lib-coverage": "^2.0.6",
"collect-v8-coverage": "^1.0.2"
},
@@ -3255,15 +3773,15 @@
}
},
"node_modules/@jest/test-sequencer": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz",
- "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz",
+ "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/test-result": "30.3.0",
+ "@jest/test-result": "30.4.1",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.3.0",
+ "jest-haste-map": "30.4.1",
"slash": "^3.0.0"
},
"engines": {
@@ -3271,23 +3789,23 @@
}
},
"node_modules/@jest/transform": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz",
- "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz",
+ "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.27.4",
- "@jest/types": "30.3.0",
+ "@jest/types": "30.4.1",
"@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.3.0",
- "jest-regex-util": "30.0.1",
- "jest-util": "30.3.0",
+ "jest-haste-map": "30.4.1",
+ "jest-regex-util": "30.4.0",
+ "jest-util": "30.4.1",
"pirates": "^4.0.7",
"slash": "^3.0.0",
"write-file-atomic": "^5.0.1"
@@ -3297,14 +3815,14 @@
}
},
"node_modules/@jest/types": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz",
- "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
+ "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.0.1",
- "@jest/schemas": "30.0.5",
+ "@jest/pattern": "30.4.0",
+ "@jest/schemas": "30.4.1",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -3367,10 +3885,193 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@lezer/common": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz",
+ "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==",
+ "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.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",
+ "@lezer/highlight": "^1.0.0",
+ "@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",
+ "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/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",
+ "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/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.10",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz",
+ "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==",
+ "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/@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",
+ "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",
- "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==",
+ "version": "1.4.11",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.11.tgz",
+ "integrity": "sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA==",
"license": "MIT",
"dependencies": {
"sparse-bitfield": "^3.0.3"
@@ -3394,7 +4095,6 @@
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
- "peer": true,
"engines": {
"node": "^14.21.3 || >=16"
},
@@ -3844,9 +4544,9 @@
}
},
"node_modules/@sinonjs/fake-timers": {
- "version": "15.3.0",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.0.tgz",
- "integrity": "sha512-m2xozxSfCIxjDdvbhIWazlP2i2aha/iUmbl94alpsIbd3iLTfeXgfBVbwyWogB6l++istyGZqamgA/EcqYf+Bg==",
+ "version": "15.4.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz",
+ "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -3876,9 +4576,9 @@
}
},
"node_modules/@tybys/wasm-util": {
- "version": "0.10.1",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
- "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -4216,9 +4916,9 @@
}
},
"node_modules/@ungap/structured-clone": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
- "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
+ "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
"dev": true,
"license": "ISC"
},
@@ -4530,7 +5230,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -4840,16 +5539,16 @@
}
},
"node_modules/babel-jest": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz",
- "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz",
+ "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/transform": "30.3.0",
+ "@jest/transform": "30.4.1",
"@types/babel__core": "^7.20.5",
"babel-plugin-istanbul": "^7.0.1",
- "babel-preset-jest": "30.3.0",
+ "babel-preset-jest": "30.4.0",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
"slash": "^3.0.0"
@@ -4882,9 +5581,9 @@
}
},
"node_modules/babel-plugin-jest-hoist": {
- "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==",
+ "version": "30.4.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz",
+ "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4975,13 +5674,13 @@
}
},
"node_modules/babel-preset-jest": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz",
- "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==",
+ "version": "30.4.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz",
+ "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "babel-plugin-jest-hoist": "30.3.0",
+ "babel-plugin-jest-hoist": "30.4.0",
"babel-preset-current-node-syntax": "^1.2.0"
},
"engines": {
@@ -5116,7 +5815,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -5406,11 +6104,13 @@
"node": ">= 0.12.0"
}
},
- "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"
+ "node_modules/codemirror-5-themes": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/codemirror-5-themes/-/codemirror-5-themes-1.5.1.tgz",
+ "integrity": "sha512-vHUyvOYy8yhqCDkps5LwoUFXHXIuKjE32n6EPP5v004fXmkZkrAC9mUsr+anNyOniaixE1W+xwcM4lgnbYfWsQ==",
+ "dependencies": {
+ "@codemirror/view": "^6.41.1"
+ }
},
"node_modules/collect-v8-coverage": {
"version": "1.0.3",
@@ -5627,6 +6327,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",
@@ -6290,7 +6996,6 @@
"integrity": "sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.11.0",
@@ -6631,18 +7336,18 @@
}
},
"node_modules/expect": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz",
- "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz",
+ "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/expect-utils": "30.3.0",
+ "@jest/expect-utils": "30.4.1",
"@jest/get-type": "30.1.0",
- "jest-matcher-utils": "30.3.0",
- "jest-message-util": "30.3.0",
- "jest-mock": "30.3.0",
- "jest-util": "30.3.0"
+ "jest-matcher-utils": "30.4.1",
+ "jest-message-util": "30.4.1",
+ "jest-mock": "30.4.1",
+ "jest-util": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
@@ -6704,9 +7409,9 @@
"license": "MIT"
},
"node_modules/express-static-gzip": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-3.0.0.tgz",
- "integrity": "sha512-36O10S0asHl3QojOBQQ0ZjXNtElmhgPS6erSUCCZymXkB/CK1mnGqOj4BTJN+FYRDIzVFnzo3wLFCZJvAk6rQQ==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-3.0.1.tgz",
+ "integrity": "sha512-LMeU/3YjFlFUa4vrPX+RoMMRW5mIpF4Iysgs6gX7A59WCY4BzyF3O28mBr4eMlWuW4DU9wVAVuVcfx29ln1N6g==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.1",
@@ -7079,9 +7784,9 @@
}
},
"node_modules/fs-extra": {
- "version": "11.3.4",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz",
- "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==",
+ "version": "11.3.5",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
+ "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
@@ -7210,9 +7915,9 @@
}
},
"node_modules/get-east-asian-width": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
- "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7342,9 +8047,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
- "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==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8222,16 +8927,6 @@
"node": ">=8"
}
},
- "node_modules/is-plain-object": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
- "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -8548,17 +9243,16 @@
}
},
"node_modules/jest": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz",
- "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==",
+ "version": "30.4.2",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz",
+ "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "@jest/core": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/core": "30.4.2",
+ "@jest/types": "30.4.1",
"import-local": "^3.2.0",
- "jest-cli": "30.3.0"
+ "jest-cli": "30.4.2"
},
"bin": {
"jest": "bin/jest.js"
@@ -8576,14 +9270,14 @@
}
},
"node_modules/jest-changed-files": {
- "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==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz",
+ "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==",
"dev": true,
"license": "MIT",
"dependencies": {
"execa": "^5.1.1",
- "jest-util": "30.3.0",
+ "jest-util": "30.4.1",
"p-limit": "^3.1.0"
},
"engines": {
@@ -8591,29 +9285,29 @@
}
},
"node_modules/jest-circus": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz",
- "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==",
+ "version": "30.4.2",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz",
+ "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/environment": "30.3.0",
- "@jest/expect": "30.3.0",
- "@jest/test-result": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/environment": "30.4.1",
+ "@jest/expect": "30.4.1",
+ "@jest/test-result": "30.4.1",
+ "@jest/types": "30.4.1",
"@types/node": "*",
"chalk": "^4.1.2",
"co": "^4.6.0",
"dedent": "^1.6.0",
"is-generator-fn": "^2.1.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",
+ "jest-each": "30.4.1",
+ "jest-matcher-utils": "30.4.1",
+ "jest-message-util": "30.4.1",
+ "jest-runtime": "30.4.2",
+ "jest-snapshot": "30.4.1",
+ "jest-util": "30.4.1",
"p-limit": "^3.1.0",
- "pretty-format": "30.3.0",
+ "pretty-format": "30.4.1",
"pure-rand": "^7.0.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
@@ -8623,21 +9317,21 @@
}
},
"node_modules/jest-cli": {
- "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==",
+ "version": "30.4.2",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz",
+ "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/core": "30.3.0",
- "@jest/test-result": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/core": "30.4.2",
+ "@jest/test-result": "30.4.1",
+ "@jest/types": "30.4.1",
"chalk": "^4.1.2",
"exit-x": "^0.2.2",
"import-local": "^3.2.0",
- "jest-config": "30.3.0",
- "jest-util": "30.3.0",
- "jest-validate": "30.3.0",
+ "jest-config": "30.4.2",
+ "jest-util": "30.4.1",
+ "jest-validate": "30.4.1",
"yargs": "^17.7.2"
},
"bin": {
@@ -8656,33 +9350,33 @@
}
},
"node_modules/jest-config": {
- "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==",
+ "version": "30.4.2",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz",
+ "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==",
"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.3.0",
- "@jest/types": "30.3.0",
- "babel-jest": "30.3.0",
+ "@jest/pattern": "30.4.0",
+ "@jest/test-sequencer": "30.4.1",
+ "@jest/types": "30.4.1",
+ "babel-jest": "30.4.1",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
"deepmerge": "^4.3.1",
"glob": "^10.5.0",
"graceful-fs": "^4.2.11",
- "jest-circus": "30.3.0",
- "jest-docblock": "30.2.0",
- "jest-environment-node": "30.3.0",
- "jest-regex-util": "30.0.1",
- "jest-resolve": "30.3.0",
- "jest-runner": "30.3.0",
- "jest-util": "30.3.0",
- "jest-validate": "30.3.0",
+ "jest-circus": "30.4.2",
+ "jest-docblock": "30.4.0",
+ "jest-environment-node": "30.4.1",
+ "jest-regex-util": "30.4.0",
+ "jest-resolve": "30.4.1",
+ "jest-runner": "30.4.2",
+ "jest-util": "30.4.1",
+ "jest-validate": "30.4.1",
"parse-json": "^5.2.0",
- "pretty-format": "30.3.0",
+ "pretty-format": "30.4.1",
"slash": "^3.0.0",
"strip-json-comments": "^3.1.1"
},
@@ -8707,25 +9401,25 @@
}
},
"node_modules/jest-diff": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz",
- "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz",
+ "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/diff-sequences": "30.3.0",
+ "@jest/diff-sequences": "30.4.0",
"@jest/get-type": "30.1.0",
"chalk": "^4.1.2",
- "pretty-format": "30.3.0"
+ "pretty-format": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-docblock": {
- "version": "30.2.0",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz",
- "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==",
+ "version": "30.4.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz",
+ "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8736,36 +9430,36 @@
}
},
"node_modules/jest-each": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz",
- "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz",
+ "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/get-type": "30.1.0",
- "@jest/types": "30.3.0",
+ "@jest/types": "30.4.1",
"chalk": "^4.1.2",
- "jest-util": "30.3.0",
- "pretty-format": "30.3.0"
+ "jest-util": "30.4.1",
+ "pretty-format": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-environment-node": {
- "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==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz",
+ "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/environment": "30.3.0",
- "@jest/fake-timers": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/environment": "30.4.1",
+ "@jest/fake-timers": "30.4.1",
+ "@jest/types": "30.4.1",
"@types/node": "*",
- "jest-mock": "30.3.0",
- "jest-util": "30.3.0",
- "jest-validate": "30.3.0"
+ "jest-mock": "30.4.1",
+ "jest-util": "30.4.1",
+ "jest-validate": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
@@ -8779,20 +9473,20 @@
"license": "MIT"
},
"node_modules/jest-haste-map": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz",
- "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
+ "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "30.3.0",
+ "@jest/types": "30.4.1",
"@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.3.0",
- "jest-worker": "30.3.0",
+ "jest-regex-util": "30.4.0",
+ "jest-util": "30.4.1",
+ "jest-worker": "30.4.1",
"picomatch": "^4.0.3",
"walker": "^1.0.8"
},
@@ -8804,49 +9498,50 @@
}
},
"node_modules/jest-leak-detector": {
- "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==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz",
+ "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/get-type": "30.1.0",
- "pretty-format": "30.3.0"
+ "pretty-format": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-matcher-utils": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz",
- "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz",
+ "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/get-type": "30.1.0",
"chalk": "^4.1.2",
- "jest-diff": "30.3.0",
- "pretty-format": "30.3.0"
+ "jest-diff": "30.4.1",
+ "pretty-format": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-message-util": {
- "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==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
+ "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@jest/types": "30.3.0",
+ "@jest/types": "30.4.1",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
+ "jest-util": "30.4.1",
"picomatch": "^4.0.3",
- "pretty-format": "30.3.0",
+ "pretty-format": "30.4.1",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
},
@@ -8855,15 +9550,15 @@
}
},
"node_modules/jest-mock": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz",
- "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz",
+ "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "30.3.0",
+ "@jest/types": "30.4.1",
"@types/node": "*",
- "jest-util": "30.3.0"
+ "jest-util": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
@@ -8888,9 +9583,9 @@
}
},
"node_modules/jest-regex-util": {
- "version": "30.0.1",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
- "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "version": "30.4.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
+ "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8898,18 +9593,18 @@
}
},
"node_modules/jest-resolve": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz",
- "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz",
+ "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.3.0",
+ "jest-haste-map": "30.4.1",
"jest-pnp-resolver": "^1.2.3",
- "jest-util": "30.3.0",
- "jest-validate": "30.3.0",
+ "jest-util": "30.4.1",
+ "jest-validate": "30.4.1",
"slash": "^3.0.0",
"unrs-resolver": "^1.7.11"
},
@@ -8918,46 +9613,46 @@
}
},
"node_modules/jest-resolve-dependencies": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz",
- "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==",
+ "version": "30.4.2",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz",
+ "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "jest-regex-util": "30.0.1",
- "jest-snapshot": "30.3.0"
+ "jest-regex-util": "30.4.0",
+ "jest-snapshot": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-runner": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz",
- "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==",
+ "version": "30.4.2",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz",
+ "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@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",
+ "@jest/console": "30.4.1",
+ "@jest/environment": "30.4.1",
+ "@jest/test-result": "30.4.1",
+ "@jest/transform": "30.4.1",
+ "@jest/types": "30.4.1",
"@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.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",
+ "jest-docblock": "30.4.0",
+ "jest-environment-node": "30.4.1",
+ "jest-haste-map": "30.4.1",
+ "jest-leak-detector": "30.4.1",
+ "jest-message-util": "30.4.1",
+ "jest-resolve": "30.4.1",
+ "jest-runtime": "30.4.2",
+ "jest-util": "30.4.1",
+ "jest-watcher": "30.4.1",
+ "jest-worker": "30.4.1",
"p-limit": "^3.1.0",
"source-map-support": "0.5.13"
},
@@ -8966,32 +9661,32 @@
}
},
"node_modules/jest-runtime": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz",
- "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==",
+ "version": "30.4.2",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz",
+ "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/environment": "30.3.0",
- "@jest/fake-timers": "30.3.0",
- "@jest/globals": "30.3.0",
+ "@jest/environment": "30.4.1",
+ "@jest/fake-timers": "30.4.1",
+ "@jest/globals": "30.4.1",
"@jest/source-map": "30.0.1",
- "@jest/test-result": "30.3.0",
- "@jest/transform": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/test-result": "30.4.1",
+ "@jest/transform": "30.4.1",
+ "@jest/types": "30.4.1",
"@types/node": "*",
"chalk": "^4.1.2",
"cjs-module-lexer": "^2.1.0",
"collect-v8-coverage": "^1.0.2",
"glob": "^10.5.0",
"graceful-fs": "^4.2.11",
- "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.3.0",
- "jest-snapshot": "30.3.0",
- "jest-util": "30.3.0",
+ "jest-haste-map": "30.4.1",
+ "jest-message-util": "30.4.1",
+ "jest-mock": "30.4.1",
+ "jest-regex-util": "30.4.0",
+ "jest-resolve": "30.4.1",
+ "jest-snapshot": "30.4.1",
+ "jest-util": "30.4.1",
"slash": "^3.0.0",
"strip-bom": "^4.0.0"
},
@@ -9000,9 +9695,9 @@
}
},
"node_modules/jest-snapshot": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz",
- "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz",
+ "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9011,20 +9706,20 @@
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-syntax-typescript": "^7.27.1",
"@babel/types": "^7.27.3",
- "@jest/expect-utils": "30.3.0",
+ "@jest/expect-utils": "30.4.1",
"@jest/get-type": "30.1.0",
- "@jest/snapshot-utils": "30.3.0",
- "@jest/transform": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/snapshot-utils": "30.4.1",
+ "@jest/transform": "30.4.1",
+ "@jest/types": "30.4.1",
"babel-preset-current-node-syntax": "^1.2.0",
"chalk": "^4.1.2",
- "expect": "30.3.0",
+ "expect": "30.4.1",
"graceful-fs": "^4.2.11",
- "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",
+ "jest-diff": "30.4.1",
+ "jest-matcher-utils": "30.4.1",
+ "jest-message-util": "30.4.1",
+ "jest-util": "30.4.1",
+ "pretty-format": "30.4.1",
"semver": "^7.7.2",
"synckit": "^0.11.8"
},
@@ -9033,9 +9728,9 @@
}
},
"node_modules/jest-snapshot/node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
+ "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -9046,13 +9741,13 @@
}
},
"node_modules/jest-util": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz",
- "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
+ "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "30.3.0",
+ "@jest/types": "30.4.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -9064,18 +9759,18 @@
}
},
"node_modules/jest-validate": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz",
- "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz",
+ "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/get-type": "30.1.0",
- "@jest/types": "30.3.0",
+ "@jest/types": "30.4.1",
"camelcase": "^6.3.0",
"chalk": "^4.1.2",
"leven": "^3.1.0",
- "pretty-format": "30.3.0"
+ "pretty-format": "30.4.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
@@ -9095,19 +9790,19 @@
}
},
"node_modules/jest-watcher": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz",
- "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz",
+ "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/test-result": "30.3.0",
- "@jest/types": "30.3.0",
+ "@jest/test-result": "30.4.1",
+ "@jest/types": "30.4.1",
"@types/node": "*",
"ansi-escapes": "^4.3.2",
"chalk": "^4.1.2",
"emittery": "^0.13.1",
- "jest-util": "30.3.0",
+ "jest-util": "30.4.1",
"string-length": "^4.0.2"
},
"engines": {
@@ -9115,15 +9810,15 @@
}
},
"node_modules/jest-worker": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz",
- "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
+ "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.3.0",
+ "jest-util": "30.4.1",
"merge-stream": "^2.0.0",
"supports-color": "^8.1.1"
},
@@ -9171,7 +9866,6 @@
"integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.31",
"@asamuzakjp/dom-selector": "^6.8.1",
@@ -9337,9 +10031,9 @@
}
},
"node_modules/kareem": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.2.0.tgz",
- "integrity": "sha512-VS8MWZz/cT+SqBCpVfNN4zoVz5VskR3N4+sTmUXme55e9avQHntpwpNq0yjnosISXqwJ3AQVjlbI4Dyzv//JtA==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.3.0.tgz",
+ "integrity": "sha512-kpSuLD3/7RenBnjnJdOHXCKC8dTd1JzeOiJhN0necWWci6cC+qX+VuwPnMVgb+a4+KNJSfgqahpnfWaeDXCimw==",
"license": "Apache-2.0",
"engines": {
"node": ">=18.0.0"
@@ -9522,9 +10216,9 @@
}
},
"node_modules/make-dir/node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
+ "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -9549,7 +10243,6 @@
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
"license": "MIT",
- "peer": true,
"bin": {
"marked": "bin/marked.js"
},
@@ -9907,13 +10600,13 @@
}
},
"node_modules/mongoose": {
- "version": "9.4.1",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.4.1.tgz",
- "integrity": "sha512-4rFBWa+/wdBQSfvnOPJBpiSG6UCEbhSQh865dEdaH9Y8WfHBUC+I2XT28dp0IBIGrEwmh+gzrgZgea5PbmrHWA==",
+ "version": "9.6.2",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.6.2.tgz",
+ "integrity": "sha512-7m8HntjkoRnwEmuPC0kdlwcZXJOQf4twumFj+PNzg/anqqZE2Er7hQslqyzy07mP3JcFjoTSgH5765PyqOXsxw==",
"license": "MIT",
"dependencies": {
- "kareem": "3.2.0",
- "mongodb": "~7.1",
+ "kareem": "3.3.0",
+ "mongodb": "~7.2",
"mpath": "0.9.0",
"mquery": "6.0.0",
"ms": "2.1.3",
@@ -9944,13 +10637,13 @@
}
},
"node_modules/mongoose/node_modules/mongodb": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz",
- "integrity": "sha512-067DXiMjcpYQl6bGjWQoTUEE9UoRViTtKFcoqX7z08I+iDZv/emH1g8XEFiO3qiDfXAheT5ozl1VffDTKhIW/w==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.2.0.tgz",
+ "integrity": "sha512-F/2+BMZtLVhY30ioZp0dAmZ+IRZMBqI+nrv6t5+9/1AIwCa8sMRC3jBf81lpxMhnZgqq8CoUD503Z1oZWq1/sw==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.3.0",
- "bson": "^7.1.1",
+ "bson": "^7.2.0",
"mongodb-connection-string-url": "^7.0.0"
},
"engines": {
@@ -10014,9 +10707,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "5.1.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.7.tgz",
- "integrity": "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==",
+ "version": "5.1.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz",
+ "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==",
"funding": [
{
"type": "github",
@@ -10734,9 +11427,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.8",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
- "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"funding": [
{
"type": "opencollective",
@@ -10752,7 +11445,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -10808,7 +11500,6 @@
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -10823,6 +11514,7 @@
"integrity": "sha512-TXbU+h6vVRW+86c/+ewhWq9k7pr7ijASTnepVhCQiC87zAOTkvB1v2dHyWP+ggstSTX/PNvjzS+IOqzejndz9w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"postcss": "^8.4.20"
}
@@ -10863,15 +11555,16 @@
}
},
"node_modules/pretty-format": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz",
- "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==",
+ "version": "30.4.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
+ "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.0.5",
+ "@jest/schemas": "30.4.1",
"ansi-styles": "^5.2.0",
- "react-is": "^18.3.1"
+ "react-is-18": "npm:react-is@^18.3.1",
+ "react-is-19": "npm:react-is@^19.2.5"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
@@ -10895,7 +11588,6 @@
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -11035,26 +11727,24 @@
}
},
"node_modules/react": {
- "version": "19.2.4",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
- "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
+ "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "19.2.4",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
- "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
+ "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
- "react": "^19.2.4"
+ "react": "^19.2.6"
}
},
"node_modules/react-frame-component": {
@@ -11068,13 +11758,22 @@
"react-dom": ">= 16.8 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
- "node_modules/react-is": {
+ "node_modules/react-is-18": {
+ "name": "react-is",
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"dev": true,
"license": "MIT"
},
+ "node_modules/react-is-19": {
+ "name": "react-is",
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz",
+ "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/react-refresh": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
@@ -11085,9 +11784,9 @@
}
},
"node_modules/react-router": {
- "version": "7.14.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz",
- "integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==",
+ "version": "7.15.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz",
+ "integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
@@ -12094,6 +12793,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",
@@ -12102,9 +12807,9 @@
"license": "ISC"
},
"node_modules/stylelint": {
- "version": "17.6.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.6.0.tgz",
- "integrity": "sha512-tokrsMIVAR9vAQ/q3UVEr7S0dGXCi7zkCezPRnS2kqPUulvUh5Vgfwngrk4EoAoW7wnrThqTdnTFN5Ra7CaxIg==",
+ "version": "17.11.1",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.11.1.tgz",
+ "integrity": "sha512-+smN/HqVTggUx3iuAzOi9fPh8SrH+cJWlZrYVldXoJ06orWBhZ4Ue/QEp64oei6pVrAh4w3tG+Y12Vw7MbCFRQ==",
"dev": true,
"funding": [
{
@@ -12117,11 +12822,10 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
- "@csstools/css-calc": "^3.1.1",
+ "@csstools/css-calc": "^3.2.0",
"@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-syntax-patches-for-csstree": "^1.1.1",
+ "@csstools/css-syntax-patches-for-csstree": "^1.1.3",
"@csstools/css-tokenizer": "^4.0.0",
"@csstools/media-query-list-parser": "^5.0.0",
"@csstools/selector-resolve-nested": "^4.0.0",
@@ -12135,22 +12839,21 @@
"fastest-levenshtein": "^1.0.16",
"file-entry-cache": "^11.1.2",
"global-modules": "^2.0.0",
- "globby": "^16.1.1",
+ "globby": "^16.2.0",
"globjoin": "^0.1.4",
"html-tags": "^5.1.0",
"ignore": "^7.0.5",
"import-meta-resolve": "^4.2.0",
- "is-plain-object": "^5.0.0",
"mathml-tag-names": "^4.0.0",
"meow": "^14.1.0",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"picocolors": "^1.1.1",
- "postcss": "^8.5.8",
+ "postcss": "^8.5.14",
"postcss-safe-parser": "^7.0.1",
"postcss-selector-parser": "^7.1.1",
"postcss-value-parser": "^4.2.0",
- "string-width": "^8.2.0",
+ "string-width": "^8.2.1",
"supports-hyperlinks": "^4.4.0",
"svg-tags": "^1.0.0",
"table": "^6.9.0",
@@ -12261,9 +12964,9 @@
}
},
"node_modules/stylelint/node_modules/string-width": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz",
- "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==",
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
+ "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -13060,7 +13763,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -13130,6 +13832,12 @@
}
}
},
+ "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 2ac02e4a1..ffd9ac423 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
- "version": "3.21.0",
+ "version": "3.22.0",
"type": "module",
"engines": {
"npm": ">=10.8 <12",
@@ -58,7 +58,7 @@
"server"
],
"transformIgnorePatterns": [
- "node_modules/(?!(nanoid|@exodus/bytes|parse5|@asamuzakjp|@csstools)/)"
+ "node_modules/(?!(nanoid|@exodus/bytes|parse5|@asamuzakjp|@csstools|entities)/)"
],
"transform": {
"^.+\\.[jt]s$": "babel-jest",
@@ -88,16 +88,28 @@
"dependencies": {
"@babel/core": "^7.29.0",
"@babel/plugin-transform-runtime": "^7.29.0",
- "@babel/preset-env": "^7.29.2",
+ "@babel/preset-env": "^7.29.5",
"@babel/preset-react": "^7.28.5",
"@babel/runtime": "^7.29.2",
+ "@codemirror/autocomplete": "^6.20.2",
+ "@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/search": "^6.6.0",
+ "@codemirror/state": "^6.6.0",
+ "@codemirror/view": "^6.43.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",
"classnames": "^2.5.1",
- "codemirror": "^5.65.6",
+ "codemirror-5-themes": "^1.5.1",
"cookie-parser": "^1.4.7",
"core-js": "^3.49.0",
"cors": "^2.8.5",
@@ -105,9 +117,9 @@
"dedent": "^1.7.1",
"express": "^5.1.0",
"express-async-handler": "^1.2.0",
- "express-static-gzip": "3.0.0",
+ "express-static-gzip": "3.0.1",
"fflate": "^0.8.2",
- "fs-extra": "^11.3.3",
+ "fs-extra": "^11.3.5",
"hash-wasm": "^4.12.0",
"idb-keyval": "^6.2.2",
"js-yaml": "^4.1.1",
@@ -126,31 +138,31 @@
"marked-variables": "^1.0.5",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^9.3.3",
- "nanoid": "5.1.7",
+ "mongoose": "^9.6.2",
+ "nanoid": "5.1.11",
"nconf": "^0.13.0",
"node": "^25.9.0",
- "react": "^19.2.4",
- "react-dom": "^19.2.4",
+ "react": "^19.2.6",
+ "react-dom": "^19.2.6",
"react-frame-component": "^5.3.2",
- "react-router": "^7.14.0",
+ "react-router": "^7.15.1",
"sanitize-filename": "1.6.4",
"superagent": "^10.2.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^5.0.1",
- "babel-jest": "^30.3.0",
+ "babel-jest": "^30.4.1",
"babel-plugin-transform-import-meta": "^2.3.3",
"eslint": "9.7",
"eslint-plugin-jest": "^29.15.1",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.4.0",
- "jest": "^30.3.0",
+ "jest": "^30.4.2",
"jest-expect-message": "^1.1.3",
"jsdom": "^28.1.0",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
- "stylelint": "^17.6.0",
+ "stylelint": "^17.11.1",
"stylelint-config-recess-order": "^7.7.0",
"stylelint-config-recommended": "^18.0.0",
"supertest": "^7.1.4",
diff --git a/server/app.js b/server/app.js
index 18b1d68bc..b1e0e1c25 100644
--- a/server/app.js
+++ b/server/app.js
@@ -593,7 +593,7 @@ export default async function createApp(vite) {
html = html.replace(
'',
- `\n\n${ogMetaTags}`
+ ()=>{ return `\n\n${ogMetaTags}`; }
);
return html;
diff --git a/server/homebrew.api.js b/server/homebrew.api.js
index 58aa4a72e..3bab31c8a 100644
--- a/server/homebrew.api.js
+++ b/server/homebrew.api.js
@@ -32,20 +32,20 @@ const isStaticTheme = (renderer, themeName)=>{
// };
-const migrateSystemsToTags = (brew) => {
- if (!('systems' in brew)) return brew;
+const migrateSystemsToTags = (brew)=>{
+ if(!('systems' in brew)) return brew;
- if (!Array.isArray(brew.systems) || brew.systems.length === 0) {
+ if(!Array.isArray(brew.systems) || brew.systems.length === 0) {
brew.systems = undefined;
return brew;
}
const systemMap = {
- '5e': 'system:D&D 5e',
- '4e': 'system:D&D 4e',
- '3.5e': 'system:D&D 3.5e',
- 'Pathfinder': 'system:Pathfinder 2e'
+ '5e' : 'system:D&D 5e',
+ '4e' : 'system:D&D 4e',
+ '3.5e' : 'system:D&D 3.5e',
+ 'Pathfinder' : 'system:Pathfinder 2e'
};
- const systemTags = brew.systems.map(s => systemMap[s]);
+ const systemTags = brew.systems.map((s)=>systemMap[s]);
brew.tags = _.uniq([...(brew.tags || []), ...systemTags]);
brew.systems = undefined;
@@ -188,7 +188,7 @@ const api = {
stub.renderer = stub.renderer || undefined; // Clear empty strings
stub = _.defaults(stub, DEFAULT_BREW_LOAD); // Fill in blank fields
-
+
const fixedStub = migrateSystemsToTags(stub);
req.brew = fixedStub;
@@ -424,22 +424,29 @@ const api = {
if(brewFromServer?.hash !== brewFromClient?.hash) {
console.log(`Hash mismatch on brew ${brewFromClient.editId}`);
- //debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`);
+ debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`);
res.setHeader('Content-Type', 'application/json');
return res.status(409).send(JSON.stringify({ message: `The server copy is out of sync with the saved brew. Please save your changes elsewhere, refresh, and try again.` }));
}
+ let result = [];
try {
const patches = parsePatch(brewFromClient.patches);
// Patch to a throwaway variable while parallelizing - we're more concerned with error/no error.
- const patchedResult = decodeURI(applyPatches(patches, encodeURI(brewFromServer.text))[0]);
- if(patchedResult != brewFromClient.text)
+ result = applyPatches(patches, encodeURI(brewFromServer.text));
+ const failedPatches = patches.map((patch, index)=>{if(!result[1][index]){ return patch; }});
+ if(failedPatches > 0){
+ throw (`Patch failure: ${failedPatches}/${result[1].length} did not apply`);
+ }
+ if(decodeURI(result[0]) != brewFromClient.text){
throw ('Patches did not apply cleanly, text mismatch detected');
+ }
// brew.text = applyPatches(patches, brewFromServer.text)[0];
} catch (err) {
- //debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`);
+ debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`);
console.error('Failed to apply patches:', {
- //patches : brewFromClient.patches,
+ // patches : brewFromClient.patches,
+ // result : result,
brewId : brewFromClient.editId || 'unknown',
error : err
});
diff --git a/server/homebrew.model.js b/server/homebrew.model.js
index b3d7702ce..e923ac928 100644
--- a/server/homebrew.model.js
+++ b/server/homebrew.model.js
@@ -15,7 +15,7 @@ const HomebrewSchema = mongoose.Schema({
description : { type: String, default: '' },
tags : { type: [String], index: true },
- systems : { type: [String], default: undefined },
+ systems : { type: [String], default: undefined },
lang : { type: String, default: 'en', index: true },
renderer : { type: String, default: '', index: true },
authors : { type: [String], index: true },
diff --git a/shared/helpers.js b/shared/helpers.js
index 18dd127ff..b3231e176 100644
--- a/shared/helpers.js
+++ b/shared/helpers.js
@@ -194,9 +194,35 @@ const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{
// Char-level diff
for (let i = 0; i < Math.min(clientText.length, serverText.length); i++) {
if(clientText[i] !== serverText[i]) {
+ const getMismatchContext = (text, index, name, size = 10)=>{
+ const lower = Math.max(index - size, 0);
+ const upper = Math.min(index + size, text.length);
+ const slice = `${JSON.stringify(text.slice(lower, index)).slice(1, -1)}\u001B[31m${JSON.stringify(text[i]).slice(1, -1)}\u001B[0m${JSON.stringify(text.slice(index+1, upper)).slice(1, -1)}`;
+ const lineNo = text.slice(0, index).split('\n').length;
+ const code = `U+${text.charCodeAt(i).toString(16).toUpperCase()}`;
+
+ return {
+ name,
+ lineNo,
+ code,
+ lower,
+ upper,
+ slice
+ };
+ };
+
+ const boundSize = 10;
+
+ const clientContext = getMismatchContext(clientText, i, 'Client', boundSize);
+ const serverContext = getMismatchContext(serverText, i, 'Server', boundSize);
+
+ const logContext = (context)=>{
+ console.log(` ${context.name} - line ${context.lineNo} : (${context.code})\t${context.slice}`);
+ };
+
console.log(`Char mismatch at index ${i}:`);
- console.log(` Client: '${clientText[i]}' (U+${clientText.charCodeAt(i).toString(16).toUpperCase()})`);
- console.log(` Server: '${serverText[i]}' (U+${serverText.charCodeAt(i).toString(16).toUpperCase()})`);
+ logContext(clientContext);
+ logContext(serverContext);
break;
}
}
diff --git a/themes/V3/Blank/snippets/imageMask.gen.js b/themes/V3/Blank/snippets/imageMask.gen.js
index 530ce01ac..833225b36 100644
--- a/themes/V3/Blank/snippets/imageMask.gen.js
+++ b/themes/V3/Blank/snippets/imageMask.gen.js
@@ -16,17 +16,17 @@ export default {
edge : (side = 'bottom')=>{
const styles = ()=>{
switch (side) {
- case 'bottom':
- return `{width:100%,bottom:0%}`
- break;
- case 'top':
- return `{width:100%,top:0%}`
- break;
- default:
- return `{height:100%}`
- break;
+ case 'bottom':
+ return `{width:100%,bottom:0%}`;
+ break;
+ case 'top':
+ return `{width:100%,top:0%}`;
+ break;
+ default:
+ return `{height:100%}`;
+ break;
}
- }
+ };
const rotation = {
'bottom' : 0,
diff --git a/themes/codeMirror/customEditorStyles.less b/themes/codeMirror/customEditorStyles.less
deleted file mode 100644
index 8c48c1b43..000000000
--- a/themes/codeMirror/customEditorStyles.less
+++ /dev/null
@@ -1,83 +0,0 @@
-.editor .codeEditor .CodeMirror {
- // 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.css b/themes/codeMirror/customThemes/darkbrewery.css
deleted file mode 100644
index 6fba4001c..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);
- .CodeMirror-scroll {
- .CodeMirror-gutters {
- border-right: 1px solid #555;
- background: var(--bg);
- .CodeMirror-gutter {
- background-color: var(--bg);
- &.CodeMirror-foldgutter {
- cursor: pointer;
- border-left: 1px solid #555;
- transition: background 0.1s;
- &:hover {
- background: #555;
- }
- }
- }
- }
- .CodeMirror-lines {
- /* Line numbers*/
- .CodeMirror-linenumber.CodeMirror-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/darkvision.css b/themes/codeMirror/customThemes/darkvision.css
deleted file mode 100644
index 4c74d105e..000000000
--- a/themes/codeMirror/customThemes/darkvision.css
+++ /dev/null
@@ -1,121 +0,0 @@
-.CodeMirror {
- background: #0C0C0C;
- color: #B9BDB6;
-}
-
-/* Brew BG */
-.brewRenderer {
- background-color: #0C0C0C;
-}
-
-.cm-s-darkvision {
- /* Blinking cursor and selection */
- .CodeMirror-cursor {
- border-left: 1px solid #B9BDB6;
- }
- .CodeMirror-selected {
- background: #E0E8FF40;
- }
-
- /* Line number stuff */
- .CodeMirror-gutter-elt {
- color: #81969A;
- }
- .CodeMirror-linenumber {
- background-color: #0C0C0C;
- }
- .CodeMirror-gutter {
- background-color: #0C0C0C;
- }
-
- /* column splits */
- .editor .codeEditor .columnSplit {
- font-style: italic;
- color: inherit;
- background-color:#1F5763;
- border-bottom: #299 solid 1px;
- }
-
- /* # headings */
- .cm-header {
- color: #C51B1B;
- -webkit-text-stroke-width: 0.1px;
- }
- /* bold points */
- .cm-strong {
- font-weight: bold;
- color: #309DD2;
- }
- /* Link headings */
- .cm-link {
- color: #DD6300;
- }
- /* links */
- .cm-string {
- color: #5CE638;
- }
- /*@import*/
- .cm-def {
- color: #2986CC;
- }
- /* Bullets and such */
- .cm-variable-2 {
- color: #3CBF30;
- }
-
- /* Tags (divs) */
- .cm-tag {
- color: #E3FF00;
- }
- .cm-attribute {
- color: #E3FF00;
- }
- .cm-atom {
- color: #CF7EA9;
- }
- .cm-qualifier {
- color: #EE1919;
- }
- .cm-comment {
- color: #BBC700;
- }
- .cm-keyword {
- color: #CC66FF;
- }
- .cm-property {
- color: aqua;
- }
- .cm-error {
- color: #C50202;
- }
- .CodeMirror-foldmarker {
- color: #F0FF00;
- }
- /* New page */
- .cm-builtin {
- color: #FFF;
- }
-}
-
-.editor .codeEditor {
- /* blocks */
- .block:not(.cm-comment) {
- color: magenta;
- }
- /* definition lists */
- .define.definition {
- color: #FFAA3E;
- }
- .define.term {
- color: #7290d9;
- }
- .define:not(.term):not(.definition) {
- background: #333;
- }
- /* New page */
- .pageLine {
- background: #000;
- color: #000;
- border-bottom: 1px solid #FFF;
- }
-}
diff --git a/themes/codeMirror/darkbrewery.js b/themes/codeMirror/darkbrewery.js
new file mode 100644
index 000000000..202159715
--- /dev/null
+++ b/themes/codeMirror/darkbrewery.js
@@ -0,0 +1,117 @@
+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-selectionBackground' : {
+ backgroundColor : '#d7d4f0',
+ },
+ '&.cm-focused .cm-selectionBackground' : {
+ backgroundColor : '#d7d4f0 !important',
+ },
+ '.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/default.js b/themes/codeMirror/default.js
new file mode 100644
index 000000000..78579a123
--- /dev/null
+++ b/themes/codeMirror/default.js
@@ -0,0 +1,81 @@
+import { EditorView } from '@codemirror/view';
+
+//This theme is made of the base css for the codemirror 5 editor
+
+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' : {
+ backgroundColor : '#becee374',
+ },
+ '.cm-gutterElement.cm-activeLineGutter' : {
+ backgroundColor : '#becee374',
+ },
+ '.cm-selectionBackground ' : {
+ backgroundColor : '#d7d4f0',
+ },
+ '.cm-foldmarker' : {
+ color : 'blue',
+ fontFamily : 'arial',
+ lineHeight : '0.3',
+ cursor : 'pointer',
+ },
+
+ '.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: '#ff96004d' },
+ '.cm-quote' : { color: '#090' },
+}, { dark: false });
\ No newline at end of file
diff --git a/vitePlugins/generateAssetsPlugin.js b/vitePlugins/generateAssetsPlugin.js
index caea2c1e8..749cc5636 100644
--- a/vitePlugins/generateAssetsPlugin.js
+++ b/vitePlugins/generateAssetsPlugin.js
@@ -61,19 +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
- 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),
- );
-
- // Copy remaining CodeMirror assets
- await fs.copy('./themes/codeMirror', `${buildDir}/homebrew/codeMirror`);
},
};
}