From ad8ed0d9f60fc1d1060d4f027593ba4e4b153afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4usler?= Date: Tue, 26 May 2020 12:07:13 +0200 Subject: [PATCH] Add GitLab math block support --- src/main/preferences/schema.json | 5 ++ src/muya/lib/config/index.js | 3 +- src/muya/lib/contentState/codeBlockCtrl.js | 3 ++ src/muya/lib/contentState/containerCtrl.js | 49 +++++++++++++++++-- src/muya/lib/contentState/copyCutCtrl.js | 12 +++-- src/muya/lib/contentState/paragraphCtrl.js | 8 ++- src/muya/lib/index.js | 4 +- src/muya/lib/parser/marked/blockRules.js | 1 + src/muya/lib/parser/marked/lexer.js | 24 ++++++++- src/muya/lib/parser/marked/options.js | 3 +- src/muya/lib/prism/languages.json | 3 ++ src/muya/lib/utils/exportHtml.js | 1 + src/muya/lib/utils/exportMarkdown.js | 15 ++++-- src/muya/lib/utils/importMarkdown.js | 22 ++++++--- .../components/editorWithTabs/editor.vue | 10 ++++ .../prefComponents/markdown/index.vue | 8 +++ src/renderer/store/preferences.js | 1 + static/preference.json | 1 + 18 files changed, 146 insertions(+), 27 deletions(-) diff --git a/src/main/preferences/schema.json b/src/main/preferences/schema.json index b3b1053d0..dd5d3bbbf 100644 --- a/src/main/preferences/schema.json +++ b/src/main/preferences/schema.json @@ -289,6 +289,11 @@ "type": "boolean", "default": false }, + "isGitlabCompatibilityEnabled": { + "description": "Markdown-Enable GitLab compatibility mode.", + "type": "boolean", + "default": false + }, "sequenceTheme": { "description": "Markdown--Sequence diagram theme", "enum": [ diff --git a/src/muya/lib/config/index.js b/src/muya/lib/config/index.js index 3c409ea13..c5405eb7a 100644 --- a/src/muya/lib/config/index.js +++ b/src/muya/lib/config/index.js @@ -280,7 +280,8 @@ export const MUYA_DEFAULT_OPTION = { // Markdown extensions superSubScript: false, - footnote: false + footnote: false, + isGitlabCompatibilityEnabled: false } // export const DIAGRAM_TEMPLATE = { diff --git a/src/muya/lib/contentState/codeBlockCtrl.js b/src/muya/lib/contentState/codeBlockCtrl.js index 3cd6ae1e1..078dbc2b0 100644 --- a/src/muya/lib/contentState/codeBlockCtrl.js +++ b/src/muya/lib/contentState/codeBlockCtrl.js @@ -35,6 +35,9 @@ const codeBlockCtrl = ContentState => { ContentState.prototype.selectLanguage = function (paragraph, lang) { const block = this.getBlock(paragraph.id) + if (lang === 'math' && this.isGitlabCompatibilityEnabled && this.updateMathBlock(block)) { + return + } this.updateCodeLanguage(block, lang) } diff --git a/src/muya/lib/contentState/containerCtrl.js b/src/muya/lib/contentState/containerCtrl.js index eebf0bffe..0b0973b37 100644 --- a/src/muya/lib/contentState/containerCtrl.js +++ b/src/muya/lib/contentState/containerCtrl.js @@ -8,11 +8,18 @@ const FUNCTION_TYPE_LANG = { } const containerCtrl = ContentState => { - ContentState.prototype.createContainerBlock = function (functionType, value = '') { + ContentState.prototype.createContainerBlock = function (functionType, value = '', style = undefined) { const figureBlock = this.createBlock('figure', { functionType }) + if (functionType === 'multiplemath') { + if (style === undefined) { + figureBlock.mathStyle = this.isGitlabCompatibilityEnabled ? 'gitlab' : '' + } + figureBlock.mathStyle = style + } + const { preBlock, preview } = this.createPreAndPreview(functionType, value) this.appendChild(figureBlock, preBlock) this.appendChild(figureBlock, preview) @@ -56,11 +63,18 @@ const containerCtrl = ContentState => { return { preBlock, preview } } - ContentState.prototype.initContainerBlock = function (functionType, block) { // p block + ContentState.prototype.initContainerBlock = function (functionType, block, style = undefined) { // p block block.type = 'figure' block.functionType = functionType block.children = [] + if (functionType === 'multiplemath') { + if (style === undefined) { + block.mathStyle = this.isGitlabCompatibilityEnabled ? 'gitlab' : '' + } + block.mathStyle = style + } + const { preBlock, preview } = this.createPreAndPreview(functionType) this.appendChild(block, preBlock) @@ -84,11 +98,36 @@ const containerCtrl = ContentState => { } ContentState.prototype.updateMathBlock = function (block) { + const functionType = 'multiplemath' const { type } = block - if (type !== 'p') return false + + // TODO(GitLab): Allow "functionType" 'languageInput' to convert an existing + // code block into math block. + if (type === 'span' && block.functionType === 'paragraphContent') { + const isMathBlock = !!block.text.match(/^`{3,}math\s*/) + if (isMathBlock) { + const result = this.initContainerBlock(functionType, block, 'gitlab') + if (result) { + // Set cursor at the first line + const { key } = result + const offset = 0 + this.cursor = { + start: { key, offset }, + end: { key, offset } + } + + // Force render + this.partialRender() + return result + } + } + return false + } else if (type !== 'p') { + return false + } + const { text } = block.children[0] - const functionType = 'multiplemath' - return text.trim() === '$$' ? this.initContainerBlock(functionType, block) : false + return text.trim() === '$$' ? this.initContainerBlock(functionType, block, '') : false } } diff --git a/src/muya/lib/contentState/copyCutCtrl.js b/src/muya/lib/contentState/copyCutCtrl.js index 3769e93ab..a467c4af8 100644 --- a/src/muya/lib/contentState/copyCutCtrl.js +++ b/src/muya/lib/contentState/copyCutCtrl.js @@ -244,8 +244,8 @@ const copyCutCtrl = ContentState => { const table = this.createTableInFigure({ rows: row, columns: column }, tableContents) this.appendChild(figureBlock, table) - const listIndentation = this.listIndentation - const markdown = new ExportMarkdown([figureBlock], listIndentation).generate() + const { isGitlabCompatibilityEnabled, listIndentation } = this + const markdown = new ExportMarkdown([figureBlock], listIndentation, isGitlabCompatibilityEnabled).generate() event.clipboardData.setData('text/html', '') event.clipboardData.setData('text/plain', markdown) @@ -281,7 +281,9 @@ const copyCutCtrl = ContentState => { case 'copyAsHtml': { event.clipboardData.setData('text/html', '') event.clipboardData.setData('text/plain', getSanitizeHtml(text, { - superSubScript: this.muya.options.superSubScript + superSubScript: this.muya.options.superSubScript, + footnote: this.muya.options.footnote, + isGitlabCompatibilityEnabled: this.muya.options.isGitlabCompatibilityEnabled })) break } @@ -290,8 +292,8 @@ const copyCutCtrl = ContentState => { const block = typeof copyInfo === 'string' ? this.getBlock(copyInfo) : copyInfo if (!block) return const anchor = this.getAnchor(block) - const listIndentation = this.listIndentation - const markdown = new ExportMarkdown([anchor], listIndentation).generate() + const { isGitlabCompatibilityEnabled, listIndentation } = this + const markdown = new ExportMarkdown([anchor], listIndentation, isGitlabCompatibilityEnabled).generate() event.clipboardData.setData('text/html', '') event.clipboardData.setData('text/plain', markdown) break diff --git a/src/muya/lib/contentState/paragraphCtrl.js b/src/muya/lib/contentState/paragraphCtrl.js index dcdb9dd36..099df5c7f 100644 --- a/src/muya/lib/contentState/paragraphCtrl.js +++ b/src/muya/lib/contentState/paragraphCtrl.js @@ -321,8 +321,12 @@ const paragraphCtrl = ContentState => { lang }) - const listIndentation = this.listIndentation - const markdown = new ExportMarkdown(children.slice(startIndex, endIndex + 1), listIndentation).generate() + const { isGitlabCompatibilityEnabled, listIndentation } = this + const markdown = new ExportMarkdown( + children.slice(startIndex, endIndex + 1), + listIndentation, + isGitlabCompatibilityEnabled + ).generate() const codeContent = this.createBlock('span', { text: markdown, lang, diff --git a/src/muya/lib/index.js b/src/muya/lib/index.js index 57e4d2e13..5fb130edd 100644 --- a/src/muya/lib/index.js +++ b/src/muya/lib/index.js @@ -127,8 +127,8 @@ class Muya { getMarkdown () { const blocks = this.contentState.getBlocks() - const listIndentation = this.contentState.listIndentation - return new ExportMarkdown(blocks, listIndentation).generate() + const { isGitlabCompatibilityEnabled, listIndentation } = this.contentState + return new ExportMarkdown(blocks, listIndentation, isGitlabCompatibilityEnabled).generate() } getHistory () { diff --git a/src/muya/lib/parser/marked/blockRules.js b/src/muya/lib/parser/marked/blockRules.js index 99774b2b1..a9a263338 100644 --- a/src/muya/lib/parser/marked/blockRules.js +++ b/src/muya/lib/parser/marked/blockRules.js @@ -36,6 +36,7 @@ export const block = { // extra frontmatter: /^(?:(?:---\n([\s\S]+?)---)|(?:\+\+\+\n([\s\S]+?)\+\+\+)|(?:;;;\n([\s\S]+?);;;)|(?:\{\n([\s\S]+?)\}))(?:\n{2,}|\n{1,2}$)/, multiplemath: /^\$\$\n([\s\S]+?)\n\$\$(?:\n+|$)/, + multiplemathGitlab: /^ {0,3}(`{3,})math\n(?:(|[\s\S]*?)\n)(?: {0,3}\1`* *(?:\n+|$)|$)/, // Math inside a code block (GitLab display math) footnote: /^\[\^([^\^\[\]\s]+?)\]:[\s\S]+?(?=\n *\n {0,3}[^ ]+|$)/ } diff --git a/src/muya/lib/parser/marked/lexer.js b/src/muya/lib/parser/marked/lexer.js index 31965b279..aaac02155 100644 --- a/src/muya/lib/parser/marked/lexer.js +++ b/src/muya/lib/parser/marked/lexer.js @@ -64,7 +64,12 @@ Lexer.prototype.lex = function (src) { */ Lexer.prototype.token = function (src, top) { - const { frontMatter, math, footnote } = this.options + const { + footnote, + frontMatter, + isGitlabCompatibilityEnabled, + math + } = this.options src = src.replace(/^ +$/gm, '') let loose @@ -149,10 +154,25 @@ Lexer.prototype.token = function (src, top) { src = src.substring(cap[0].length) this.tokens.push({ type: 'multiplemath', - text: cap[1] + text: cap[1], + mathStyle: '' }) continue } + + // match GitLab display math blocks (```math) + if (isGitlabCompatibilityEnabled) { + cap = this.rules.multiplemathGitlab.exec(src) + if (cap) { + src = src.substring(cap[0].length) + this.tokens.push({ + type: 'multiplemath', + text: cap[2] || '', + mathStyle: 'gitlab' + }) + continue + } + } } if (footnote) { diff --git a/src/muya/lib/parser/marked/options.js b/src/muya/lib/parser/marked/options.js index 3d3a81586..4c1399cd6 100644 --- a/src/muya/lib/parser/marked/options.js +++ b/src/muya/lib/parser/marked/options.js @@ -29,5 +29,6 @@ export default { math: true, frontMatter: true, superSubScript: false, - footnote: false + footnote: false, + isGitlabCompatibilityEnabled: false } diff --git a/src/muya/lib/prism/languages.json b/src/muya/lib/prism/languages.json index fabb18d05..c88bd7f76 100644 --- a/src/muya/lib/prism/languages.json +++ b/src/muya/lib/prism/languages.json @@ -564,6 +564,9 @@ }, "latex": { "title": "LaTeX", + "alias": [ + "math" + ], "ext": [ "text", "ltx", diff --git a/src/muya/lib/utils/exportHtml.js b/src/muya/lib/utils/exportHtml.js index 230f3d00e..01a259384 100644 --- a/src/muya/lib/utils/exportHtml.js +++ b/src/muya/lib/utils/exportHtml.js @@ -114,6 +114,7 @@ class ExportHtml { let html = marked(this.markdown, { superSubScript: this.muya ? this.muya.options.superSubScript : false, footnote: this.muya ? this.muya.options.footnote : false, + isGitlabCompatibilityEnabled: this.muya ? this.muya.options.isGitlabCompatibilityEnabled : false, highlight (code, lang) { // Language may be undefined (GH#591) if (!lang) { diff --git a/src/muya/lib/utils/exportMarkdown.js b/src/muya/lib/utils/exportMarkdown.js index da4453985..f6e63b693 100644 --- a/src/muya/lib/utils/exportMarkdown.js +++ b/src/muya/lib/utils/exportMarkdown.js @@ -10,11 +10,12 @@ */ class ExportMarkdown { - constructor (blocks, listIndentation = 1) { + constructor (blocks, listIndentation = 1, isGitlabCompatibilityEnabled = false) { this.blocks = blocks this.listType = [] // 'ul' or 'ol' // helper to translate the first tight item in a nested list this.isLooseParentList = true + this.isGitlabCompatibilityEnabled = !!isGitlabCompatibilityEnabled // set and validate settings this.listIndentation = 'number' @@ -229,12 +230,20 @@ class ExportMarkdown { } normalizeMultipleMath (block, /* figure */ indent) { + const { isGitlabCompatibilityEnabled } = this + let startToken = '$$' + let endToken = '$$' + if (isGitlabCompatibilityEnabled && block.mathStyle === 'gitlab') { + startToken = '```math' + endToken = '```' + } + const result = [] - result.push(`${indent}$$\n`) + result.push(`${indent}${startToken}\n`) for (const line of block.children[0].children[0].children) { result.push(`${indent}${line.text}\n`) } - result.push(`${indent}$$\n`) + result.push(`${indent}${endToken}\n`) return result.join('') } diff --git a/src/muya/lib/utils/importMarkdown.js b/src/muya/lib/utils/importMarkdown.js index bb632b756..254be1991 100644 --- a/src/muya/lib/utils/importMarkdown.js +++ b/src/muya/lib/utils/importMarkdown.js @@ -77,9 +77,19 @@ const importRegister = ContentState => { nextSibling: null, children: [] } - - const { trimUnnecessaryCodeBlockEmptyLines, footnote } = this.muya.options - const tokens = new Lexer({ disableInline: true, footnote }).lex(markdown) + const { + footnote, + isGitlabCompatibilityEnabled, + superSubScript, + trimUnnecessaryCodeBlockEmptyLines + } = this.muya.options + + const tokens = new Lexer({ + disableInline: true, + footnote, + isGitlabCompatibilityEnabled, + superSubScript + }).lex(markdown) let token let block @@ -152,7 +162,7 @@ const importRegister = ContentState => { case 'multiplemath': { value = token.text - block = this.createContainerBlock(token.type, value) + block = this.createContainerBlock(token.type, value, token.mathStyle) this.appendChild(parentList[0], block) break } @@ -457,8 +467,8 @@ const importRegister = ContentState => { focusBlock.text = focusText.substring(0, focus.offset) + CURSOR_FOCUS_DNA + focusText.substring(focus.offset) } - const listIndentation = this.listIndentation - const markdown = new ExportMarkdown(blocks, listIndentation).generate() + const { isGitlabCompatibilityEnabled, listIndentation } = this + const markdown = new ExportMarkdown(blocks, listIndentation, isGitlabCompatibilityEnabled).generate() const cursor = markdown.split('\n').reduce((acc, line, index) => { const ach = line.indexOf(CURSOR_ANCHOR_DNA) const fch = line.indexOf(CURSOR_FOCUS_DNA) diff --git a/src/renderer/components/editorWithTabs/editor.vue b/src/renderer/components/editorWithTabs/editor.vue index 68d28ae08..18d17fd84 100644 --- a/src/renderer/components/editorWithTabs/editor.vue +++ b/src/renderer/components/editorWithTabs/editor.vue @@ -140,6 +140,7 @@ export default { frontmatterType: state => state.preferences.frontmatterType, superSubScript: state => state.preferences.superSubScript, footnote: state => state.preferences.footnote, + isGitlabCompatibilityEnabled: state => state.preferences.isGitlabCompatibilityEnabled, lineHeight: state => state.preferences.lineHeight, fontSize: state => state.preferences.fontSize, codeFontSize: state => state.preferences.codeFontSize, @@ -284,6 +285,13 @@ export default { } }, + isGitlabCompatibilityEnabled: function (value, oldValue) { + const { editor } = this + if (value !== oldValue && editor) { + editor.setOptions({ isGitlabCompatibilityEnabled: value }, true) + } + }, + hideQuickInsertHint: function (value, oldValue) { const { editor } = this if (value !== oldValue && editor) { @@ -517,6 +525,7 @@ export default { frontmatterType, superSubScript, footnote, + isGitlabCompatibilityEnabled, hideQuickInsertHint, editorLineWidth, theme, @@ -564,6 +573,7 @@ export default { frontmatterType, superSubScript, footnote, + isGitlabCompatibilityEnabled, hideQuickInsertHint, hideLinkPopup, autoCheck, diff --git a/src/renderer/prefComponents/markdown/index.vue b/src/renderer/prefComponents/markdown/index.vue index 572bfbe13..cd810aefb 100644 --- a/src/renderer/prefComponents/markdown/index.vue +++ b/src/renderer/prefComponents/markdown/index.vue @@ -61,6 +61,13 @@ more="https://pandoc.org/MANUAL.html#footnotes" > +
Compatibility
+ +
Diagram theme
state.preferences.frontmatterType, superSubScript: state => state.preferences.superSubScript, footnote: state => state.preferences.footnote, + isGitlabCompatibilityEnabled: state => state.preferences.isGitlabCompatibilityEnabled, sequenceTheme: state => state.preferences.sequenceTheme }) }, diff --git a/src/renderer/store/preferences.js b/src/renderer/store/preferences.js index c2b23e17f..2c0e13b6c 100644 --- a/src/renderer/store/preferences.js +++ b/src/renderer/store/preferences.js @@ -47,6 +47,7 @@ const state = { frontmatterType: '-', superSubScript: false, footnote: false, + isGitlabCompatibilityEnabled: false, sequenceTheme: 'hand', theme: 'light', diff --git a/static/preference.json b/static/preference.json index 019de8912..5f687534e 100644 --- a/static/preference.json +++ b/static/preference.json @@ -43,6 +43,7 @@ "frontmatterType": "-", "superSubScript": false, "footnote": false, + "isGitlabCompatibilityEnabled": false, "sequenceTheme": "hand", "theme": "light",