From d390d518a3324767c6fda0672254417460c60a45 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Tue, 7 Nov 2023 22:42:53 -0600 Subject: [PATCH 01/10] Initial commit, subscripts and superscripts --- shared/naturalcrit/markdown.js | 46 +++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 114229887..ae30e4922 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -206,6 +206,50 @@ const mustacheInjectBlock = { } }; +const superScripts = { + name : 'superScriptsInjectInline', + level : 'inline', + start(src) { return src.match(/.*\^\^(.+)\^\^/)?.index; }, // Hint to Marked.js to stop and check for a match + tokenizer(src, tokens) { + const inlineRegex = /.*\^\^(.+)\^\^/y; + const match = inlineRegex.exec(src); + if(match) { + const tags = ` ${processStyleTags(match[1])}`; + return { + type : 'text', // Should match "name" above + raw : match[0], // Text to consume from the source + text : '', + tags + }; + } + }, + renderer(token) { + return `${token.tags}`; + } +}; + +const subScripts = { + name : 'subScriptsInjectInline', + level : 'inline', + start(src) { return src.match(/.*\^\^\^(.+)\^\^\^/)?.index; }, // Hint to Marked.js to stop and check for a match + tokenizer(src, tokens) { + const inlineRegex = /.*\^\^\^(.+)\^\^\^/y; + const match = inlineRegex.exec(src); + if(match) { + const tags = ` ${processStyleTags(match[1])}`; + return { + type : 'text', // Should match "name" above + raw : match[0], // Text to consume from the source + text : '', + tags + }; + } + }, + renderer(token) { + return `${token.tags}`; + } +}; + const definitionLists = { name : 'definitionLists', level : 'block', @@ -238,7 +282,7 @@ const definitionLists = { } }; -Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] }); +Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, subScripts, superScripts] }); Marked.use(mustacheInjectBlock); Marked.use({ renderer: renderer, mangle: false }); Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite()); From f1ca6eeee28cb664ef370eefee71ac25ba33a9d7 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Wed, 8 Nov 2023 00:49:39 -0600 Subject: [PATCH 02/10] Near complete --- client/homebrew/editor/editor.jsx | 18 +++++++ client/homebrew/editor/editor.less | 8 +++ shared/naturalcrit/codeEditor/codeEditor.jsx | 23 +++++++++ shared/naturalcrit/markdown.js | 53 ++++++++------------ 4 files changed, 71 insertions(+), 31 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index b4f1fc824..df8100db3 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -160,6 +160,24 @@ const Editor = createClass({ } } + // Superscript + if(line.includes('^^') && !line.includes('^^^')) { + const regex = /.*\^\^(.+)\^\^/y; + let match; + while ((match = regex.exec(line)) != null) { + codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 2 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 2 }, { className: 'superscript' }); + } + } + + // Subscript + if(line.includes('^^^')) { + const regex = /.*\^\^\^(.+)\^\^\^/y; + let match; + while ((match = regex.exec(line)) != null) { + codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 3 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 3 }, { className: 'subscript' }); + } + } + // Highlight injectors {style} if(line.includes('{') && line.includes('}')){ const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm; diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index d31ceb97c..1f8b6e718 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -43,6 +43,14 @@ font-weight : bold; color : green; } + .superscript:not(.cm-comment) { + font-weight : bold; + color : goldenrod; + } + .subscript:not(.cm-comment) { + font-weight : bold; + color : rgb(123, 123, 15); + } } .brewJump { diff --git a/shared/naturalcrit/codeEditor/codeEditor.jsx b/shared/naturalcrit/codeEditor/codeEditor.jsx index e0a2220b4..b6ea07cf1 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.jsx +++ b/shared/naturalcrit/codeEditor/codeEditor.jsx @@ -112,6 +112,10 @@ const CodeEditor = createClass({ 'Shift-Tab' : this.dedent, 'Ctrl-B' : this.makeBold, 'Cmd-B' : this.makeBold, + 'Ctrl-6' : this.makeSuper, + 'Cmd-6' : this.makeSuper, + 'Ctrl-7' : this.makeSub, + 'Cmd-7' : this.makeSub, 'Ctrl-I' : this.makeItalic, 'Cmd-I' : this.makeItalic, 'Ctrl-U' : this.makeUnderline, @@ -219,6 +223,25 @@ const CodeEditor = createClass({ } }, + makeSuper : function() { + const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '^^' && selection.slice(-2) === '^^'; + this.codeMirror.replaceSelection(t ? selection.slice(2, -2) : `^^${selection}^^`, 'around'); + if(selection.length === 0){ + const cursor = this.codeMirror.getCursor(); + this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 2 }); + } + }, + + makeSub : function() { + const selection = this.codeMirror.getSelection(), t = selection.slice(0, 3) === '^^^' && selection.slice(-3) === '^^^'; + this.codeMirror.replaceSelection(t ? selection.slice(3, -3) : `^^^${selection}^^^`, 'around'); + if(selection.length === 0){ + const cursor = this.codeMirror.getCursor(); + this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 3 }); + } + }, + + makeNbsp : function() { this.codeMirror.replaceSelection(' ', 'end'); }, diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index ae30e4922..d032a37df 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -206,47 +206,38 @@ const mustacheInjectBlock = { } }; -const superScripts = { - name : 'superScriptsInjectInline', +const superSubScripts = { + name : 'superSubScripts', level : 'inline', start(src) { return src.match(/.*\^\^(.+)\^\^/)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const inlineRegex = /.*\^\^(.+)\^\^/y; - const match = inlineRegex.exec(src); - if(match) { - const tags = ` ${processStyleTags(match[1])}`; - return { - type : 'text', // Should match "name" above - raw : match[0], // Text to consume from the source - text : '', - tags - }; + const superRegex = /.*\^\^(.+)\^\^/y; + const subRegex = /.*\^\^\^(.+)\^\^\^/y; + let isSuper = false; + let match = subRegex.exec(src); + if(!match){ + match = superRegex.exec(src); + if(match) { + isSuper = true; + } + } else { + console.log(src); } - }, - renderer(token) { - return `${token.tags}`; - } -}; -const subScripts = { - name : 'subScriptsInjectInline', - level : 'inline', - start(src) { return src.match(/.*\^\^\^(.+)\^\^\^/)?.index; }, // Hint to Marked.js to stop and check for a match - tokenizer(src, tokens) { - const inlineRegex = /.*\^\^\^(.+)\^\^\^/y; - const match = inlineRegex.exec(src); - if(match) { - const tags = ` ${processStyleTags(match[1])}`; + if(match?.length) { + const tags = this.lexer.inlineTokens(match[1]); return { - type : 'text', // Should match "name" above - raw : match[0], // Text to consume from the source - text : '', + type : 'superSubScripts', // Should match "name" above + raw : match[0], // Text to consume from the source + text : src, + super : isSuper, tags }; } }, renderer(token) { - return `${token.tags}`; + const tag = token.super ? 'sup' : 'sub'; + return `<${tag}>${this.parser.parseInline(token.tags)}`; } }; @@ -282,7 +273,7 @@ const definitionLists = { } }; -Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, subScripts, superScripts] }); +Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] }); Marked.use(mustacheInjectBlock); Marked.use({ renderer: renderer, mangle: false }); Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite()); From d43ea46e408930f176dbe2cf7e0310601fa07c96 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Wed, 8 Nov 2023 00:54:43 -0600 Subject: [PATCH 03/10] Add subscript and subscript markdown tokens Uses ^^ for superscript and ^^^ subscript as wrappers in the same pattern as italics and bold ( * and **, respectively) Adds editor hot-keys and sytax highlighting. (CTRL-6/CTRL-7) Exact values may not be ideal. Short of the suggestted overloading of ~, I didn't see a better option for the delimiter. --- shared/naturalcrit/markdown.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index d032a37df..62e826e67 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -211,8 +211,8 @@ const superSubScripts = { level : 'inline', start(src) { return src.match(/.*\^\^(.+)\^\^/)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const superRegex = /.*\^\^(.+)\^\^/y; - const subRegex = /.*\^\^\^(.+)\^\^\^/y; + const superRegex = /\^\^(.+)\^\^/y; + const subRegex = /\^\^\^(.+)\^\^\^/y; let isSuper = false; let match = subRegex.exec(src); if(!match){ @@ -220,10 +220,7 @@ const superSubScripts = { if(match) { isSuper = true; } - } else { - console.log(src); } - if(match?.length) { const tags = this.lexer.inlineTokens(match[1]); return { From 7b9a23670d1dcff8d8c54d4fcc862261adf7986f Mon Sep 17 00:00:00 2001 From: David Bolack Date: Thu, 9 Nov 2023 21:31:08 -0600 Subject: [PATCH 04/10] Update Regex based on PR siuggestions. This should match more economically and in line with marked recomendations as well as not allow whitespace ( \s ) at the beginning or end of a sub or superscript mark. Additionally, a failure in having mixed sub and supers on the same line was corrected. --- shared/naturalcrit/markdown.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 62e826e67..26bc810e4 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -209,10 +209,10 @@ const mustacheInjectBlock = { const superSubScripts = { name : 'superSubScripts', level : 'inline', - start(src) { return src.match(/.*\^\^(.+)\^\^/)?.index; }, // Hint to Marked.js to stop and check for a match + start(src) { return src.match(/\^\^[^\s].+[^\s]\^\^/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const superRegex = /\^\^(.+)\^\^/y; - const subRegex = /\^\^\^(.+)\^\^\^/y; + const superRegex = /^\^\^([^\s\^][^\^]*[^\s\^])\^\^/m; + const subRegex = /^\^\^\^([^\s\^][^\^]*[^\s\^])\^\^\^/m; let isSuper = false; let match = subRegex.exec(src); if(!match){ From c6d8bbae1601fb8c880b4568c2bbe3d2dfe50187 Mon Sep 17 00:00:00 2001 From: Louis David Bolack Date: Fri, 10 Nov 2023 23:09:49 -0600 Subject: [PATCH 05/10] Update shared/naturalcrit/markdown.js Co-authored-by: Trevor Buckner --- shared/naturalcrit/markdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 26bc810e4..c7b389b5a 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -211,7 +211,7 @@ const superSubScripts = { level : 'inline', start(src) { return src.match(/\^\^[^\s].+[^\s]\^\^/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const superRegex = /^\^\^([^\s\^][^\^]*[^\s\^])\^\^/m; + const superRegex = /^\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/m; const subRegex = /^\^\^\^([^\s\^][^\^]*[^\s\^])\^\^\^/m; let isSuper = false; let match = subRegex.exec(src); From 8d94e5fbe01c0453f9e4264df5c67743ea8923b7 Mon Sep 17 00:00:00 2001 From: Louis David Bolack Date: Fri, 10 Nov 2023 23:10:01 -0600 Subject: [PATCH 06/10] Update shared/naturalcrit/markdown.js Co-authored-by: Trevor Buckner --- shared/naturalcrit/markdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index c7b389b5a..8cafe1ca5 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -209,7 +209,7 @@ const mustacheInjectBlock = { const superSubScripts = { name : 'superSubScripts', level : 'inline', - start(src) { return src.match(/\^\^[^\s].+[^\s]\^\^/m)?.index; }, // Hint to Marked.js to stop and check for a match + start(src) { return src.match(/\^\^/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { const superRegex = /^\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/m; const subRegex = /^\^\^\^([^\s\^][^\^]*[^\s\^])\^\^\^/m; From ddef21cd7ee216501656cd1f17d7f1b00eb0160c Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Mon, 4 Dec 2023 21:54:44 -0500 Subject: [PATCH 07/10] Give editor highlighting sub/superscript alignment --- client/homebrew/editor/editor.less | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 1f8b6e718..b165f91db 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -44,12 +44,16 @@ color : green; } .superscript:not(.cm-comment) { - font-weight : bold; - color : goldenrod; + font-weight : bold; + color : goldenrod; + vertical-align : super; + font-size : 0.9em; } .subscript:not(.cm-comment) { - font-weight : bold; - color : rgb(123, 123, 15); + font-weight : bold; + color : rgb(123, 123, 15); + vertical-align : sub; + font-size : 0.9em; } } From 6bf51cd94aa7cb44dc07b6960f6a60c718b87810 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Mon, 4 Dec 2023 21:56:08 -0500 Subject: [PATCH 08/10] Allow both sub and super highlighting on same line --- client/homebrew/editor/editor.jsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index c4df7a89c..637394072 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -161,20 +161,20 @@ const Editor = createClass({ } // Superscript - if(line.includes('^^') && !line.includes('^^^')) { - const regex = /.*\^\^(.+)\^\^/y; + if(line.includes('\^')) { + const regex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/g; let match; while ((match = regex.exec(line)) != null) { - codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 2 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 2 }, { className: 'superscript' }); + codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 1 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 1 }, { className: 'superscript' }); } } // Subscript - if(line.includes('^^^')) { - const regex = /.*\^\^\^(.+)\^\^\^/y; + if(line.includes('^^')) { + const regex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/g; let match; while ((match = regex.exec(line)) != null) { - codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 3 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 3 }, { className: 'subscript' }); + codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 2 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 2 }, { className: 'subscript' }); } } From 38fa428fde303edc018d4c5417f8943c3fb3b997 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Mon, 4 Dec 2023 22:11:05 -0500 Subject: [PATCH 09/10] Change to 1 and 2 ^'s . Slight cleanup --- shared/naturalcrit/markdown.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 8cafe1ca5..7185cab8e 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -207,34 +207,30 @@ const mustacheInjectBlock = { }; const superSubScripts = { - name : 'superSubScripts', + name : 'superSubScript', level : 'inline', - start(src) { return src.match(/\^\^/m)?.index; }, // Hint to Marked.js to stop and check for a match + start(src) { return src.match(/\^/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const superRegex = /^\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/m; - const subRegex = /^\^\^\^([^\s\^][^\^]*[^\s\^])\^\^\^/m; + const superRegex = /^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/m; + const subRegex = /^\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/m; let isSuper = false; let match = subRegex.exec(src); if(!match){ match = superRegex.exec(src); - if(match) { + if(match) isSuper = true; - } } if(match?.length) { - const tags = this.lexer.inlineTokens(match[1]); return { - type : 'superSubScripts', // Should match "name" above - raw : match[0], // Text to consume from the source - text : src, - super : isSuper, - tags + type : 'superSubScript', // Should match "name" above + raw : match[0], // Text to consume from the source + tag : isSuper ? 'sup' : 'sub', + tokens : this.lexer.inlineTokens(match[1]) }; } }, renderer(token) { - const tag = token.super ? 'sup' : 'sub'; - return `<${tag}>${this.parser.parseInline(token.tags)}`; + return `<${token.tag}>${this.parser.parseInline(token.tokens)}`; } }; From 2dc874daba433f3e9f5683221ef898d6c0592911 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Mon, 4 Dec 2023 22:21:58 -0500 Subject: [PATCH 10/10] Adjust hotkeys to match other changes --- shared/naturalcrit/codeEditor/codeEditor.jsx | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/shared/naturalcrit/codeEditor/codeEditor.jsx b/shared/naturalcrit/codeEditor/codeEditor.jsx index b6ea07cf1..fcfee1dbf 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.jsx +++ b/shared/naturalcrit/codeEditor/codeEditor.jsx @@ -112,10 +112,10 @@ const CodeEditor = createClass({ 'Shift-Tab' : this.dedent, 'Ctrl-B' : this.makeBold, 'Cmd-B' : this.makeBold, - 'Ctrl-6' : this.makeSuper, - 'Cmd-6' : this.makeSuper, - 'Ctrl-7' : this.makeSub, - 'Cmd-7' : this.makeSub, + 'Shift-Ctrl-=' : this.makeSuper, + 'Shift-Cmd-=' : this.makeSuper, + 'Ctrl-=' : this.makeSub, + 'Cmd-=' : this.makeSub, 'Ctrl-I' : this.makeItalic, 'Cmd-I' : this.makeItalic, 'Ctrl-U' : this.makeUnderline, @@ -224,6 +224,15 @@ const CodeEditor = createClass({ }, makeSuper : function() { + const selection = this.codeMirror.getSelection(), t = selection.slice(0, 1) === '^' && selection.slice(-1) === '^'; + this.codeMirror.replaceSelection(t ? selection.slice(1, -1) : `^${selection}^`, 'around'); + if(selection.length === 0){ + const cursor = this.codeMirror.getCursor(); + this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 1 }); + } + }, + + makeSub : function() { const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '^^' && selection.slice(-2) === '^^'; this.codeMirror.replaceSelection(t ? selection.slice(2, -2) : `^^${selection}^^`, 'around'); if(selection.length === 0){ @@ -232,15 +241,6 @@ const CodeEditor = createClass({ } }, - makeSub : function() { - const selection = this.codeMirror.getSelection(), t = selection.slice(0, 3) === '^^^' && selection.slice(-3) === '^^^'; - this.codeMirror.replaceSelection(t ? selection.slice(3, -3) : `^^^${selection}^^^`, 'around'); - if(selection.length === 0){ - const cursor = this.codeMirror.getCursor(); - this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 3 }); - } - }, - makeNbsp : function() { this.codeMirror.replaceSelection(' ', 'end');