Merge branch 'master' into hbfm

This commit is contained in:
Víctor Losada Hernández
2026-08-29 23:45:50 +02:00
committed by GitHub
9 changed files with 3997 additions and 2090 deletions
+5 -3
View File
@@ -43,9 +43,9 @@ const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
const themeCompartment = new Compartment(); const themeCompartment = new Compartment();
const highlightCompartment = new Compartment(); const highlightCompartment = new Compartment();
import { generalKeymap, markdownKeymap } from './extensions/customKeyMaps.js'; import { generalKeymap, markdownKeymap, cssKeymap, formatCSS } from './extensions/customKeyMaps.js';
import foldOnPages from './extensions/customFolding.js'; import foldOnPages from './extensions/customFolding.js';
import { customHighlightPlugin, customHighlightStyle } from './extensions/customHighlight.js'; import { customHighlightStyle , customHighlightPlugin } from './extensions/customHighlight.js';
import { legacyCustomHighlightStyle } from './extensions/legacyCustomHighlight.js'; import { legacyCustomHighlightStyle } from './extensions/legacyCustomHighlight.js';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
@@ -186,7 +186,7 @@ const CodeEditor = forwardRef(
//keyboard shortcut //keyboard shortcut
keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]), keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]),
generalKeymap, generalKeymap,
...(tab !== 'brewStyles' ? [markdownKeymap] : []), ...(tab === 'brewStyles' ? [cssKeymap] : [markdownKeymap]),
//multiple cursors and selections //multiple cursors and selections
drawSelection(), drawSelection(),
@@ -356,6 +356,8 @@ const CodeEditor = forwardRef(
}, 400); }, 400);
}, },
formatCode : ()=>formatCSS(viewRef.current),
undo : ()=>undo(viewRef.current), undo : ()=>undo(viewRef.current),
redo : ()=>redo(viewRef.current), redo : ()=>redo(viewRef.current),
@@ -15,6 +15,18 @@
} }
} }
@keyframes slideacross {
0% {
bottom: -200px;
left: -200px;
}
100% {
bottom:100%;
left:100%;
}
}
:where(.codeEditor) { :where(.codeEditor) {
width : 100%; width : 100%;
height : calc(100% - 25px); height : calc(100% - 25px);
@@ -23,6 +35,27 @@
.cm-editor { .cm-editor {
height : 100%; height : 100%;
outline : none !important; outline : none !important;
&.cm-flash {
position:relative;
&::after {
position:absolute;
content:'';
bottom:-200px;
left:-200px;
translate:-50%;
width:200%;
height:100px;
background:linear-gradient(0deg, #89eafc00 0px, #89ebfc8e 50px, #89eafc00 100px, transparent);
display:block;
rotate:30deg;
@media screen and (prefers-reduced-motion: no-preference) {
animation: .5s linear 1 slideacross ;
}
}
}
} }
&.brewSnippets .cm-snippetLine, &.brewSnippets .cm-snippetLine,
@@ -251,13 +251,13 @@ function tokenizeCustomMarkdown(text) {
); );
if(match) endCh = match.index + match[0].length; if(match) endCh = match.index + match[0].length;
const closingMatch = lineText.match(/ *(}})/d); const closingMatch = lineText.match(/ *(}})/d);
if(closingMatch) { if(closingMatch) {
tokens.push({ line: lineNumber, from: closingMatch.indices[1][0], to: closingMatch.indices[1][1], type: customTags.block }); tokens.push({ line: lineNumber, from: closingMatch.indices[1][0], to: closingMatch.indices[1][1], type: customTags.block });
} else { } else {
tokens.push({ line: lineNumber, type: customTags.block }); tokens.push({ line: lineNumber, type: customTags.block });
} }
} }
}); });
@@ -350,10 +350,9 @@ class ImageWidget extends WidgetType {
toDOM() { toDOM() {
const img = document.createElement('img'); const img = document.createElement('img');
img.loading = "lazy"; img.loading = 'lazy';
img.className = 'cm-preview'; img.className = 'cm-preview';
img.src = this.url; img.src = this.url;
img.onerror = ()=>{ img.onerror = ()=>{
img.src = 'client/icons/broken-image.jpg'; img.src = 'client/icons/broken-image.jpg';
@@ -403,8 +402,8 @@ export function customHighlightPlugin(renderer, tab) {
const url = getUrl(node, view.state.doc); const url = getUrl(node, view.state.doc);
const widgetPosition = node.node.lastChild.from; const widgetPosition = node.node.lastChild.from;
//this is not exactly standard, but should hold, //this is not exactly standard, but should hold,
//and is the shortest way i could find of positioning //and is the shortest way i could find of positioning
//the image inside the cm-image node //the image inside the cm-image node
if(!url) return; if(!url) return;
@@ -3,7 +3,60 @@ import { keymap } from '@codemirror/view';
import { undo, redo, indentMore, indentLess, deleteLine } from '@codemirror/commands'; import { undo, redo, indentMore, indentLess, deleteLine } from '@codemirror/commands';
import { EditorSelection } from '@codemirror/state'; import { EditorSelection } from '@codemirror/state';
import { Prec } from '@codemirror/state'; import { Prec } from '@codemirror/state';
import * as prettier from 'prettier/standalone';
import * as postcssPlugin from 'prettier/plugins/postcss';
export async function formatCSS(view) {
try {
const { from, to, empty } = view.state.selection.main;
const fullDoc = view.state.doc.toString();
const selection = view.state.doc.sliceString(from, to);
const code = empty ? fullDoc : selection;
let formatted = await prettier.format(code, {
parser: 'css',
plugins: [postcssPlugin],
// formatting options
tabWidth: 2,
useTabs: false,
printWidth: 100,
singleQuote: false,
trailingComma: 'all',
bracketSpacing: true,
endOfLine: 'lf'
});
//format manually single declaration rules to span one line.
//Prettier can't do it by default, this is crude but it works
formatted = formatted.replace(
/([^{]+)\{\s*\n\s*([^;\n]+:[^;\n]+;)\s*\n\s*\}(\s*)/g,
(_, selector, decl, whitespace) =>
`${selector} { ${decl.trim()} }${whitespace}`
);
if(formatted === code) return true;
const dom = view.dom;
dom.classList.add('cm-flash');
setTimeout(()=>{
dom.classList.remove('cm-flash');
view.dispatch({
changes : {
from : empty ? 0 : from,
to : empty ? view.state.doc.length : to,
insert : formatted
}
});
}, 500);
} catch (err) {
console.error('Error formatting css: ', err);
}
return true;
}
const insertTab = (view)=>{ const insertTab = (view)=>{
// If any selection spans multiple lines, delegates to CodeMirror's indentMore // If any selection spans multiple lines, delegates to CodeMirror's indentMore
// Otherwise inserts two spaces at each cursor/selection // Otherwise inserts two spaces at each cursor/selection
@@ -188,6 +241,11 @@ export const generalKeymap = Prec.high(keymap.of([
{ key: 'Mod-d', run: deleteLine }, //annoyingly overrides "selectNextOccurrence" because users asked { key: 'Mod-d', run: deleteLine }, //annoyingly overrides "selectNextOccurrence" because users asked
])); ]));
export const cssKeymap = Prec.highest(keymap.of([
{ key: 'Mod-Shift-f', run: formatCSS },
{ key: 'Alt-Shift-f', run: formatCSS },
]));
export const markdownKeymap = Prec.highest(keymap.of([ export const markdownKeymap = Prec.highest(keymap.of([
{ key: 'Mod-b', run: wrapSelection('**', '**') }, // makeBold { key: 'Mod-b', run: wrapSelection('**', '**') }, // makeBold
+5
View File
@@ -111,6 +111,10 @@ const Editor = forwardRef(
useEffect(()=>{ if(liveScroll) brewJump(currentEditorViewPageNum, false); }, [currentEditorViewPageNum, liveScroll]); useEffect(()=>{ if(liveScroll) brewJump(currentEditorViewPageNum, false); }, [currentEditorViewPageNum, liveScroll]);
useEffect(()=>{ if(liveScroll) brewJump(currentEditorCursorPageNum, false); }, [currentEditorCursorPageNum, liveScroll]); useEffect(()=>{ if(liveScroll) brewJump(currentEditorCursorPageNum, false); }, [currentEditorCursorPageNum, liveScroll]);
const handleFormatCode = () => {
codeEditor.current?.formatCode();
};
const handleControlKeys = (e)=>{ const handleControlKeys = (e)=>{
if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return; if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return;
const LEFTARROW_KEY = 37; const LEFTARROW_KEY = 37;
@@ -319,6 +323,7 @@ const Editor = forwardRef(
redo={redo} redo={redo}
foldCode={foldCode} foldCode={foldCode}
unfoldCode={unfoldCode} unfoldCode={unfoldCode}
formatCode={isStyle() ? handleFormatCode : null}
historySize={historySize()} historySize={historySize()}
currentEditorTheme={currentEditorTheme} currentEditorTheme={currentEditorTheme}
updateEditorTheme={updateEditorTheme} updateEditorTheme={updateEditorTheme}
@@ -65,6 +65,7 @@ const Snippetbar = createReactClass({
historySize : ()=>{}, historySize : ()=>{},
foldCode : ()=>{}, foldCode : ()=>{},
unfoldCode : ()=>{}, unfoldCode : ()=>{},
formatCode : ()=>{},
updateEditorTheme : ()=>{}, updateEditorTheme : ()=>{},
cursorPos : {}, cursorPos : {},
themeBundle : [], themeBundle : [],
@@ -269,6 +270,10 @@ const Snippetbar = createReactClass({
onClick={this.props.unfoldCode} > onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' /> <i className='fas fa-expand-alt' />
</div> </div>
<div className={`editorTool formatCode ${this.props.formatCode ? 'active' : ''}`}
onClick={this.props.formatCode} >
<i className='fas fa-wand-magic-sparkles' />
</div>
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`} <div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} > onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' /> <i className='fas fa-palette' />
@@ -21,7 +21,7 @@
.editors { .editors {
display : flex; display : flex;
justify-content : flex-end; justify-content : flex-end;
min-width : 250px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied min-width : 275px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
font-size: .85rem; font-size: .85rem;
&:only-child {min-width : unset; margin-left : auto;} &:only-child {min-width : unset; margin-left : auto;}
@@ -75,6 +75,11 @@
color : grey; color : grey;
&.active { color : inherit; } &.active { color : inherit; }
} }
&.formatCode {
.tooltipLeft('Clean your Code');
color : grey;
&.active { color : inherit; }
}
&.history { &.history {
.tooltipLeft('History'); .tooltipLeft('History');
position : relative; position : relative;
@@ -208,8 +213,8 @@
} }
} }
} }
@container editor (width < 816px) { @container editor (width < 841px) {
.snippetBar { .snippetBar {
.editors { .editors {
flex : 1; flex : 1;
justify-content : space-between; justify-content : space-between;
+3877 -2078
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -145,6 +145,7 @@
"nanoid": "6.0.1", "nanoid": "6.0.1",
"nconf": "^0.13.0", "nconf": "^0.13.0",
"node": "^26.7.0", "node": "^26.7.0",
"prettier": "^3.8.1",
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.7", "react-dom": "^19.2.7",
"react-frame-component": "^5.3.2", "react-frame-component": "^5.3.2",