mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-03-23 15:18:14 +00:00
not really working but it will
This commit is contained in:
@@ -36,6 +36,53 @@ const highlightStyle = HighlightStyle.define([
|
||||
// …
|
||||
]);
|
||||
|
||||
/*custom tokens */
|
||||
import { Decoration, ViewPlugin, WidgetType } from "@codemirror/view";
|
||||
import { tokenizeCustomMarkdown, customTags } from "./customMarkdownGrammar.js";
|
||||
|
||||
const customHighlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.heading1, color: "#000", fontWeight: "700" },
|
||||
{ tag: tags.keyword, color: "#07a" }, // example for your markdown headings
|
||||
{ tag: customTags.pageLine, color: "#f0a" },
|
||||
{ tag: customTags.snippetBreak, class: "cm-snippet-break", color: "#0af" },
|
||||
{ tag: customTags.inlineBlock, class: "cm-inline-block", backgroundColor: "#fffae6" },
|
||||
{ tag: customTags.emoji, class: "cm-emoji", color: "#fa0" },
|
||||
{ tag: customTags.superscript, class: "cm-superscript", verticalAlign: "super", fontSize: "0.8em" },
|
||||
{ tag: customTags.subscript, class: "cm-subscript", verticalAlign: "sub", fontSize: "0.8em" },
|
||||
{ tag: customTags.definitionTerm, class: "cm-dt", fontWeight: "bold", color: "#0a0" },
|
||||
{ tag: customTags.definitionDesc, class: "cm-dd", color: "#070" },
|
||||
]);
|
||||
|
||||
const customHighlightPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
constructor(view) {
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
update(update) {
|
||||
if (update.docChanged) {
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
}
|
||||
buildDecorations(view) {
|
||||
const widgets = [];
|
||||
const tokens = tokenizeCustomMarkdown(view.state.doc.toString());
|
||||
|
||||
// sort by line number
|
||||
tokens.sort((a, b) => a.line - b.line);
|
||||
|
||||
tokens.forEach((tok) => {
|
||||
const line = view.state.doc.line(tok.line + 1); // CM lines are 1-based
|
||||
widgets.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from));
|
||||
});
|
||||
|
||||
return Decoration.set(widgets);
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
},
|
||||
);
|
||||
|
||||
const CodeEditor = forwardRef(
|
||||
(
|
||||
{
|
||||
@@ -116,6 +163,8 @@ const CodeEditor = forwardRef(
|
||||
lineNumbers(),
|
||||
themeExtension,
|
||||
syntaxHighlighting(highlightStyle),
|
||||
customHighlightPlugin,
|
||||
syntaxHighlighting(customHighlightStyle),
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
95
client/components/codeEditor/customMarkdownGrammar.js
Normal file
95
client/components/codeEditor/customMarkdownGrammar.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// customMarkdownGrammar.js
|
||||
|
||||
// --- Custom tags with CM6-compatible class names ---
|
||||
export const customTags = {
|
||||
pageLine: "pageLine", // .cm-pageLine
|
||||
snippetLine: "snippetLine", // .cm-snippetLine
|
||||
columnSplit: "columnSplit", // .cm-columnSplit
|
||||
snippetBreak: "snippetBreak", // .cm-snippetBreak
|
||||
inlineBlock: "inline-block", // .cm-inline-block
|
||||
block: "block", // .cm-block
|
||||
emoji: "emoji", // .cm-emoji
|
||||
superscript: "superscript", // .cm-superscript
|
||||
subscript: "subscript", // .cm-subscript
|
||||
definitionTerm: "dt-highlight", // .cm-dt-highlight
|
||||
definitionDesc: "dd-highlight", // .cm-dd-highlight
|
||||
injection: "injection", // .cm-injection
|
||||
};
|
||||
|
||||
// --- Tokenizer function ---
|
||||
export function tokenizeCustomMarkdown(text) {
|
||||
const tokens = [];
|
||||
const lines = text.split("\n");
|
||||
|
||||
// Track multi-line blocks
|
||||
let inBlock = false;
|
||||
let blockStart = 0;
|
||||
|
||||
lines.forEach((lineText, lineNumber) => {
|
||||
// --- Page / snippet lines ---
|
||||
if (/\\page/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.pageLine });
|
||||
if (/\\snippet/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetLine });
|
||||
if (/^\\column(?:break)?$/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.columnSplit });
|
||||
if (/\\snippet/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetBreak });
|
||||
|
||||
// --- Emoji ---
|
||||
if (/:\w+?:/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.emoji });
|
||||
|
||||
// --- Superscript / Subscript ---
|
||||
if (/\^\^/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.subscript });
|
||||
if (/\^/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.superscript });
|
||||
|
||||
// --- Definition lists ---
|
||||
if (/::/.test(lineText)) {
|
||||
tokens.push({ line: lineNumber, type: customTags.definitionDesc });
|
||||
tokens.push({ line: lineNumber, type: customTags.definitionTerm });
|
||||
}
|
||||
|
||||
// Track ranges already marked for injections
|
||||
const injectionRanges = [];
|
||||
|
||||
if (line.includes("{") && line.includes("}")) {
|
||||
const regex = /{[^{}]*}/gm;
|
||||
let match;
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
codeMirror?.markText(
|
||||
{ line: lineNumber, ch: match.index },
|
||||
{ line: lineNumber, ch: match.index + match[0].length },
|
||||
{ className: "injection" },
|
||||
);
|
||||
injectionRanges.push([match.index, match.index + match[0].length]);
|
||||
}
|
||||
}
|
||||
|
||||
// Now mark inline blocks, but skip overlapping injection ranges
|
||||
if (line.includes("{{") && line.includes("}}")) {
|
||||
const regex = /{{[^{}]*}}/gm;
|
||||
let match;
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
const start = match.index,
|
||||
end = match.index + match[0].length;
|
||||
const overlaps = injectionRanges.some(([iStart, iEnd]) => start < iEnd && end > iStart);
|
||||
if (!overlaps) {
|
||||
codeMirror?.markText(
|
||||
{ line: lineNumber, ch: start },
|
||||
{ line: lineNumber, ch: end },
|
||||
{ className: "inline-block" },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Multi-line blocks `{{…}}` --- only start/end lines
|
||||
if (lineText.trimLeft().startsWith("{{") && !lineText.trimLeft().endsWith("}}")) {
|
||||
inBlock = true;
|
||||
blockStart = lineNumber;
|
||||
tokens.push({ line: lineNumber, type: customTags.block });
|
||||
}
|
||||
if (lineText.trimLeft().startsWith("}}") && inBlock) {
|
||||
tokens.push({ line: lineNumber, type: customTags.block });
|
||||
inBlock = false;
|
||||
}
|
||||
});
|
||||
|
||||
return tokens;
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
/*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 dedent from 'dedent';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
import "./editor.less";
|
||||
import React from "react";
|
||||
import createReactClass from "create-react-class";
|
||||
import _ from "lodash";
|
||||
import dedent from "dedent";
|
||||
import Markdown from "@shared/markdown.js";
|
||||
|
||||
import CodeEditor from '../../components/codeEditor/codeEditor.jsx';
|
||||
import SnippetBar from './snippetbar/snippetbar.jsx';
|
||||
import MetadataEditor from './metadataEditor/metadataEditor.jsx';
|
||||
import CodeEditor from "../../components/codeEditor/codeEditor.jsx";
|
||||
import SnippetBar from "./snippetbar/snippetbar.jsx";
|
||||
import MetadataEditor from "./metadataEditor/metadataEditor.jsx";
|
||||
|
||||
const EDITOR_THEME_KEY = 'HB_editor_theme';
|
||||
const EDITOR_THEME_KEY = "HB_editor_theme";
|
||||
|
||||
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||
const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
|
||||
@@ -32,295 +32,348 @@ const DEFAULT_SNIPPET_TEXT = dedent`
|
||||
let isJumping = false;
|
||||
|
||||
const Editor = createReactClass({
|
||||
displayName : 'Editor',
|
||||
getDefaultProps : function() {
|
||||
displayName: "Editor",
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
brew : {
|
||||
text : '',
|
||||
style : ''
|
||||
brew: {
|
||||
text: "",
|
||||
style: "",
|
||||
},
|
||||
|
||||
onBrewChange : ()=>{},
|
||||
reportError : ()=>{},
|
||||
onBrewChange: () => {},
|
||||
reportError: () => {},
|
||||
|
||||
onCursorPageChange : ()=>{},
|
||||
onViewPageChange : ()=>{},
|
||||
onCursorPageChange: () => {},
|
||||
onViewPageChange: () => {},
|
||||
|
||||
editorTheme : 'default',
|
||||
renderer : 'legacy',
|
||||
editorTheme: "default",
|
||||
renderer: "legacy",
|
||||
|
||||
currentEditorCursorPageNum : 1,
|
||||
currentEditorViewPageNum : 1,
|
||||
currentBrewRendererPageNum : 1,
|
||||
currentEditorCursorPageNum: 1,
|
||||
currentEditorViewPageNum: 1,
|
||||
currentBrewRendererPageNum: 1,
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
editorTheme : this.props.editorTheme,
|
||||
view : 'text', //'text', 'style', 'meta', 'snippet'
|
||||
snippetBarHeight : 26,
|
||||
editorTheme: this.props.editorTheme,
|
||||
view: "text", //'text', 'style', 'meta', 'snippet'
|
||||
snippetBarHeight: 26,
|
||||
};
|
||||
},
|
||||
|
||||
editor : React.createRef(null),
|
||||
codeEditor : React.createRef(null),
|
||||
editor: React.createRef(null),
|
||||
codeEditor: React.createRef(null),
|
||||
|
||||
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() {
|
||||
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 () {
|
||||
this.highlightCustomMarkdown();
|
||||
document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys);
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
document.getElementById("BrewRenderer").addEventListener("keydown", this.handleControlKeys);
|
||||
document.addEventListener("keydown", this.handleControlKeys);
|
||||
|
||||
this.codeEditor.current.codeMirror?.on('cursorActivity', (cm)=>{this.updateCurrentCursorPage(cm.getCursor());});
|
||||
this.codeEditor.current.codeMirror?.on('scroll', _.throttle(()=>{this.updateCurrentViewPage(this.codeEditor.current.getTopVisibleLine());}, 200));
|
||||
this.codeEditor.current.codeMirror?.on("cursorActivity", (cm) => {
|
||||
this.updateCurrentCursorPage(cm.getCursor());
|
||||
});
|
||||
this.codeEditor.current.codeMirror?.on(
|
||||
"scroll",
|
||||
_.throttle(() => {
|
||||
this.updateCurrentViewPage(this.codeEditor.current.getTopVisibleLine());
|
||||
}, 200),
|
||||
);
|
||||
|
||||
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
||||
if(editorTheme) {
|
||||
if (editorTheme) {
|
||||
this.setState({
|
||||
editorTheme : editorTheme
|
||||
editorTheme: editorTheme,
|
||||
});
|
||||
}
|
||||
const snippetBar = document.querySelector('.editor > .snippetBar');
|
||||
if(!snippetBar) return;
|
||||
const snippetBar = document.querySelector(".editor > .snippetBar");
|
||||
if (!snippetBar) return;
|
||||
|
||||
this.resizeObserver = new ResizeObserver((entries)=>{
|
||||
const height = document.querySelector('.editor > .snippetBar').offsetHeight;
|
||||
this.resizeObserver = new ResizeObserver((entries) => {
|
||||
const height = document.querySelector(".editor > .snippetBar").offsetHeight;
|
||||
this.setState({ snippetBarHeight: height });
|
||||
});
|
||||
|
||||
this.resizeObserver.observe(snippetBar);
|
||||
},
|
||||
|
||||
componentDidUpdate : function(prevProps, prevState, snapshot) {
|
||||
|
||||
componentDidUpdate: function (prevProps, prevState, snapshot) {
|
||||
this.highlightCustomMarkdown();
|
||||
if(prevProps.moveBrew !== this.props.moveBrew)
|
||||
this.brewJump();
|
||||
if (prevProps.moveBrew !== this.props.moveBrew) this.brewJump();
|
||||
|
||||
if(prevProps.moveSource !== this.props.moveSource)
|
||||
this.sourceJump();
|
||||
if (prevProps.moveSource !== this.props.moveSource) this.sourceJump();
|
||||
|
||||
if(this.props.liveScroll) {
|
||||
if(prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) {
|
||||
if (this.props.liveScroll) {
|
||||
if (prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) {
|
||||
this.sourceJump(this.props.currentBrewRendererPageNum, false);
|
||||
} else if(prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) {
|
||||
} else if (prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) {
|
||||
this.brewJump(this.props.currentEditorViewPageNum, false);
|
||||
} else if(prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) {
|
||||
} else if (prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) {
|
||||
this.brewJump(this.props.currentEditorCursorPageNum, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
if(this.resizeObserver) this.resizeObserver.disconnect();
|
||||
if (this.resizeObserver) this.resizeObserver.disconnect();
|
||||
},
|
||||
|
||||
handleControlKeys : function(e){
|
||||
if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return;
|
||||
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) {
|
||||
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(cursor) {
|
||||
const lines = this.props.brew.text.split('\n').slice(1, cursor.line + 1);
|
||||
const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
|
||||
const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1);
|
||||
updateCurrentCursorPage: function (cursor) {
|
||||
const lines = this.props.brew.text.split("\n").slice(1, cursor.line + 1);
|
||||
const pageRegex = this.props.brew.renderer == "V3" ? PAGEBREAK_REGEX_V3 : /\\page/;
|
||||
const currentPage = lines.reduce((count, line) => count + (pageRegex.test(line) ? 1 : 0), 1);
|
||||
this.props.onCursorPageChange(currentPage);
|
||||
},
|
||||
|
||||
updateCurrentViewPage : function(topScrollLine) {
|
||||
const lines = this.props.brew.text.split('\n').slice(1, topScrollLine + 1);
|
||||
const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
|
||||
const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1);
|
||||
updateCurrentViewPage: function (topScrollLine) {
|
||||
const lines = this.props.brew.text.split("\n").slice(1, topScrollLine + 1);
|
||||
const pageRegex = this.props.brew.renderer == "V3" ? PAGEBREAK_REGEX_V3 : /\\page/;
|
||||
const currentPage = lines.reduce((count, line) => count + (pageRegex.test(line) ? 1 : 0), 1);
|
||||
this.props.onViewPageChange(currentPage);
|
||||
},
|
||||
|
||||
handleInject : function(injectText){
|
||||
handleInject: function (injectText) {
|
||||
this.codeEditor.current?.injectText(injectText, false);
|
||||
},
|
||||
|
||||
handleViewChange : function(newView){
|
||||
this.props.setMoveArrows(newView === 'text');
|
||||
handleViewChange: function (newView) {
|
||||
this.props.setMoveArrows(newView === "text");
|
||||
|
||||
this.setState({
|
||||
view : newView
|
||||
}, ()=>{
|
||||
this.codeEditor.current?.codeMirror?.focus();
|
||||
});
|
||||
this.setState(
|
||||
{
|
||||
view: newView,
|
||||
},
|
||||
() => {
|
||||
this.codeEditor.current?.codeMirror?.focus();
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
highlightCustomMarkdown : function(){
|
||||
if(!this.codeEditor.current?.codeMirror) return;
|
||||
if((this.state.view === 'text') ||(this.state.view === 'snippet')) {
|
||||
highlightCustomMarkdown: function () {
|
||||
if (!this.codeEditor.current?.codeMirror) return;
|
||||
if (this.state.view === "text" || this.state.view === "snippet") {
|
||||
const codeMirror = this.codeEditor.current.codeMirror;
|
||||
|
||||
codeMirror?.operation(()=>{ // Batch CodeMirror styling
|
||||
codeMirror?.operation(() => {
|
||||
// Batch CodeMirror styling
|
||||
|
||||
const foldLines = [];
|
||||
|
||||
//reset custom text styles
|
||||
const customHighlights = codeMirror?.getAllMarks().filter((mark)=>{
|
||||
const customHighlights = codeMirror?.getAllMarks().filter((mark) => {
|
||||
// Record details of folded sections
|
||||
if(mark.__isFold) {
|
||||
if (mark.__isFold) {
|
||||
const fold = mark.find();
|
||||
foldLines.push({ from: fold.from?.line, to: fold.to?.line });
|
||||
}
|
||||
return !mark.__isFold;
|
||||
}); //Don't undo code folding
|
||||
|
||||
for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear();
|
||||
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
|
||||
|
||||
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';
|
||||
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');
|
||||
codeMirror?.removeLineClass(lineNumber, "background", "pageLine");
|
||||
codeMirror?.removeLineClass(lineNumber, "background", "snippetLine");
|
||||
codeMirror?.removeLineClass(lineNumber, "text");
|
||||
codeMirror?.removeLineClass(lineNumber, "wrap", "sourceMoveFlash");
|
||||
|
||||
// Don't process lines inside folded text
|
||||
// If the current lineNumber is inside any folded marks, skip line styling
|
||||
if(foldLines.some((fold)=>lineNumber >= fold.from && lineNumber <= fold.to))
|
||||
return;
|
||||
if (foldLines.some((fold) => lineNumber >= fold.from && lineNumber <= fold.to)) return;
|
||||
|
||||
// Styling for \page breaks
|
||||
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
|
||||
(this.props.renderer == 'V3' && line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3))) {
|
||||
|
||||
if((lineNumber > 0) && (textOrSnip)) // Since \page is optional on first line of document,
|
||||
if (
|
||||
(this.props.renderer == "legacy" && line.includes("\\page")) ||
|
||||
(this.props.renderer == "V3" &&
|
||||
line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3))
|
||||
) {
|
||||
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;
|
||||
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', tabHighlight);
|
||||
const pageCountElement = Object.assign(document.createElement('span'), {
|
||||
className : 'editor-page-count',
|
||||
textContent : textOrSnip ? editorPageCount : userSnippetCount
|
||||
codeMirror?.addLineClass(lineNumber, "background", tabHighlight);
|
||||
const pageCountElement = Object.assign(document.createElement("span"), {
|
||||
className: "editor-page-count",
|
||||
textContent: textOrSnip ? editorPageCount : userSnippetCount,
|
||||
});
|
||||
codeMirror?.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// New CodeMirror styling for V3 renderer
|
||||
if(this.props.renderer === 'V3') {
|
||||
if(line.match(/^\\column(?:break)?$/)){
|
||||
codeMirror?.addLineClass(lineNumber, 'text', 'columnSplit');
|
||||
if (this.props.renderer === "V3") {
|
||||
if (line.match(/^\\column(?:break)?$/)) {
|
||||
codeMirror?.addLineClass(lineNumber, "text", "columnSplit");
|
||||
}
|
||||
|
||||
// definition lists
|
||||
if(line.includes('::')){
|
||||
if(/^:*$/.test(line) == true){ return; };
|
||||
const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error.
|
||||
if (line.includes("::")) {
|
||||
if (/^:*$/.test(line) == true) {
|
||||
return;
|
||||
}
|
||||
const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/dmy; // the `d` flag, for match indices, throws an ESLint error.
|
||||
let match;
|
||||
while ((match = regex.exec(line)) != null){
|
||||
codeMirror?.markText({ line: lineNumber, ch: match.indices[0][0] }, { line: lineNumber, ch: match.indices[0][1] }, { className: 'dl-highlight' });
|
||||
codeMirror?.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' });
|
||||
codeMirror?.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' });
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
codeMirror?.markText(
|
||||
{ line: lineNumber, ch: match.indices[0][0] },
|
||||
{ line: lineNumber, ch: match.indices[0][1] },
|
||||
{ className: "dl-highlight" },
|
||||
);
|
||||
codeMirror?.markText(
|
||||
{ line: lineNumber, ch: match.indices[1][0] },
|
||||
{ line: lineNumber, ch: match.indices[1][1] },
|
||||
{ className: "dt-highlight" },
|
||||
);
|
||||
codeMirror?.markText(
|
||||
{ line: lineNumber, ch: match.indices[2][0] },
|
||||
{ line: lineNumber, ch: match.indices[2][1] },
|
||||
{ className: "dd-highlight" },
|
||||
);
|
||||
const ddIndex = match.indices[2][0];
|
||||
const colons = /::/g;
|
||||
const colonMatches = colons.exec(match[2]);
|
||||
if(colonMatches !== null){
|
||||
codeMirror?.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight' });
|
||||
if (colonMatches !== null) {
|
||||
codeMirror?.markText(
|
||||
{ line: lineNumber, ch: colonMatches.index + ddIndex },
|
||||
{ line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex },
|
||||
{ className: "dl-colon-highlight" },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subscript & Superscript
|
||||
if(line.includes('^')) {
|
||||
let startIndex = line.indexOf('^');
|
||||
if (line.includes("^")) {
|
||||
let startIndex = line.indexOf("^");
|
||||
const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy;
|
||||
const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy;
|
||||
const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy;
|
||||
|
||||
while (startIndex >= 0) {
|
||||
superRegex.lastIndex = subRegex.lastIndex = startIndex;
|
||||
let isSuper = false;
|
||||
const match = subRegex.exec(line) || superRegex.exec(line);
|
||||
if(match) {
|
||||
if (match) {
|
||||
isSuper = !subRegex.lastIndex;
|
||||
codeMirror?.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' });
|
||||
codeMirror?.markText(
|
||||
{ line: lineNumber, ch: match.index },
|
||||
{ line: lineNumber, ch: match.index + match[0].length },
|
||||
{ className: isSuper ? "superscript" : "subscript" },
|
||||
);
|
||||
}
|
||||
startIndex = line.indexOf('^', Math.max(startIndex + 1, subRegex.lastIndex, superRegex.lastIndex));
|
||||
startIndex = line.indexOf(
|
||||
"^",
|
||||
Math.max(startIndex + 1, subRegex.lastIndex, superRegex.lastIndex),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight injectors {style}
|
||||
if(line.includes('{') && line.includes('}')){
|
||||
const regex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm;
|
||||
// Injections: single-line {…}
|
||||
if (line.includes("{") && line.includes("}")) {
|
||||
const injectionRegex =
|
||||
/(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm;
|
||||
let match;
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
codeMirror?.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' });
|
||||
while ((match = injectionRegex.exec(line)) !== null) {
|
||||
tokens.push({
|
||||
line: lineNumber,
|
||||
from: match.index,
|
||||
to: match.index + match[1].length,
|
||||
type: "injection",
|
||||
});
|
||||
}
|
||||
}
|
||||
// Highlight inline spans {{content}}
|
||||
if(line.includes('{{') && line.includes('}}')){
|
||||
const regex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g;
|
||||
} else if (line.includes("{{") && line.includes("}}")) { // Inline blocks: single-line {{…}}
|
||||
const spanRegex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g;
|
||||
let match;
|
||||
let blockCount = 0;
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
if(match[0].startsWith('{')) {
|
||||
while ((match = spanRegex.exec(line)) !== null) {
|
||||
if (match[0].startsWith("{{")) {
|
||||
blockCount += 1;
|
||||
} else {
|
||||
blockCount -= 1;
|
||||
}
|
||||
if(blockCount < 0) {
|
||||
if (blockCount < 0) {
|
||||
blockCount = 0;
|
||||
continue;
|
||||
}
|
||||
codeMirror?.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' });
|
||||
tokens.push({
|
||||
line: lineNumber,
|
||||
from: match.index,
|
||||
to: match.index + match[0].length,
|
||||
type: "inline-block",
|
||||
});
|
||||
}
|
||||
} else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){
|
||||
} else if (line.trimLeft().startsWith("{{") || line.trimLeft().startsWith("}}")) {
|
||||
// Highlight block divs {{\n Content \n}}
|
||||
let endCh = line.length+1;
|
||||
let endCh = line.length + 1;
|
||||
|
||||
const match = line.match(/^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/);
|
||||
if(match)
|
||||
endCh = match.index+match[0].length;
|
||||
codeMirror?.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
|
||||
const match = line.match(
|
||||
/^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/,
|
||||
);
|
||||
if (match) endCh = match.index + match[0].length;
|
||||
codeMirror?.markText(
|
||||
{ line: lineNumber, ch: 0 },
|
||||
{ line: lineNumber, ch: endCh },
|
||||
{ className: "block" },
|
||||
);
|
||||
}
|
||||
|
||||
// Emojis
|
||||
if(line.match(/:[^\s:]+:/g)) {
|
||||
let startIndex = line.indexOf(':');
|
||||
if (line.match(/:[^\s:]+:/g)) {
|
||||
let startIndex = line.indexOf(":");
|
||||
const emojiRegex = /:[^\s:]+:/gy;
|
||||
|
||||
while (startIndex >= 0) {
|
||||
emojiRegex.lastIndex = startIndex;
|
||||
const match = emojiRegex.exec(line);
|
||||
if(match) {
|
||||
if (match) {
|
||||
let tokens = Markdown.marked.lexer(match[0]);
|
||||
tokens = tokens[0].tokens.filter((t)=>t.type == 'emoji');
|
||||
if(!tokens.length)
|
||||
return;
|
||||
tokens = tokens[0].tokens.filter((t) => t.type == "emoji");
|
||||
if (!tokens.length) return;
|
||||
|
||||
const startPos = { line: lineNumber, ch: match.index };
|
||||
const endPos = { line: lineNumber, ch: match.index + match[0].length };
|
||||
const endPos = { line: lineNumber, ch: match.index + match[0].length };
|
||||
|
||||
// Iterate over conflicting marks and clear them
|
||||
const marks = codeMirror?.findMarks(startPos, endPos);
|
||||
marks.forEach(function(marker) {
|
||||
if(!marker.__isFold) marker.clear();
|
||||
marks.forEach(function (marker) {
|
||||
if (!marker.__isFold) marker.clear();
|
||||
});
|
||||
codeMirror?.markText(startPos, endPos, { className: 'emoji' });
|
||||
codeMirror?.markText(startPos, endPos, { className: "emoji" });
|
||||
}
|
||||
startIndex = line.indexOf(':', Math.max(startIndex + 1, emojiRegex.lastIndex));
|
||||
startIndex = line.indexOf(":", Math.max(startIndex + 1, emojiRegex.lastIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,198 +382,226 @@ const Editor = createReactClass({
|
||||
}
|
||||
},
|
||||
|
||||
brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){
|
||||
if(!window || !this.isText() || isJumping)
|
||||
return;
|
||||
brewJump: function (targetPage = this.props.currentEditorCursorPageNum, smooth = true) {
|
||||
if (!window || !this.isText() || isJumping) return;
|
||||
|
||||
// Get current brewRenderer scroll position and calculate target position
|
||||
const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0];
|
||||
const brewRenderer = window.frames["BrewRenderer"].contentDocument.getElementsByClassName("brewRenderer")[0];
|
||||
const currentPos = brewRenderer.scrollTop;
|
||||
const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top;
|
||||
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(()=>{
|
||||
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;
|
||||
brewRenderer.removeEventListener('scroll', checkIfScrollComplete);
|
||||
}, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done
|
||||
brewRenderer.removeEventListener("scroll", checkIfScrollComplete);
|
||||
}, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done
|
||||
};
|
||||
|
||||
isJumping = true;
|
||||
checkIfScrollComplete();
|
||||
brewRenderer.addEventListener('scroll', checkIfScrollComplete);
|
||||
brewRenderer.addEventListener("scroll", checkIfScrollComplete);
|
||||
|
||||
if(smooth) {
|
||||
const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling
|
||||
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 });
|
||||
};
|
||||
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 },
|
||||
);
|
||||
}
|
||||
this.throttleBrewMove(currentPos, bouncePos, targetPos);
|
||||
} else {
|
||||
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'instant', block: 'start' });
|
||||
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: "instant", block: "start" });
|
||||
}
|
||||
},
|
||||
|
||||
sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){
|
||||
if(!this.isText() || isJumping)
|
||||
return;
|
||||
sourceJump: function (targetPage = this.props.currentBrewRendererPageNum, smooth = true) {
|
||||
if (!this.isText() || isJumping) return;
|
||||
|
||||
const textSplit = this.props.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
|
||||
const textString = this.props.brew.text.split(textSplit).slice(0, targetPage-1).join(textSplit);
|
||||
const targetLine = textString.match('\n') ? textString.split('\n').length - 1 : -1;
|
||||
const textSplit = this.props.renderer == "V3" ? PAGEBREAK_REGEX_V3 : /\\page/;
|
||||
const textString = this.props.brew.text
|
||||
.split(textSplit)
|
||||
.slice(0, targetPage - 1)
|
||||
.join(textSplit);
|
||||
const targetLine = textString.match("\n") ? textString.split("\n").length - 1 : -1;
|
||||
|
||||
let currentY = this.codeEditor.current.codeMirror?.getScrollInfo().top;
|
||||
let targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, 'local', true);
|
||||
let targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, "local", true);
|
||||
|
||||
let scrollingTimeout;
|
||||
const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times
|
||||
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(()=>{
|
||||
scrollingTimeout = setTimeout(() => {
|
||||
isJumping = false;
|
||||
this.codeEditor.current.codeMirror?.off('scroll', checkIfScrollComplete);
|
||||
this.codeEditor.current.codeMirror?.off("scroll", checkIfScrollComplete);
|
||||
}, 150); // If 150 ms pass without a scroll event, assume scrolling is done
|
||||
};
|
||||
|
||||
isJumping = true;
|
||||
checkIfScrollComplete();
|
||||
if(this.codeEditor.current?.codeMirror) {
|
||||
this.codeEditor.current.codeMirror?.on('scroll', checkIfScrollComplete);
|
||||
if (this.codeEditor.current?.codeMirror) {
|
||||
this.codeEditor.current.codeMirror?.on("scroll", checkIfScrollComplete);
|
||||
}
|
||||
|
||||
if(smooth) {
|
||||
if (smooth) {
|
||||
//Scroll 1/10 of the way every 10ms until 1px off.
|
||||
const incrementalScroll = setInterval(()=>{
|
||||
const incrementalScroll = setInterval(() => {
|
||||
currentY += (targetY - currentY) / 10;
|
||||
this.codeEditor.current.codeMirror?.scrollTo(null, currentY);
|
||||
|
||||
// Update target: target height is not accurate until within +-10 lines of the visible window
|
||||
if(Math.abs(targetY - currentY > 100))
|
||||
targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, 'local', true);
|
||||
if (Math.abs(targetY - currentY > 100))
|
||||
targetY = this.codeEditor.current.codeMirror?.heightAtLine(targetLine, "local", true);
|
||||
|
||||
// End when close enough
|
||||
if(Math.abs(targetY - currentY) < 1) {
|
||||
this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference
|
||||
if (Math.abs(targetY - currentY) < 1) {
|
||||
this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference
|
||||
this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 });
|
||||
this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash');
|
||||
this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, "wrap", "sourceMoveFlash");
|
||||
clearInterval(incrementalScroll);
|
||||
}
|
||||
}, 10);
|
||||
} else {
|
||||
this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference
|
||||
this.codeEditor.current.codeMirror?.scrollTo(null, targetY); // Scroll any remaining difference
|
||||
this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 });
|
||||
this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash');
|
||||
this.codeEditor.current.codeMirror?.addLineClass(targetLine + 1, "wrap", "sourceMoveFlash");
|
||||
}
|
||||
},
|
||||
|
||||
//Called when there are changes to the editor's dimensions
|
||||
update : function(){},
|
||||
update: function () {},
|
||||
|
||||
updateEditorTheme : function(newTheme){
|
||||
updateEditorTheme: function (newTheme) {
|
||||
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
|
||||
this.setState({
|
||||
editorTheme : newTheme
|
||||
editorTheme: newTheme,
|
||||
});
|
||||
},
|
||||
|
||||
//Called by CodeEditor after document switch, so Snippetbar can refresh UndoHistory
|
||||
rerenderParent : function (){
|
||||
rerenderParent: function () {
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
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')}
|
||||
editorTheme={this.state.editorTheme}
|
||||
rerenderParent={this.rerenderParent}
|
||||
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }} />
|
||||
</>;
|
||||
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")}
|
||||
editorTheme={this.state.editorTheme}
|
||||
rerenderParent={this.rerenderParent}
|
||||
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')}
|
||||
enableFolding={true}
|
||||
editorTheme={this.state.editorTheme}
|
||||
rerenderParent={this.rerenderParent}
|
||||
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")}
|
||||
enableFolding={true}
|
||||
editorTheme={this.state.editorTheme}
|
||||
rerenderParent={this.rerenderParent}
|
||||
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if(this.isMeta()){
|
||||
return <>
|
||||
<CodeEditor key='codeEditor'
|
||||
view={this.state.view}
|
||||
style={{ display: 'none' }}
|
||||
rerenderParent={this.rerenderParent} />
|
||||
<MetadataEditor
|
||||
metadata={this.props.brew}
|
||||
themeBundle={this.props.themeBundle}
|
||||
onChange={this.props.onBrewChange('metadata')}
|
||||
reportError={this.props.reportError}
|
||||
userThemes={this.props.userThemes}/>
|
||||
</>;
|
||||
if (this.isMeta()) {
|
||||
return (
|
||||
<>
|
||||
<CodeEditor
|
||||
key="codeEditor"
|
||||
view={this.state.view}
|
||||
style={{ display: "none" }}
|
||||
rerenderParent={this.rerenderParent}
|
||||
/>
|
||||
<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}
|
||||
rerenderParent={this.rerenderParent}
|
||||
style={{ height: `calc(100% -${this.state.snippetBarHeight}px)` }} />
|
||||
</>;
|
||||
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}
|
||||
rerenderParent={this.rerenderParent}
|
||||
style={{ height: `calc(100% -${this.state.snippetBarHeight}px)` }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
redo : function(){
|
||||
redo: function () {
|
||||
return this.codeEditor.current?.redo();
|
||||
},
|
||||
|
||||
historySize : function(){
|
||||
historySize: function () {
|
||||
return this.codeEditor.current?.historySize();
|
||||
},
|
||||
|
||||
undo : function(){
|
||||
undo: function () {
|
||||
return this.codeEditor.current?.undo();
|
||||
},
|
||||
|
||||
foldCode : function(){
|
||||
foldCode: function () {
|
||||
return this.codeEditor.current?.foldAllCode();
|
||||
},
|
||||
|
||||
unfoldCode : function(){
|
||||
unfoldCode: function () {
|
||||
return this.codeEditor.current?.unfoldAllCode();
|
||||
},
|
||||
|
||||
render : function(){
|
||||
render: function () {
|
||||
return (
|
||||
<div className='editor' ref={this.editor}>
|
||||
<div className="editor" ref={this.editor}>
|
||||
<SnippetBar
|
||||
brew={this.props.brew}
|
||||
view={this.state.view}
|
||||
@@ -544,7 +625,7 @@ const Editor = createReactClass({
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default Editor;
|
||||
|
||||
@@ -10,25 +10,25 @@
|
||||
.codeEditor {
|
||||
height : calc(100% - 25px);
|
||||
.cm-editor { height : 100%; }
|
||||
.pageLine, .snippetLine {
|
||||
.cm-pageLine, .cm-snippetLine {
|
||||
background : #33333328;
|
||||
border-top : #333399 solid 1px;
|
||||
}
|
||||
.editor-page-count {
|
||||
.cm-editor-page-count {
|
||||
float : right;
|
||||
color : grey;
|
||||
}
|
||||
.editor-snippet-count {
|
||||
.cm-editor-snippet-count {
|
||||
float : right;
|
||||
color : grey;
|
||||
}
|
||||
.columnSplit {
|
||||
.cm-cm-columnSplit {
|
||||
font-style : italic;
|
||||
color : grey;
|
||||
background-color : fade(#229999, 15%);
|
||||
border-bottom : #229999 solid 1px;
|
||||
}
|
||||
.define {
|
||||
.cm-define {
|
||||
&:not(.term):not(.definition) {
|
||||
font-weight : bold;
|
||||
color : #949494;
|
||||
@@ -38,21 +38,21 @@
|
||||
&.term { color : rgb(96, 117, 143); }
|
||||
&.definition { color : rgb(97, 57, 178); }
|
||||
}
|
||||
.block:not(.cm-comment) {
|
||||
.cm-block:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : purple;
|
||||
//font-style: italic;
|
||||
}
|
||||
.inline-block:not(.cm-comment) {
|
||||
.cm-inline-block:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : red;
|
||||
//font-style: italic;
|
||||
}
|
||||
.injection:not(.cm-comment) {
|
||||
.cm-injection:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : green;
|
||||
}
|
||||
.emoji:not(.cm-comment) {
|
||||
.cm-emoji:not(.cm-comment) {
|
||||
padding-bottom : 1px;
|
||||
margin-left : 2px;
|
||||
font-weight : bold;
|
||||
@@ -62,19 +62,19 @@
|
||||
background : #FFC8FF;
|
||||
border-radius : 6px;
|
||||
}
|
||||
.superscript:not(.cm-comment) {
|
||||
.cm-superscript:not(.cm-comment) {
|
||||
font-size : 0.9em;
|
||||
font-weight : bold;
|
||||
vertical-align : super;
|
||||
color : goldenrod;
|
||||
}
|
||||
.subscript:not(.cm-comment) {
|
||||
.cm-subscript:not(.cm-comment) {
|
||||
font-size : 0.9em;
|
||||
font-weight : bold;
|
||||
vertical-align : sub;
|
||||
color : rgb(123, 123, 15);
|
||||
}
|
||||
.dl-highlight {
|
||||
.cm-dl-highlight {
|
||||
&.dl-colon-highlight {
|
||||
font-weight : bold;
|
||||
color : #949494;
|
||||
|
||||
Reference in New Issue
Block a user