mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-09-20 16:42:58 +00:00
Merge branch 'master' into hbfm
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Generated
+3877
-2078
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user