mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-09-20 16:42:58 +00:00
resolve merge conflicts
This commit is contained in:
@@ -43,9 +43,9 @@ const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
|
||||
const themeCompartment = 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 { customHighlightPlugin, customHighlightStyle } from './extensions/customHighlight.js';
|
||||
import { customHighlightStyle , customHighlightPlugin } from './extensions/customHighlight.js';
|
||||
import { legacyCustomHighlightStyle } from './extensions/legacyCustomHighlight.js';
|
||||
|
||||
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||
@@ -186,7 +186,7 @@ const CodeEditor = forwardRef(
|
||||
//keyboard shortcut
|
||||
keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]),
|
||||
generalKeymap,
|
||||
...(tab !== 'brewStyles' ? [markdownKeymap] : []),
|
||||
...(tab === 'brewStyles' ? [cssKeymap] : [markdownKeymap]),
|
||||
|
||||
//multiple cursors and selections
|
||||
drawSelection(),
|
||||
@@ -356,6 +356,8 @@ const CodeEditor = forwardRef(
|
||||
}, 400);
|
||||
},
|
||||
|
||||
formatCode : ()=>formatCSS(viewRef.current),
|
||||
|
||||
undo : ()=>undo(viewRef.current),
|
||||
redo : ()=>redo(viewRef.current),
|
||||
|
||||
|
||||
@@ -15,6 +15,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideacross {
|
||||
0% {
|
||||
bottom: -200px;
|
||||
left: -200px;
|
||||
}
|
||||
|
||||
100% {
|
||||
bottom:100%;
|
||||
left:100%;
|
||||
}
|
||||
}
|
||||
|
||||
:where(.codeEditor) {
|
||||
width : 100%;
|
||||
height : calc(100% - 25px);
|
||||
@@ -23,6 +35,27 @@
|
||||
.cm-editor {
|
||||
height : 100%;
|
||||
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,
|
||||
|
||||
@@ -251,13 +251,13 @@ function tokenizeCustomMarkdown(text) {
|
||||
);
|
||||
if(match) endCh = match.index + match[0].length;
|
||||
const closingMatch = lineText.match(/ *(}})/d);
|
||||
|
||||
|
||||
if(closingMatch) {
|
||||
tokens.push({ line: lineNumber, from: closingMatch.indices[1][0], to: closingMatch.indices[1][1], type: customTags.block });
|
||||
} else {
|
||||
tokens.push({ line: lineNumber, type: customTags.block });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -350,10 +350,9 @@ class ImageWidget extends WidgetType {
|
||||
|
||||
toDOM() {
|
||||
const img = document.createElement('img');
|
||||
img.loading = "lazy";
|
||||
img.loading = 'lazy';
|
||||
img.className = 'cm-preview';
|
||||
img.src = this.url;
|
||||
|
||||
|
||||
img.onerror = ()=>{
|
||||
img.src = 'client/icons/broken-image.jpg';
|
||||
@@ -403,8 +402,8 @@ export function customHighlightPlugin(renderer, tab) {
|
||||
const url = getUrl(node, view.state.doc);
|
||||
|
||||
const widgetPosition = node.node.lastChild.from;
|
||||
//this is not exactly standard, but should hold,
|
||||
//and is the shortest way i could find of positioning
|
||||
//this is not exactly standard, but should hold,
|
||||
//and is the shortest way i could find of positioning
|
||||
//the image inside the cm-image node
|
||||
|
||||
if(!url) return;
|
||||
|
||||
@@ -3,7 +3,60 @@ import { keymap } from '@codemirror/view';
|
||||
import { undo, redo, indentMore, indentLess, deleteLine } from '@codemirror/commands';
|
||||
import { EditorSelection } 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)=>{
|
||||
// If any selection spans multiple lines, delegates to CodeMirror's indentMore
|
||||
// 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
|
||||
]));
|
||||
|
||||
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([
|
||||
|
||||
{ key: 'Mod-b', run: wrapSelection('**', '**') }, // makeBold
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, { useState, useRef, useMemo, useEffect } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import MarkdownLegacy from '@shared/markdownLegacy.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import { hbfm } from 'hbmarkedwrapper';
|
||||
import ErrorBar from './errorBar/errorBar.jsx';
|
||||
import ToolBar from './toolBar/toolBar.jsx';
|
||||
|
||||
@@ -203,7 +203,7 @@ const BrewRenderer = (props)=>{
|
||||
return <BrewPage className='page phb' index={index} key={index} contents={html} style={styles} onVisibilityChange={handlePageVisibilityChange} />;
|
||||
} else {
|
||||
if(pageText.startsWith('\\page')) {
|
||||
const firstLineTokens = Markdown.marked.lexer(pageText.split('\n', 1)[0])[0].tokens;
|
||||
const firstLineTokens = hbfm.marked.lexer(pageText.split('\n', 1)[0])[0].tokens;
|
||||
const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
|
||||
if(injectedTags) {
|
||||
styles = { ...styles, ...injectedTags.styles };
|
||||
@@ -231,7 +231,7 @@ const BrewRenderer = (props)=>{
|
||||
// DO NOT REMOVE!!! REQUIRED FOR BACKWARDS COMPATIBILITY WITH NON-UPGRADABLE VERSIONS OF CHROME.
|
||||
pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
||||
|
||||
const html = Markdown.render(pageText, index);
|
||||
const html = hbfm.render(pageText, index);
|
||||
|
||||
return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} onVisibilityChange={handlePageVisibilityChange} />;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './notificationPopup.less';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import request from '../../utils/request-middleware.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import { hbfm } from 'hbmarkedwrapper';
|
||||
|
||||
import Dialog from '@components/dialog.jsx';
|
||||
|
||||
@@ -44,7 +44,7 @@ const NotificationPopup = ()=>{
|
||||
return notifications.map((notification)=>(
|
||||
<li key={notification.dismissKey} >
|
||||
<em>{notification.title}</em><br />
|
||||
<p dangerouslySetInnerHTML={{ __html: Markdown.render(notification.text) }}></p>
|
||||
<p dangerouslySetInnerHTML={{ __html: hbfm.render(notification.text) }}></p>
|
||||
</li>
|
||||
));
|
||||
};
|
||||
|
||||
+269
-278
@@ -1,8 +1,6 @@
|
||||
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
|
||||
import './editor.less';
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import _ from 'lodash';
|
||||
import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import dedent from 'dedent';
|
||||
|
||||
import CodeEditor from '@components/codeEditor/codeEditor.jsx';
|
||||
@@ -18,12 +16,8 @@ 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);
|
||||
|
||||
.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\ .*$/;
|
||||
@@ -45,306 +39,303 @@ const DEFAULT_SNIPPET_TEXT = dedent`
|
||||
let isJumping = false;
|
||||
let jumpSource = null;
|
||||
|
||||
const Editor = createReactClass({
|
||||
displayName : 'Editor',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
text : '',
|
||||
style : ''
|
||||
},
|
||||
const Editor = forwardRef(
|
||||
(
|
||||
{
|
||||
brew = {},
|
||||
|
||||
onBrewChange : ()=>{},
|
||||
reportError : ()=>{},
|
||||
onBrewChange = ()=>{},
|
||||
reportError = ()=>{},
|
||||
|
||||
onCursorPageChange : ()=>{},
|
||||
onViewPageChange : ()=>{},
|
||||
onCursorPageChange = ()=>{},
|
||||
onViewPageChange = ()=>{},
|
||||
|
||||
editorTheme : 'default',
|
||||
renderer : 'legacy',
|
||||
editorTheme = 'default',
|
||||
renderer = 'legacy',
|
||||
|
||||
currentEditorCursorPageNum : 1,
|
||||
currentEditorViewPageNum : 1,
|
||||
currentBrewRendererPageNum : 1,
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
editorTheme : this.props.editorTheme,
|
||||
view : 'text', //'text', 'style', 'meta', 'snippet'
|
||||
snippetBarHeight : 26,
|
||||
};
|
||||
},
|
||||
moveBrew,
|
||||
moveSource,
|
||||
liveScroll,
|
||||
|
||||
editor : React.createRef(null),
|
||||
codeEditor : React.createRef(null),
|
||||
setMoveArrows,
|
||||
updateBrew,
|
||||
showEditButtons,
|
||||
themeBundle,
|
||||
userThemes,
|
||||
|
||||
isText : function() {return this.state.view == 'text';},
|
||||
isStyle : function() {return this.state.view == 'style';},
|
||||
isMeta : function() {return this.state.view == 'meta';},
|
||||
isSnip : function() {return this.state.view == 'snippet';},
|
||||
currentEditorCursorPageNum = 1,
|
||||
currentEditorViewPageNum = 1,
|
||||
currentBrewRendererPageNum = 1,
|
||||
},
|
||||
ref,
|
||||
)=>{
|
||||
const [currentEditorTheme, setEditorTheme] = useState(editorTheme);
|
||||
const [view, setView] = useState('text'); // 'text', 'style', 'meta', 'snippet'
|
||||
const [snippetBarHeight, setSnippetBarHeight] = useState(26);
|
||||
|
||||
componentDidMount : function() {
|
||||
const editor = useRef(null);
|
||||
const codeEditor = useRef(null);
|
||||
const throttleBrewMove = useRef(null);
|
||||
|
||||
const brewRenderer = document.getElementById('BrewRenderer');
|
||||
brewRenderer.onload = ()=>brewRenderer.contentDocument?.addEventListener('keydown', this.handleControlKeys);
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
const isText = ()=>isView('text');
|
||||
const isStyle = ()=>isView('style');
|
||||
const isMeta = ()=>isView('meta');
|
||||
const isSnip = ()=>isView('snippet');
|
||||
|
||||
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
||||
if(editorTheme && EditorThemes.includes(editorTheme)) {
|
||||
this.setState({ editorTheme });
|
||||
} else {
|
||||
this.setState({ editorTheme: 'default' });
|
||||
}
|
||||
const snippetBar = document.querySelector('.editor > .snippetBar');
|
||||
if(!snippetBar) return;
|
||||
const isView = (name)=>view === name;
|
||||
|
||||
this.resizeObserver = new ResizeObserver((entries)=>{
|
||||
const height = document.querySelector('.editor > .snippetBar').offsetHeight;
|
||||
this.setState({ snippetBarHeight: height });
|
||||
});
|
||||
useEffect(()=>{
|
||||
const brewRenderer = document.getElementById('BrewRenderer');
|
||||
brewRenderer.onload = ()=>brewRenderer.contentDocument?.addEventListener('keydown', handleControlKeys);
|
||||
document.addEventListener('keydown', handleControlKeys);
|
||||
|
||||
this.resizeObserver.observe(snippetBar);
|
||||
},
|
||||
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
||||
if(editorTheme && EditorThemes.includes(editorTheme)) setEditorTheme(editorTheme); else setEditorTheme('default');
|
||||
const snippetBar = document.querySelector('.editor > .snippetBar');
|
||||
if(!snippetBar) return;
|
||||
|
||||
componentDidUpdate : function(prevProps, prevState, snapshot) {
|
||||
const resizeObserver = new ResizeObserver((entries)=>{
|
||||
const height = document.querySelector('.editor > .snippetBar').offsetHeight;
|
||||
setSnippetBarHeight(height);
|
||||
});
|
||||
resizeObserver.observe(snippetBar);
|
||||
|
||||
if(prevProps.moveBrew !== this.props.moveBrew)
|
||||
this.brewJump();
|
||||
|
||||
if(prevProps.moveSource !== this.props.moveSource)
|
||||
this.sourceJump();
|
||||
|
||||
if(this.props.liveScroll) {
|
||||
if(prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) {
|
||||
this.sourceJump(this.props.currentBrewRendererPageNum, false);
|
||||
} else if(prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) {
|
||||
this.brewJump(this.props.currentEditorViewPageNum, false);
|
||||
} else if(prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) {
|
||||
this.brewJump(this.props.currentEditorCursorPageNum, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
if(this.resizeObserver) this.resizeObserver.disconnect();
|
||||
},
|
||||
|
||||
handleControlKeys : function(e){
|
||||
if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return;
|
||||
const LEFTARROW_KEY = 37;
|
||||
const RIGHTARROW_KEY = 39;
|
||||
if(e.keyCode == RIGHTARROW_KEY) this.brewJump();
|
||||
if(e.keyCode == LEFTARROW_KEY) this.sourceJump();
|
||||
if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
updateCurrentCursorPage : function(pageNumber) {
|
||||
this.props.onCursorPageChange(pageNumber);
|
||||
},
|
||||
|
||||
updateCurrentViewPage : function(pageNumber) {
|
||||
this.props.onViewPageChange(pageNumber);
|
||||
},
|
||||
|
||||
handleInject : function(injectText){
|
||||
this.codeEditor.current?.injectText(injectText);
|
||||
},
|
||||
|
||||
handleViewChange : function(newView){
|
||||
this.props.setMoveArrows(newView === 'text');
|
||||
|
||||
this.setState({
|
||||
view : newView
|
||||
}, ()=>{
|
||||
this.codeEditor.current?.focus();
|
||||
});
|
||||
},
|
||||
|
||||
brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){
|
||||
if(!window || !this.isText() || isJumping || jumpSource === 'source')
|
||||
return;
|
||||
|
||||
// Get current brewRenderer scroll position and calculate target position
|
||||
const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0];
|
||||
const currentPos = brewRenderer.scrollTop;
|
||||
const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top;
|
||||
|
||||
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;
|
||||
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);
|
||||
|
||||
if(smooth) {
|
||||
const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling
|
||||
const bounceDelay = 100;
|
||||
const scrollDelay = 500;
|
||||
|
||||
if(!this.throttleBrewMove) {
|
||||
this.throttleBrewMove = _.throttle((currentPos, bouncePos, targetPos)=>{
|
||||
brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' });
|
||||
setTimeout(()=>{
|
||||
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' });
|
||||
}, bounceDelay);
|
||||
}, scrollDelay, { leading: true, trailing: false });
|
||||
return ()=>{
|
||||
if(resizeObserver) resizeObserver.disconnect();
|
||||
};
|
||||
this.throttleBrewMove(currentPos, bouncePos, targetPos);
|
||||
} else {
|
||||
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'instant', block: 'start' });
|
||||
}
|
||||
},
|
||||
}, []);
|
||||
|
||||
sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){
|
||||
if(!this.isText() || isJumping || jumpSource === 'brew')
|
||||
return;
|
||||
useEffect(()=>{ if(moveBrew) brewJump(); }, [moveBrew]);
|
||||
useEffect(()=>{ if(moveSource) sourceJump(); }, [moveSource]);
|
||||
useEffect(()=>{ if(liveScroll) sourceJump(currentBrewRendererPageNum, false); }, [currentBrewRendererPageNum, liveScroll]);
|
||||
useEffect(()=>{ if(liveScroll) brewJump(currentEditorViewPageNum, false); }, [currentEditorViewPageNum, liveScroll]);
|
||||
useEffect(()=>{ if(liveScroll) brewJump(currentEditorCursorPageNum, false); }, [currentEditorCursorPageNum, liveScroll]);
|
||||
|
||||
const editor = this.codeEditor.current;
|
||||
if(!editor) return;
|
||||
jumpSource = 'source';
|
||||
const handleFormatCode = () => {
|
||||
codeEditor.current?.formatCode();
|
||||
};
|
||||
|
||||
editor.scrollToPage(targetPage);
|
||||
setTimeout(()=>{
|
||||
jumpSource = null;
|
||||
}, 200);
|
||||
},
|
||||
const handleControlKeys = (e)=>{
|
||||
if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return;
|
||||
const LEFTARROW_KEY = 37;
|
||||
const RIGHTARROW_KEY = 39;
|
||||
if(e.keyCode == RIGHTARROW_KEY) brewJump();
|
||||
if(e.keyCode == LEFTARROW_KEY) sourceJump();
|
||||
if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
//Called when there are changes to the editor's dimensions
|
||||
update : function(){},
|
||||
const updateCurrentCursorPage = (pageNumber)=>{
|
||||
onCursorPageChange(pageNumber);
|
||||
};
|
||||
|
||||
updateEditorTheme : function(newTheme){
|
||||
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
|
||||
this.setState({
|
||||
editorTheme : newTheme
|
||||
});
|
||||
},
|
||||
const updateCurrentViewPage = (pageNumber)=>{
|
||||
onViewPageChange(pageNumber);
|
||||
};
|
||||
|
||||
//Called by CodeEditor after document switch, so Snippetbar can refresh UndoHistory
|
||||
rerenderParent : function (){
|
||||
this.forceUpdate();
|
||||
},
|
||||
const handleInject = (injectText)=>{
|
||||
codeEditor.current?.injectText(injectText);
|
||||
};
|
||||
|
||||
renderEditor : function(){
|
||||
if(this.isText()){
|
||||
return <>
|
||||
<CodeEditor key='codeEditor'
|
||||
ref={this.codeEditor}
|
||||
language='gfm'
|
||||
tab='brewText'
|
||||
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}
|
||||
renderer={this.props.brew.renderer}
|
||||
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}/>
|
||||
</>;
|
||||
}
|
||||
if(this.isStyle()){
|
||||
return <>
|
||||
<CodeEditor key='codeEditor'
|
||||
ref={this.codeEditor}
|
||||
language='css'
|
||||
tab='brewStyles'
|
||||
view={this.state.view}
|
||||
value={this.props.brew.style ?? DEFAULT_STYLE_TEXT}
|
||||
onChange={this.props.onBrewChange('style')}
|
||||
editorTheme={this.state.editorTheme}
|
||||
renderer={this.props.brew.renderer}
|
||||
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}/>
|
||||
</>;
|
||||
}
|
||||
if(this.isMeta()){
|
||||
return <>
|
||||
<CodeEditor key='codeEditor'
|
||||
view={this.state.view}
|
||||
style={{ display: 'none' }}/>
|
||||
<MetadataEditor
|
||||
metadata={this.props.brew}
|
||||
themeBundle={this.props.themeBundle}
|
||||
onChange={this.props.onBrewChange('metadata')}
|
||||
reportError={this.props.reportError}
|
||||
userThemes={this.props.userThemes}/>
|
||||
</>;
|
||||
}
|
||||
if(this.isSnip()){
|
||||
if(!this.props.brew.snippets) { this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; }
|
||||
return <>
|
||||
<CodeEditor key='codeEditor'
|
||||
ref={this.codeEditor}
|
||||
language='gfm'
|
||||
tab='brewSnippets'
|
||||
view={this.state.view}
|
||||
value={this.props.brew.snippets}
|
||||
onChange={this.props.onBrewChange('snippets')}
|
||||
enableFolding={true}
|
||||
editorTheme={this.state.editorTheme}
|
||||
renderer={this.props.brew.renderer}
|
||||
rerenderParent={this.rerenderParent}
|
||||
style={{ height: `calc(100% - 25px)` }}/>
|
||||
</>;
|
||||
}
|
||||
},
|
||||
const handleViewChange = (newView)=>{
|
||||
setMoveArrows(newView === 'text');
|
||||
setView(newView);
|
||||
};
|
||||
useEffect(()=>{
|
||||
codeEditor.current?.focus();
|
||||
}, [view]);
|
||||
|
||||
redo : function(){
|
||||
return this.codeEditor.current?.redo();
|
||||
},
|
||||
const brewJump = (targetPage = currentEditorCursorPageNum, smooth = true)=>{
|
||||
if(!window || !isText() || isJumping || jumpSource === 'source') return;
|
||||
|
||||
historySize : function(){
|
||||
return this.codeEditor.current?.historySize();
|
||||
},
|
||||
const brewRenderer =
|
||||
window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0];
|
||||
|
||||
undo : function(){
|
||||
return this.codeEditor.current?.undo();
|
||||
},
|
||||
const currentPos = brewRenderer.scrollTop;
|
||||
|
||||
foldCode : function() {
|
||||
return this.codeEditor.current?.foldAll();
|
||||
},
|
||||
const targetPos = window.frames['BrewRenderer'].contentDocument
|
||||
.getElementById(`p${targetPage}`)
|
||||
.getBoundingClientRect().top;
|
||||
|
||||
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;
|
||||
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);
|
||||
|
||||
if(smooth) {
|
||||
const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling
|
||||
const now = Date.now();
|
||||
|
||||
if(now - throttleBrewMove.current >= 500) {
|
||||
throttleBrewMove.current = now;
|
||||
|
||||
brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' });
|
||||
|
||||
setTimeout(()=>{
|
||||
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' });
|
||||
}, 100);
|
||||
}
|
||||
} else {
|
||||
brewRenderer.scrollTo({ top : currentPos + targetPos, behavior : 'instant', block : 'start',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const sourceJump = (targetPage = currentBrewRendererPageNum, smooth = true)=>{
|
||||
if(!isText() || isJumping || jumpSource === 'brew') return;
|
||||
|
||||
if(!codeEditor.current) return;
|
||||
jumpSource = 'source';
|
||||
|
||||
codeEditor.current.scrollToPage(targetPage);
|
||||
setTimeout(()=>{
|
||||
jumpSource = null;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const updateEditorTheme = (newTheme)=>{
|
||||
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
|
||||
setEditorTheme(newTheme);
|
||||
};
|
||||
|
||||
const renderEditor = ()=>{
|
||||
if(isText()) {
|
||||
return (
|
||||
<>
|
||||
<CodeEditor
|
||||
key='codeEditor'
|
||||
ref={codeEditor}
|
||||
language='gfm'
|
||||
tab='brewText'
|
||||
view={view}
|
||||
value={brew.text}
|
||||
onChange={onBrewChange('text')}
|
||||
onCursorChange={(page)=>updateCurrentCursorPage(page)}
|
||||
onViewChange={(page)=>updateCurrentViewPage(page)}
|
||||
editorTheme={currentEditorTheme}
|
||||
renderer={brew.renderer}
|
||||
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if(isStyle()) {
|
||||
return (
|
||||
<>
|
||||
<CodeEditor
|
||||
key='codeEditor'
|
||||
ref={codeEditor}
|
||||
language='css'
|
||||
tab='brewStyles'
|
||||
view={view}
|
||||
value={brew.style ?? DEFAULT_STYLE_TEXT}
|
||||
onChange={onBrewChange('style')}
|
||||
editorTheme={currentEditorTheme}
|
||||
renderer={brew.renderer}
|
||||
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if(isSnip()) {
|
||||
if(!brew.snippets) {
|
||||
brew.snippets = DEFAULT_SNIPPET_TEXT;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<CodeEditor
|
||||
key='codeEditor'
|
||||
ref={codeEditor}
|
||||
language='gfm'
|
||||
tab='brewSnippets'
|
||||
view={view}
|
||||
value={brew.snippets}
|
||||
onChange={onBrewChange('snippets')}
|
||||
enableFolding={true}
|
||||
editorTheme={currentEditorTheme}
|
||||
renderer={brew.renderer}
|
||||
style={{ height: `calc(100% - 25px)` }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if(isMeta()) {
|
||||
return (
|
||||
<>
|
||||
<CodeEditor key='codeEditor' view={view} style={{ display: 'none' }} />
|
||||
<MetadataEditor
|
||||
metadata={brew}
|
||||
themeBundle={themeBundle}
|
||||
onChange={onBrewChange('metadata')}
|
||||
reportError={reportError}
|
||||
userThemes={userThemes}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const redo = ()=>codeEditor.current?.redo();
|
||||
const historySize = ()=>codeEditor.current?.historySize();
|
||||
const undo = ()=>codeEditor.current?.undo();
|
||||
const foldCode = ()=>codeEditor.current?.foldAll();
|
||||
const unfoldCode = ()=>codeEditor.current?.unfoldAll();
|
||||
|
||||
//Called when there are changes to the editor's dimensions
|
||||
const update = ()=>{};
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
update,
|
||||
undo,
|
||||
redo,
|
||||
foldCode,
|
||||
unfoldCode,
|
||||
historySize,
|
||||
}));
|
||||
|
||||
unfoldCode : function() {
|
||||
return this.codeEditor.current?.unfoldAll();
|
||||
},
|
||||
render : function(){
|
||||
return (
|
||||
<div className='editor' ref={this.editor}>
|
||||
<div className='editor' ref={editor}>
|
||||
<SnippetBar
|
||||
brew={this.props.brew}
|
||||
view={this.state.view}
|
||||
onViewChange={this.handleViewChange}
|
||||
onInject={this.handleInject}
|
||||
showEditButtons={this.props.showEditButtons}
|
||||
renderer={this.props.renderer}
|
||||
theme={this.props.brew.theme}
|
||||
undo={this.undo}
|
||||
redo={this.redo}
|
||||
foldCode={this.foldCode}
|
||||
unfoldCode={this.unfoldCode}
|
||||
historySize={this.historySize()}
|
||||
currentEditorTheme={this.state.editorTheme}
|
||||
updateEditorTheme={this.updateEditorTheme}
|
||||
themeBundle={this.props.themeBundle}
|
||||
cursorPos={this.codeEditor.current?.getCursorPosition() || {}}
|
||||
updateBrew={this.props.updateBrew}
|
||||
brew={brew}
|
||||
view={view}
|
||||
onViewChange={handleViewChange}
|
||||
onInject={handleInject}
|
||||
showEditButtons={showEditButtons}
|
||||
renderer={renderer}
|
||||
theme={brew.theme}
|
||||
undo={undo}
|
||||
redo={redo}
|
||||
foldCode={foldCode}
|
||||
unfoldCode={unfoldCode}
|
||||
formatCode={isStyle() ? handleFormatCode : null}
|
||||
historySize={historySize()}
|
||||
currentEditorTheme={currentEditorTheme}
|
||||
updateEditorTheme={updateEditorTheme}
|
||||
themeBundle={themeBundle}
|
||||
cursorPos={codeEditor.current?.getCursorPosition() || {}}
|
||||
updateBrew={updateBrew}
|
||||
/>
|
||||
|
||||
{this.renderEditor()}
|
||||
{renderEditor()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
export default Editor;
|
||||
export default Editor;
|
||||
|
||||
@@ -157,11 +157,11 @@ const MetadataEditor = createReactClass({
|
||||
renderPublish : function(){
|
||||
if(this.props.metadata.published){
|
||||
return <button className='unpublish' onClick={()=>this.handlePublish(false)}>
|
||||
<i className='fas fa-ban' /> unpublish
|
||||
<i className='fas fa-ban' aria-hidden='true' /> unpublish
|
||||
</button>;
|
||||
} else {
|
||||
return <button className='publish' onClick={()=>this.handlePublish(true)}>
|
||||
<i className='fas fa-globe' /> publish
|
||||
<i className='fas fa-globe' aria-hidden='true' /> publish
|
||||
</button>;
|
||||
}
|
||||
},
|
||||
@@ -359,26 +359,28 @@ const MetadataEditor = createReactClass({
|
||||
<h1>Properties Editor</h1>
|
||||
|
||||
<div className='field title'>
|
||||
<label>title</label>
|
||||
<input type='text' className='value'
|
||||
<label for='title_field'>title</label>
|
||||
<input type='text' id='title_field' className='value'
|
||||
defaultValue={this.props.metadata.title}
|
||||
onChange={(e)=>this.handleFieldChange('title', e)} />
|
||||
</div>
|
||||
<div className='field-group'>
|
||||
<div className='field-column'>
|
||||
<div className='field description'>
|
||||
<label>description</label>
|
||||
<textarea defaultValue={this.props.metadata.description} className='value'
|
||||
<label for='description_field'>description</label>
|
||||
<textarea id='description_field' defaultValue={this.props.metadata.description} className='value'
|
||||
onChange={(e)=>this.handleFieldChange('description', e)} />
|
||||
</div>
|
||||
<div className='field thumbnail'>
|
||||
<label>thumbnail</label>
|
||||
<label for='thumbnail_field'>thumbnail</label>
|
||||
<input type='text'
|
||||
id='thumbnail_field'
|
||||
defaultValue={this.props.metadata.thumbnail}
|
||||
placeholder='https://my.thumbnail.url'
|
||||
className='value'
|
||||
onChange={(e)=>this.handleFieldChange('thumbnail', e)} />
|
||||
<button className='display' onClick={this.toggleThumbnailDisplay}>
|
||||
<button className='display' onClick={this.toggleThumbnailDisplay}
|
||||
aria-label={`${this.state.showThumbnail ? 'hide thumbnail' : 'show thumbnail'}`}>
|
||||
<i className={`fas fa-caret-${this.state.showThumbnail ? 'right' : 'left'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
}
|
||||
a {
|
||||
color:black;
|
||||
text-decoration:unset;
|
||||
text-underline-offset:0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ const Snippetbar = createReactClass({
|
||||
historySize : ()=>{},
|
||||
foldCode : ()=>{},
|
||||
unfoldCode : ()=>{},
|
||||
formatCode : ()=>{},
|
||||
updateEditorTheme : ()=>{},
|
||||
cursorPos : {},
|
||||
themeBundle : [],
|
||||
@@ -269,6 +270,10 @@ const Snippetbar = createReactClass({
|
||||
onClick={this.props.unfoldCode} >
|
||||
<i className='fas fa-expand-alt' />
|
||||
</button>
|
||||
<button className={`editorTool formatCode ${this.props.formatCode ? 'active' : ''}`}
|
||||
onClick={this.props.formatCode} >
|
||||
<i className='fas fa-wand-magic-sparkles' />
|
||||
</button>
|
||||
<button className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
|
||||
onClick={this.toggleThemeSelector} >
|
||||
<i className='fas fa-palette' />
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
.editors {
|
||||
display : flex;
|
||||
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;
|
||||
&:only-child {min-width : unset; margin-left : auto;}
|
||||
reading-order : 2;
|
||||
@@ -77,6 +77,11 @@
|
||||
color : grey;
|
||||
&.active { color : inherit; }
|
||||
}
|
||||
&.formatCode {
|
||||
.tooltipLeft('Clean your Code');
|
||||
color : grey;
|
||||
&.active { color : inherit; }
|
||||
}
|
||||
&.history {
|
||||
.tooltipLeft('History');
|
||||
position : relative;
|
||||
@@ -211,8 +216,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
@container editor (width < 816px) {
|
||||
.snippetBar {
|
||||
@container editor (width < 841px) {
|
||||
.snippetBar {
|
||||
.editors {
|
||||
flex : 1;
|
||||
justify-content : space-between;
|
||||
|
||||
@@ -181,6 +181,7 @@ const TagInput = ({ tooltip, label, valuePatterns, values = [], unique = true, p
|
||||
{t.value}
|
||||
<button
|
||||
type='button'
|
||||
aria-label={`remove ${t.value} tag`}
|
||||
onClick={(e)=>{
|
||||
e.stopPropagation();
|
||||
removeTag(i);
|
||||
|
||||
@@ -4,7 +4,7 @@ import './editPage.less';
|
||||
// Common imports
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import request from '../../utils/request-middleware.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import { hbfm } from 'hbmarkedwrapper';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
||||
@@ -62,7 +62,7 @@ const EditPage = (props)=>{
|
||||
const [lastSavedTime, setLastSavedTime] = useState(new Date());
|
||||
const [saveGoogle, setSaveGoogle] = useState(!!props.brew.googleId);
|
||||
const [error, setError] = useState(null);
|
||||
const [HTMLErrors, setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
||||
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
@@ -85,7 +85,7 @@ const EditPage = (props)=>{
|
||||
const autoSavePref = JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true);
|
||||
setAutoSaveEnabled(autoSavePref);
|
||||
setWarnUnsavedChanges(!autoSavePref);
|
||||
setHTMLErrors(Markdown.validate(currentBrew.text));
|
||||
setHTMLErrors(hbfm.validate(currentBrew.text));
|
||||
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
|
||||
|
||||
const handleControlKeys = (e)=>{
|
||||
@@ -131,7 +131,7 @@ const EditPage = (props)=>{
|
||||
|
||||
//If there are HTML errors, run the validator on every change to give quick feedback
|
||||
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
|
||||
setHTMLErrors(Markdown.validate(value));
|
||||
setHTMLErrors(hbfm.validate(value));
|
||||
|
||||
if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value }));
|
||||
else setCurrentBrew((prev)=>({ ...prev, [field]: value }));
|
||||
@@ -205,7 +205,7 @@ const EditPage = (props)=>{
|
||||
};
|
||||
|
||||
const save = async (brew, saveToGoogle)=>{
|
||||
setHTMLErrors(Markdown.validate(brew.text));
|
||||
setHTMLErrors(hbfm.validate(brew.text));
|
||||
|
||||
await updateHistory(brew).catch(console.error);
|
||||
await versionHistoryGarbageCollection().catch(console.error);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './errorPage.less';
|
||||
import React from 'react';
|
||||
import UIPage from '../basePages/uiPage/uiPage.jsx';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import { hbfm } from 'hbmarkedwrapper';
|
||||
import ErrorIndex from './errors/errorIndex.js';
|
||||
|
||||
const ErrorPage = ({ brew })=>{
|
||||
@@ -16,7 +16,7 @@ const ErrorPage = ({ brew })=>{
|
||||
<h4>{brew?.text || 'No error text'}</h4>
|
||||
</div>
|
||||
<hr />
|
||||
<div dangerouslySetInnerHTML={{ __html: Markdown.render(errorText) }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: hbfm.render(errorText) }} />
|
||||
</div>
|
||||
</UIPage>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import './homePage.less';
|
||||
// Common imports
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import request from '../../utils/request-middleware.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import { hbfm } from 'hbmarkedwrapper';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
||||
@@ -47,7 +47,7 @@ const HomePage =(props)=>{
|
||||
|
||||
const [currentBrew, setCurrentBrew] = useState(props.brew);
|
||||
const [error, setError] = useState(undefined);
|
||||
const [HTMLErrors, setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
||||
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
@@ -118,7 +118,7 @@ const HomePage =(props)=>{
|
||||
|
||||
//If there are HTML errors, run the validator on every change to give quick feedback
|
||||
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
|
||||
setHTMLErrors(Markdown.validate(value));
|
||||
setHTMLErrors(hbfm.validate(value));
|
||||
|
||||
if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value }));
|
||||
else setCurrentBrew((prev)=>({ ...prev, [field]: value }));
|
||||
|
||||
@@ -4,7 +4,7 @@ import './newPage.less';
|
||||
// Common imports
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import request from '../../utils/request-middleware.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import { hbfm } from 'hbmarkedwrapper';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
||||
@@ -46,7 +46,7 @@ const NewPage = (props)=>{
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveGoogle, setSaveGoogle] = useState(global.account?.googleId ? true : false);
|
||||
const [error, setError] = useState(null);
|
||||
const [HTMLErrors, setHTMLErrors] = useState(Markdown.validate(props.brew.text));
|
||||
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
@@ -133,7 +133,7 @@ const NewPage = (props)=>{
|
||||
|
||||
//If there are HTML errors, run the validator on every change to give quick feedback
|
||||
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
|
||||
setHTMLErrors(Markdown.validate(value));
|
||||
setHTMLErrors(hbfm.validate(value));
|
||||
|
||||
if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value }));
|
||||
else setCurrentBrew((prev)=>({ ...prev, [field]: value }));
|
||||
|
||||
Reference in New Issue
Block a user