mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-06-22 04:58:40 +00:00
Merge branch 'master' of https://github.com/naturalcrit/homebrewery into add-cm-features
This commit is contained in:
@@ -17,8 +17,7 @@ import {
|
|||||||
crosshairCursor,
|
crosshairCursor,
|
||||||
} from '@codemirror/view';
|
} from '@codemirror/view';
|
||||||
import { EditorState, Compartment, StateEffect, StateField } from '@codemirror/state';
|
import { EditorState, Compartment, StateEffect, StateField } from '@codemirror/state';
|
||||||
import { foldAll as foldAllCmd, unfoldAll as unfoldAllCmd, foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language';
|
import { foldAll as foldAllCmd, unfoldAll as unfoldAllCmd, foldGutter, foldKeymap, foldEffect, foldState, syntaxHighlighting } from '@codemirror/language';
|
||||||
import { foldEffect } from '@codemirror/language';
|
|
||||||
import { defaultKeymap, history, undo, redo, undoDepth, redoDepth } from '@codemirror/commands';
|
import { defaultKeymap, history, undo, redo, undoDepth, redoDepth } from '@codemirror/commands';
|
||||||
import { languages } from '@codemirror/language-data';
|
import { languages } from '@codemirror/language-data';
|
||||||
import { css } from '@codemirror/lang-css';
|
import { css } from '@codemirror/lang-css';
|
||||||
@@ -38,8 +37,6 @@ const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
|
|||||||
const themeCompartment = new Compartment();
|
const themeCompartment = new Compartment();
|
||||||
const highlightCompartment = new Compartment();
|
const highlightCompartment = new Compartment();
|
||||||
|
|
||||||
console.log(themes);
|
|
||||||
|
|
||||||
import { generalKeymap, markdownKeymap } from './customKeyMaps.js';
|
import { generalKeymap, markdownKeymap } from './customKeyMaps.js';
|
||||||
import foldOnPages from './customFolding.js';
|
import foldOnPages from './customFolding.js';
|
||||||
import { customHighlightStyle, tokenizeCustomMarkdown, tokenizeCustomCSS } from './customHighlight.js';
|
import { customHighlightStyle, tokenizeCustomMarkdown, tokenizeCustomCSS } from './customHighlight.js';
|
||||||
@@ -151,11 +148,14 @@ const CodeEditor = forwardRef(
|
|||||||
const editorRef = useRef(null);
|
const editorRef = useRef(null);
|
||||||
const viewRef = useRef(null);
|
const viewRef = useRef(null);
|
||||||
const docsRef = useRef({});
|
const docsRef = useRef({});
|
||||||
|
const tabRef = useRef(tab);
|
||||||
const prevTabRef = useRef(tab);
|
const prevTabRef = useRef(tab);
|
||||||
|
const scrollRef = useRef({});
|
||||||
|
const foldsRef = useRef({});
|
||||||
const pageMap = useRef([]);
|
const pageMap = useRef([]);
|
||||||
|
|
||||||
const recomputePages = (doc)=>{
|
const recomputePages = (doc)=>{
|
||||||
|
if(tab !== 'brewText') return;
|
||||||
const pages = [0];
|
const pages = [0];
|
||||||
const text = doc.toString();
|
const text = doc.toString();
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
@@ -181,6 +181,14 @@ const CodeEditor = forwardRef(
|
|||||||
return page;
|
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 createExtensions = ({ onChange, language, editorTheme })=>{
|
||||||
const setEventListeners = EditorView.updateListener.of((update)=>{
|
const setEventListeners = EditorView.updateListener.of((update)=>{
|
||||||
if(update.docChanged) {
|
if(update.docChanged) {
|
||||||
@@ -267,11 +275,10 @@ const CodeEditor = forwardRef(
|
|||||||
ticking = true;
|
ticking = true;
|
||||||
requestAnimationFrame(()=>{
|
requestAnimationFrame(()=>{
|
||||||
const top = view.scrollDOM.scrollTop;
|
const top = view.scrollDOM.scrollTop;
|
||||||
|
scrollRef.current[tabRef.current] = top;
|
||||||
const block = view.lineBlockAtHeight(top);
|
const block = view.lineBlockAtHeight(top);
|
||||||
|
const page = findPageFromPos(block.from);
|
||||||
const page = findPageFromPos(block.from); // CHANGED
|
|
||||||
onViewChange(page);
|
onViewChange(page);
|
||||||
|
|
||||||
ticking = false;
|
ticking = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -286,12 +293,23 @@ const CodeEditor = forwardRef(
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const restoreFolds = (view, folds)=>{
|
||||||
|
if(!folds?.length) return;
|
||||||
|
|
||||||
|
view.dispatch({
|
||||||
|
effects : folds.map((f)=>foldEffect.of(f))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
const view = viewRef.current;
|
const view = viewRef.current;
|
||||||
if(!view) return;
|
if(!view) return;
|
||||||
|
|
||||||
|
tabRef.current = tab;
|
||||||
const prevTab = prevTabRef.current;
|
const prevTab = prevTabRef.current;
|
||||||
|
|
||||||
|
foldsRef.current[prevTab] = getFoldRanges(view.state);
|
||||||
|
|
||||||
if(prevTab !== tab) {
|
if(prevTab !== tab) {
|
||||||
docsRef.current[prevTab] = view.state;
|
docsRef.current[prevTab] = view.state;
|
||||||
|
|
||||||
@@ -305,6 +323,16 @@ const CodeEditor = forwardRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
view.setState(nextState);
|
view.setState(nextState);
|
||||||
|
restoreFolds(view, foldsRef.current[tab]);
|
||||||
|
|
||||||
|
const savedScroll = scrollRef.current[tab];
|
||||||
|
|
||||||
|
if(savedScroll != null) {
|
||||||
|
requestAnimationFrame(()=>{
|
||||||
|
view.scrollDOM.scrollTop = savedScroll;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
prevTabRef.current = tab;
|
prevTabRef.current = tab;
|
||||||
}
|
}
|
||||||
view.focus();
|
view.focus();
|
||||||
|
|||||||
@@ -100,6 +100,10 @@
|
|||||||
vertical-align : sub;
|
vertical-align : sub;
|
||||||
color : rgb(123, 123, 15);
|
color : rgb(123, 123, 15);
|
||||||
}
|
}
|
||||||
|
.cm-strikethrough {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-definitionList {
|
.cm-definitionList {
|
||||||
.cm-definitionTerm { color : rgb(96, 117, 143); }
|
.cm-definitionTerm { color : rgb(96, 117, 143); }
|
||||||
.cm-definitionColon:not(:has(.cm-comment)) {
|
.cm-definitionColon:not(:has(.cm-comment)) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const customTags = {
|
|||||||
definitionTerm : 'definitionTerm', // .cm-definitionTerm
|
definitionTerm : 'definitionTerm', // .cm-definitionTerm
|
||||||
definitionDesc : 'definitionDesc', // .cm-definitionDesc
|
definitionDesc : 'definitionDesc', // .cm-definitionDesc
|
||||||
definitionColon : 'definitionColon', // .cm-definitionColon
|
definitionColon : 'definitionColon', // .cm-definitionColon
|
||||||
|
strikethrough : 'strikethrough', // .cm-strikethrough
|
||||||
|
|
||||||
//CSS
|
//CSS
|
||||||
|
|
||||||
@@ -81,6 +82,23 @@ export function tokenizeCustomMarkdown(text) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Strikethrough ---
|
||||||
|
if(/\~/.test(lineText)) {
|
||||||
|
const strikethroughRegex = /~(?!\s)(.+?)(?<!\s)~/g;
|
||||||
|
|
||||||
|
let match = strikethroughRegex.exec(lineText);
|
||||||
|
let type = customTags.strikethrough;
|
||||||
|
|
||||||
|
if(match) {
|
||||||
|
tokens.push({
|
||||||
|
line : lineNumber,
|
||||||
|
type,
|
||||||
|
from : match.index,
|
||||||
|
to : match.index + match[0].length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- single line def list ---
|
// --- single line def list ---
|
||||||
const singleLineRegex = /^(?=.*[^:])(.+?)(\s*)(::)([^\n]*)$/dmy;
|
const singleLineRegex = /^(?=.*[^:])(.+?)(\s*)(::)([^\n]*)$/dmy;
|
||||||
const match = singleLineRegex.exec(lineText);
|
const match = singleLineRegex.exec(lineText);
|
||||||
@@ -182,13 +200,13 @@ export function tokenizeCustomMarkdown(text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(lineText.includes('{') && lineText.includes('}')) {
|
if(lineText.includes('{') && lineText.includes('}')) {
|
||||||
const injectionRegex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm;
|
const injectionRegex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gmd;
|
||||||
let match;
|
let match;
|
||||||
while ((match = injectionRegex.exec(lineText)) !== null) {
|
while ((match = injectionRegex.exec(lineText)) !== null) {
|
||||||
tokens.push({
|
tokens.push({
|
||||||
line : lineNumber,
|
line : lineNumber,
|
||||||
from : match.index,
|
from : match.indices[1][0],
|
||||||
to : match.index + match[1].length,
|
to : match.indices[1][1],
|
||||||
type : customTags.injection,
|
type : customTags.injection,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,17 @@ async function formatCSS(view) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 indentLess = (view)=>{
|
||||||
const { from, to } = view.state.selection.main;
|
const { from, to } = view.state.selection.main;
|
||||||
const lines = [];
|
const lines = [];
|
||||||
@@ -37,27 +48,25 @@ const indentLess = (view)=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const wrapSelection = (prefix, suffix) => (view) => {
|
const wrapSelection = (prefix, suffix) => (view) => {
|
||||||
const { from, to } = view.state.selection.main;
|
const changes = [];
|
||||||
const selected = view.state.doc.sliceString(from, to);
|
|
||||||
|
|
||||||
let text, selection;
|
for(const range of view.state.selection.ranges) {
|
||||||
|
const { from, to } = range;
|
||||||
|
const selected = view.state.doc.sliceString(from, to);
|
||||||
|
|
||||||
if(from === to) {
|
let text;
|
||||||
text = prefix + suffix;
|
|
||||||
selection = { anchor: from + prefix.length, head: from + prefix.length };
|
if(from === to) { text = prefix + suffix }
|
||||||
}
|
else if(selected.startsWith(prefix) && selected.endsWith(suffix)) {
|
||||||
else if(selected.startsWith(prefix) && selected.endsWith(suffix)) {
|
text = selected.slice(prefix.length, -suffix.length);
|
||||||
text = selected.slice(prefix.length, -suffix.length);
|
}
|
||||||
selection = { anchor: from, head: from + text.length };
|
else {text = `${prefix}${selected}${suffix}`}
|
||||||
}
|
|
||||||
else {
|
changes.push({ from, to, insert: text });
|
||||||
text = `${prefix}${selected}${suffix}`;
|
|
||||||
selection = { anchor: from, head: from + text.length };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes : { from, to, insert: text },
|
changes
|
||||||
selection
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -181,7 +190,7 @@ const newPage = (view)=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const generalKeymap = Prec.high(keymap.of([
|
export const generalKeymap = Prec.high(keymap.of([
|
||||||
{ key: 'Tab', run: indentMore },
|
{ key: 'Tab', run: insertTab },
|
||||||
{ key: 'Mod-z', run: undo }, //i think it may be unnecessary
|
{ key: 'Mod-z', run: undo }, //i think it may be unnecessary
|
||||||
{ key: 'Mod-Shift-z', run: redo },
|
{ key: 'Mod-Shift-z', run: redo },
|
||||||
{ key: 'Mod-y', run: redo },
|
{ key: 'Mod-y', run: redo },
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ const Snippetbar = createReactClass({
|
|||||||
this.props.updateEditorTheme(e.target.value);
|
this.props.updateEditorTheme(e.target.value);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
showThemeSelector : false,
|
themeSelector : false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const EditPage = (props)=>{
|
|||||||
|
|
||||||
const handleControlKeys = (e)=>{
|
const handleControlKeys = (e)=>{
|
||||||
if(!(e.ctrlKey || e.metaKey)) return;
|
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(e.keyCode === 80) printCurrentBrew();
|
||||||
if([83, 80].includes(e.keyCode)) {
|
if([83, 80].includes(e.keyCode)) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -118,13 +118,9 @@ const EditPage = (props)=>{
|
|||||||
const hasChange = !_.isEqual(currentBrew, lastSavedBrew.current);
|
const hasChange = !_.isEqual(currentBrew, lastSavedBrew.current);
|
||||||
setUnsavedChanges(hasChange);
|
setUnsavedChanges(hasChange);
|
||||||
|
|
||||||
if(autoSaveEnabled) trySave(false, hasChange);
|
if(autoSaveEnabled) trySave(false, hasChange, saveGoogle);
|
||||||
}, [currentBrew]);
|
}, [currentBrew]);
|
||||||
|
|
||||||
useEffect(()=>{
|
|
||||||
trySave(true);
|
|
||||||
}, [saveGoogle]);
|
|
||||||
|
|
||||||
const handleSplitMove = ()=>{
|
const handleSplitMove = ()=>{
|
||||||
editorRef.current?.update();
|
editorRef.current?.update();
|
||||||
};
|
};
|
||||||
@@ -183,11 +179,13 @@ const EditPage = (props)=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toggleGoogleStorage = ()=>{
|
const toggleGoogleStorage = ()=>{
|
||||||
|
const newSaveGoogle = !saveGoogle;
|
||||||
setSaveGoogle((prev)=>!prev);
|
setSaveGoogle((prev)=>!prev);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
trySave(true, true, newSaveGoogle);
|
||||||
};
|
};
|
||||||
|
|
||||||
const trySave = (immediate = false, hasChanges = true)=>{
|
const trySave = (immediate = false, hasChanges = true, saveToGoogle = false)=>{
|
||||||
clearTimeout(saveTimeout.current);
|
clearTimeout(saveTimeout.current);
|
||||||
if(isSaving) return;
|
if(isSaving) return;
|
||||||
if(!hasChanges && !immediate) return;
|
if(!hasChanges && !immediate) return;
|
||||||
@@ -196,7 +194,7 @@ const EditPage = (props)=>{
|
|||||||
saveTimeout.current = setTimeout(async ()=>{
|
saveTimeout.current = setTimeout(async ()=>{
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
await save(currentBrew, saveGoogle)
|
await save(currentBrew, saveToGoogle)
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
setError(err);
|
setError(err);
|
||||||
});
|
});
|
||||||
@@ -216,7 +214,7 @@ const EditPage = (props)=>{
|
|||||||
const brewToSave = {
|
const brewToSave = {
|
||||||
...brew,
|
...brew,
|
||||||
text : brew.text.normalize('NFC'),
|
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')))),
|
patches : stringifyPatches(makePatches(encodeURI(lastSavedBrew.current.text.normalize('NFC')), encodeURI(brew.text.normalize('NFC')))),
|
||||||
hash : await md5(lastSavedBrew.current.text.normalize('NFC')),
|
hash : await md5(lastSavedBrew.current.text.normalize('NFC')),
|
||||||
textBin : undefined,
|
textBin : undefined,
|
||||||
@@ -314,7 +312,7 @@ const EditPage = (props)=>{
|
|||||||
|
|
||||||
// #3 - Unsaved changes exist, click to save, show SAVE NOW
|
// #3 - Unsaved changes exist, click to save, show SAVE NOW
|
||||||
if(unsavedChanges)
|
if(unsavedChanges)
|
||||||
return <Nav.item className='save' onClick={()=>trySave(true)} color='blue' icon='fas fa-save'>save now</Nav.item>;
|
return <Nav.item className='save' onClick={()=>trySave(true, true, saveGoogle)} color='blue' icon='fas fa-save'>save now</Nav.item>;
|
||||||
|
|
||||||
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
|
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
|
||||||
if(autoSaveEnabled)
|
if(autoSaveEnabled)
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ const NewPage = (props)=>{
|
|||||||
const updatedBrew = { ...currentBrew };
|
const updatedBrew = { ...currentBrew };
|
||||||
splitTextStyleAndMetadata(updatedBrew);
|
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;
|
updatedBrew.pageCount = (updatedBrew.text.match(pageRegex) || []).length + 1;
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
|
|||||||
Generated
+679
-633
File diff suppressed because it is too large
Load Diff
+13
-13
@@ -88,10 +88,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.29.0",
|
"@babel/core": "^7.29.0",
|
||||||
"@babel/plugin-transform-runtime": "^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/preset-react": "^7.28.5",
|
||||||
"@babel/runtime": "^7.29.2",
|
"@babel/runtime": "^7.29.2",
|
||||||
"@codemirror/autocomplete": "^6.20.1",
|
"@codemirror/autocomplete": "^6.20.2",
|
||||||
"@codemirror/commands": "^6.10.3",
|
"@codemirror/commands": "^6.10.3",
|
||||||
"@codemirror/highlight": "^0.19.8",
|
"@codemirror/highlight": "^0.19.8",
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
"@codemirror/language-data": "^6.5.2",
|
"@codemirror/language-data": "^6.5.2",
|
||||||
"@codemirror/search": "^6.6.0",
|
"@codemirror/search": "^6.6.0",
|
||||||
"@codemirror/state": "^6.6.0",
|
"@codemirror/state": "^6.6.0",
|
||||||
"@codemirror/view": "^6.40.0",
|
"@codemirror/view": "^6.42.1",
|
||||||
"@dmsnell/diff-match-patch": "^1.1.0",
|
"@dmsnell/diff-match-patch": "^1.1.0",
|
||||||
"@googleapis/drive": "^20.1.0",
|
"@googleapis/drive": "^20.1.0",
|
||||||
"@lezer/highlight": "^1.2.3",
|
"@lezer/highlight": "^1.2.3",
|
||||||
@@ -117,9 +117,9 @@
|
|||||||
"dedent": "^1.7.1",
|
"dedent": "^1.7.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "3.0.0",
|
"express-static-gzip": "3.0.1",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"fs-extra": "^11.3.3",
|
"fs-extra": "^11.3.5",
|
||||||
"hash-wasm": "^4.12.0",
|
"hash-wasm": "^4.12.0",
|
||||||
"idb-keyval": "^6.2.2",
|
"idb-keyval": "^6.2.2",
|
||||||
"js-yaml": "^4.1.1",
|
"js-yaml": "^4.1.1",
|
||||||
@@ -138,32 +138,32 @@
|
|||||||
"marked-variables": "^1.0.5",
|
"marked-variables": "^1.0.5",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongoose": "^9.3.3",
|
"mongoose": "^9.6.2",
|
||||||
"nanoid": "5.1.7",
|
"nanoid": "5.1.11",
|
||||||
"nconf": "^0.13.0",
|
"nconf": "^0.13.0",
|
||||||
"node": "^25.9.0",
|
"node": "^25.9.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.6",
|
||||||
"react-frame-component": "^5.3.2",
|
"react-frame-component": "^5.3.2",
|
||||||
"react-router": "^7.14.0",
|
"react-router": "^7.15.0",
|
||||||
"sanitize-filename": "1.6.4",
|
"sanitize-filename": "1.6.4",
|
||||||
"superagent": "^10.2.1"
|
"superagent": "^10.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/stylelint-plugin": "^5.0.1",
|
"@stylistic/stylelint-plugin": "^5.0.1",
|
||||||
"babel-jest": "^30.3.0",
|
"babel-jest": "^30.4.1",
|
||||||
"babel-plugin-transform-import-meta": "^2.3.3",
|
"babel-plugin-transform-import-meta": "^2.3.3",
|
||||||
"eslint": "9.7",
|
"eslint": "9.7",
|
||||||
"eslint-plugin-jest": "^29.15.1",
|
"eslint-plugin-jest": "^29.15.1",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"jest": "^30.3.0",
|
"jest": "^30.4.2",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
"postcss-less": "^6.0.0",
|
"postcss-less": "^6.0.0",
|
||||||
"stylelint": "^17.6.0",
|
"stylelint": "^17.11.0",
|
||||||
"stylelint-config-recess-order": "^7.7.0",
|
"stylelint-config-recess-order": "^7.7.0",
|
||||||
"stylelint-config-recommended": "^18.0.0",
|
"stylelint-config-recommended": "^18.0.0",
|
||||||
"supertest": "^7.1.4",
|
"supertest": "^7.1.4",
|
||||||
|
|||||||
+1
-1
@@ -593,7 +593,7 @@ export default async function createApp(vite) {
|
|||||||
|
|
||||||
html = html.replace(
|
html = html.replace(
|
||||||
'<head>',
|
'<head>',
|
||||||
`<head>\n<script id="props" >window.__INITIAL_PROPS__ = ${JSON.stringify(props)}</script>\n${ogMetaTags}`
|
()=>{ return `<head>\n<script id="props" >window.__INITIAL_PROPS__ = ${JSON.stringify(props)}</script>\n${ogMetaTags}`; }
|
||||||
);
|
);
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
|
|||||||
+28
-2
@@ -160,9 +160,35 @@ const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{
|
|||||||
// Char-level diff
|
// Char-level diff
|
||||||
for (let i = 0; i < Math.min(clientText.length, serverText.length); i++) {
|
for (let i = 0; i < Math.min(clientText.length, serverText.length); i++) {
|
||||||
if(clientText[i] !== serverText[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(`Char mismatch at index ${i}:`);
|
||||||
console.log(` Client: '${clientText[i]}' (U+${clientText.charCodeAt(i).toString(16).toUpperCase()})`);
|
logContext(clientContext);
|
||||||
console.log(` Server: '${serverText[i]}' (U+${serverText.charCodeAt(i).toString(16).toUpperCase()})`);
|
logContext(serverContext);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user