mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-01 23:52:40 +00:00
Merge branch 'addLockRoutes-#3326' of https://github.com/G-Ambatte/homebrewery into addLockRoutes-#3326
This commit is contained in:
@@ -13,6 +13,8 @@ const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
|
||||
const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME';
|
||||
|
||||
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?: *{[^\n{}]*})?$)/m;
|
||||
const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
|
||||
const SNIPPETBAR_HEIGHT = 25;
|
||||
const DEFAULT_STYLE_TEXT = dedent`
|
||||
/*=======--- Example CSS styling ---=======*/
|
||||
/* Any CSS here will apply to your document! */
|
||||
@@ -21,6 +23,13 @@ const DEFAULT_STYLE_TEXT = dedent`
|
||||
color: black;
|
||||
}`;
|
||||
|
||||
const DEFAULT_SNIPPET_TEXT = dedent`
|
||||
\snippet example snippet
|
||||
|
||||
The text between \`\snippet title\` lines will become a snippet of name \`title\` as this example provides.
|
||||
|
||||
This snippet is accessible in the brew tab, and will be inherited if the brew is used as a theme.
|
||||
`;
|
||||
let isJumping = false;
|
||||
|
||||
const Editor = createClass({
|
||||
@@ -35,6 +44,7 @@ const Editor = createClass({
|
||||
onTextChange : ()=>{},
|
||||
onStyleChange : ()=>{},
|
||||
onMetaChange : ()=>{},
|
||||
onSnipChange : ()=>{},
|
||||
reportError : ()=>{},
|
||||
|
||||
onCursorPageChange : ()=>{},
|
||||
@@ -51,7 +61,7 @@ const Editor = createClass({
|
||||
getInitialState : function() {
|
||||
return {
|
||||
editorTheme : this.props.editorTheme,
|
||||
view : 'text' //'text', 'style', 'meta'
|
||||
view : 'text' //'text', 'style', 'meta', 'snippet'
|
||||
};
|
||||
},
|
||||
|
||||
@@ -61,6 +71,7 @@ const Editor = createClass({
|
||||
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';},
|
||||
|
||||
componentDidMount : function() {
|
||||
|
||||
@@ -131,6 +142,7 @@ const Editor = createClass({
|
||||
|
||||
handleViewChange : function(newView){
|
||||
this.props.setMoveArrows(newView === 'text');
|
||||
|
||||
this.setState({
|
||||
view : newView
|
||||
}, ()=>{
|
||||
@@ -140,7 +152,7 @@ const Editor = createClass({
|
||||
|
||||
highlightCustomMarkdown : function(){
|
||||
if(!this.codeEditor.current) return;
|
||||
if(this.state.view === 'text') {
|
||||
if((this.state.view === 'text') ||(this.state.view === 'snippet')) {
|
||||
const codeMirror = this.codeEditor.current.codeMirror;
|
||||
|
||||
codeMirror.operation(()=>{ // Batch CodeMirror styling
|
||||
@@ -159,12 +171,18 @@ const Editor = createClass({
|
||||
|
||||
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
|
||||
|
||||
_.forEach(this.props.brew.text.split('\n'), (line, lineNumber)=>{
|
||||
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');
|
||||
|
||||
@@ -175,22 +193,24 @@ const Editor = createClass({
|
||||
|
||||
// Styling for \page breaks
|
||||
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
|
||||
(this.props.renderer == 'V3' && line.match(PAGEBREAK_REGEX_V3))) {
|
||||
(this.props.renderer == 'V3' && line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3))) {
|
||||
|
||||
if(lineNumber > 0) // Since \page is optional on first line of document,
|
||||
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', 'pageLine');
|
||||
codeMirror.addLineClass(lineNumber, 'background', tabHighlight);
|
||||
const pageCountElement = Object.assign(document.createElement('span'), {
|
||||
className : 'editor-page-count',
|
||||
textContent : editorPageCount
|
||||
textContent : textOrSnip ? editorPageCount : userSnippetCount
|
||||
});
|
||||
codeMirror.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement);
|
||||
};
|
||||
|
||||
|
||||
// New Codemirror styling for V3 renderer
|
||||
if(this.props.renderer == 'V3') {
|
||||
if(this.props.renderer === 'V3') {
|
||||
if(line.match(/^\\column$/)){
|
||||
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
||||
}
|
||||
@@ -446,6 +466,21 @@ const Editor = createClass({
|
||||
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'
|
||||
view={this.state.view}
|
||||
value={this.props.brew.snippets}
|
||||
onChange={this.props.onSnipChange}
|
||||
enableFolding={true}
|
||||
editorTheme={this.state.editorTheme}
|
||||
rerenderParent={this.rerenderParent} />
|
||||
</>;
|
||||
}
|
||||
},
|
||||
|
||||
redo : function(){
|
||||
@@ -486,7 +521,7 @@ const Editor = createClass({
|
||||
historySize={this.historySize()}
|
||||
currentEditorTheme={this.state.editorTheme}
|
||||
updateEditorTheme={this.updateEditorTheme}
|
||||
snippetBundle={this.props.snippetBundle}
|
||||
themeBundle={this.props.themeBundle}
|
||||
cursorPos={this.codeEditor.current?.getCursorPosition() || {}}
|
||||
updateBrew={this.props.updateBrew}
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
.codeEditor {
|
||||
height : 100%;
|
||||
.CodeMirror { height : 100%; }
|
||||
.pageLine {
|
||||
.pageLine, .snippetLine {
|
||||
background : #33333328;
|
||||
border-top : #333399 solid 1px;
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
float : right;
|
||||
color : grey;
|
||||
}
|
||||
.editor-snippet-count {
|
||||
float : right;
|
||||
color : grey;
|
||||
}
|
||||
.columnSplit {
|
||||
font-style : italic;
|
||||
color : grey;
|
||||
|
||||
@@ -6,6 +6,7 @@ const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
import { loadHistory } from '../../utils/versionHistory.js';
|
||||
import { brewSnippetsToJSON } from '../../../../shared/helpers.js';
|
||||
|
||||
//Import all themes
|
||||
const ThemeSnippets = {};
|
||||
@@ -40,7 +41,7 @@ const Snippetbar = createClass({
|
||||
unfoldCode : ()=>{},
|
||||
updateEditorTheme : ()=>{},
|
||||
cursorPos : {},
|
||||
snippetBundle : [],
|
||||
themeBundle : [],
|
||||
updateBrew : ()=>{}
|
||||
};
|
||||
},
|
||||
@@ -64,7 +65,10 @@ const Snippetbar = createClass({
|
||||
},
|
||||
|
||||
componentDidUpdate : async function(prevProps, prevState) {
|
||||
if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) {
|
||||
if(prevProps.renderer != this.props.renderer ||
|
||||
prevProps.theme != this.props.theme ||
|
||||
prevProps.themeBundle != this.props.themeBundle ||
|
||||
prevProps.brew.snippets != this.props.brew.snippets) {
|
||||
this.setState({
|
||||
snippets : this.compileSnippets()
|
||||
});
|
||||
@@ -97,7 +101,7 @@ const Snippetbar = createClass({
|
||||
if(key == 'snippets') {
|
||||
const result = _.reverse(_.unionBy(_.reverse(newValue), _.reverse(oldValue), 'name')); // Join snippets together, with preference for the child theme over the parent theme
|
||||
return result.filter((snip)=>snip.gen || snip.subsnippets);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
compileSnippets : function() {
|
||||
@@ -105,15 +109,21 @@ const Snippetbar = createClass({
|
||||
|
||||
let oldSnippets = _.keyBy(compiledSnippets, 'groupName');
|
||||
|
||||
for (let snippets of this.props.snippetBundle) {
|
||||
if(typeof(snippets) == 'string') // load staticThemes as needed; they were sent as just a file name
|
||||
snippets = ThemeSnippets[snippets];
|
||||
if(this.props.themeBundle.snippets) {
|
||||
for (let snippets of this.props.themeBundle.snippets) {
|
||||
if(typeof(snippets) == 'string') // load staticThemes as needed; they were sent as just a file name
|
||||
snippets = ThemeSnippets[snippets];
|
||||
|
||||
const newSnippets = _.keyBy(_.cloneDeep(snippets), 'groupName');
|
||||
compiledSnippets = _.values(_.mergeWith(oldSnippets, newSnippets, this.mergeCustomizer));
|
||||
const newSnippets = _.keyBy(_.cloneDeep(snippets), 'groupName');
|
||||
compiledSnippets = _.values(_.mergeWith(oldSnippets, newSnippets, this.mergeCustomizer));
|
||||
|
||||
oldSnippets = _.keyBy(compiledSnippets, 'groupName');
|
||||
oldSnippets = _.keyBy(compiledSnippets, 'groupName');
|
||||
}
|
||||
}
|
||||
|
||||
const userSnippetsasJSON = brewSnippetsToJSON(this.props.brew.title || 'New Document', this.props.brew.snippets, this.props.themeBundle.snippets);
|
||||
compiledSnippets.push(userSnippetsasJSON);
|
||||
|
||||
return compiledSnippets;
|
||||
},
|
||||
|
||||
@@ -207,8 +217,6 @@ const Snippetbar = createClass({
|
||||
renderEditorButtons : function(){
|
||||
if(!this.props.showEditButtons) return;
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className='editors'>
|
||||
{this.props.view !== 'meta' && <><div className='historyTools'>
|
||||
@@ -242,7 +250,6 @@ const Snippetbar = createClass({
|
||||
</div>
|
||||
</div></>}
|
||||
|
||||
|
||||
<div className='tabs'>
|
||||
<div className={cx('text', { selected: this.props.view === 'text' })}
|
||||
onClick={()=>this.props.onViewChange('text')}>
|
||||
@@ -252,6 +259,10 @@ const Snippetbar = createClass({
|
||||
onClick={()=>this.props.onViewChange('style')}>
|
||||
<i className='fa fa-paint-brush' />
|
||||
</div>
|
||||
<div className={cx('snippet', { selected: this.props.view === 'snippet' })}
|
||||
onClick={()=>this.props.onViewChange('snippet')}>
|
||||
<i className='fas fa-th-list' />
|
||||
</div>
|
||||
<div className={cx('meta', { selected: this.props.view === 'meta' })}
|
||||
onClick={()=>this.props.onViewChange('meta')}>
|
||||
<i className='fas fa-info-circle' />
|
||||
@@ -272,11 +283,6 @@ const Snippetbar = createClass({
|
||||
|
||||
module.exports = Snippetbar;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const SnippetGroup = createClass({
|
||||
displayName : 'SnippetGroup',
|
||||
getDefaultProps : function() {
|
||||
@@ -310,7 +316,8 @@ const SnippetGroup = createClass({
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='snippetGroup snippetBarButton'>
|
||||
const snippetGroup = `snippetGroup snippetBarButton ${this.props.snippets.length === 0 ? 'disabledSnippets' : ''}`;
|
||||
return <div className={snippetGroup}>
|
||||
<div className='text'>
|
||||
<i className={this.props.icon} />
|
||||
<span className='groupName'>{this.props.groupName}</span>
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
.snippets {
|
||||
display : flex;
|
||||
justify-content : flex-start;
|
||||
min-width : 327.58px;
|
||||
min-width : 432.18px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
|
||||
}
|
||||
|
||||
.editors {
|
||||
display : flex;
|
||||
justify-content : flex-end;
|
||||
min-width : 225px;
|
||||
min-width : 250px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
|
||||
|
||||
&:only-child {min-width : unset; margin-left : auto;}
|
||||
|
||||
@@ -51,6 +51,9 @@
|
||||
&.meta {
|
||||
.tooltipLeft('Properties');
|
||||
}
|
||||
&.snip {
|
||||
.tooltipLeft('Snippets');
|
||||
}
|
||||
&.undo {
|
||||
.tooltipLeft('Undo');
|
||||
font-size : 0.75em;
|
||||
@@ -226,8 +229,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabledSnippets {
|
||||
color: grey;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover { background-color: #DDDDDD;}
|
||||
}
|
||||
|
||||
}
|
||||
@container editor (width < 553px) {
|
||||
@container editor (width < 683px) {
|
||||
.snippetBar {
|
||||
.editors {
|
||||
flex : 1;
|
||||
|
||||
@@ -150,6 +150,18 @@ const EditPage = createClass({
|
||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
||||
},
|
||||
|
||||
handleSnipChange : function(snippet){
|
||||
//If there are errors, run the validator on every change to give quick feedback
|
||||
let htmlErrors = this.state.htmlErrors;
|
||||
if(htmlErrors.length) htmlErrors = Markdown.validate(snippet);
|
||||
|
||||
this.setState((prevState)=>({
|
||||
brew : { ...prevState.brew, snippets: snippet },
|
||||
isPending : true,
|
||||
htmlErrors : htmlErrors,
|
||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
||||
},
|
||||
|
||||
handleStyleChange : function(style){
|
||||
this.setState((prevState)=>({
|
||||
brew : { ...prevState.brew, style: style }
|
||||
@@ -451,12 +463,12 @@ const EditPage = createClass({
|
||||
brew={this.state.brew}
|
||||
onTextChange={this.handleTextChange}
|
||||
onStyleChange={this.handleStyleChange}
|
||||
onSnipChange={this.handleSnipChange}
|
||||
onMetaChange={this.handleMetaChange}
|
||||
reportError={this.errorReported}
|
||||
renderer={this.state.brew.renderer}
|
||||
userThemes={this.props.userThemes}
|
||||
themeBundle={this.state.themeBundle}
|
||||
snippetBundle={this.state.themeBundle.snippets}
|
||||
updateBrew={this.updateBrew}
|
||||
onCursorPageChange={this.handleEditorCursorPageChange}
|
||||
onViewPageChange={this.handleEditorViewPageChange}
|
||||
|
||||
@@ -108,7 +108,7 @@ const HomePage = createClass({
|
||||
onTextChange={this.handleTextChange}
|
||||
renderer={this.state.brew.renderer}
|
||||
showEditButtons={false}
|
||||
snippetBundle={this.state.themeBundle.snippets}
|
||||
themeBundle={this.state.themeBundle}
|
||||
onCursorPageChange={this.handleEditorCursorPageChange}
|
||||
onViewPageChange={this.handleEditorViewPageChange}
|
||||
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
||||
|
||||
@@ -141,6 +141,18 @@ const NewPage = createClass({
|
||||
localStorage.setItem(STYLEKEY, style);
|
||||
},
|
||||
|
||||
handleSnipChange : function(snippet){
|
||||
//If there are errors, run the validator on every change to give quick feedback
|
||||
let htmlErrors = this.state.htmlErrors;
|
||||
if(htmlErrors.length) htmlErrors = Markdown.validate(snippet);
|
||||
|
||||
this.setState((prevState)=>({
|
||||
brew : { ...prevState.brew, snippets: snippet },
|
||||
isPending : true,
|
||||
htmlErrors : htmlErrors,
|
||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
||||
},
|
||||
|
||||
handleMetaChange : function(metadata, field=undefined){
|
||||
if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed
|
||||
fetchThemeBundle(this, metadata.renderer, metadata.theme);
|
||||
@@ -231,10 +243,10 @@ const NewPage = createClass({
|
||||
onTextChange={this.handleTextChange}
|
||||
onStyleChange={this.handleStyleChange}
|
||||
onMetaChange={this.handleMetaChange}
|
||||
onSnipChange={this.handleSnipChange}
|
||||
renderer={this.state.brew.renderer}
|
||||
userThemes={this.props.userThemes}
|
||||
themeBundle={this.state.themeBundle}
|
||||
snippetBundle={this.state.themeBundle.snippets}
|
||||
onCursorPageChange={this.handleEditorCursorPageChange}
|
||||
onViewPageChange={this.handleEditorViewPageChange}
|
||||
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
||||
|
||||
Reference in New Issue
Block a user