Add GitLab math block support (#2119)

This commit is contained in:
Felix Häusler 2020-06-04 01:31:21 +02:00 committed by GitHub
parent 79c7200406
commit 5bb7856cb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 146 additions and 27 deletions

View File

@ -289,6 +289,11 @@
"type": "boolean", "type": "boolean",
"default": false "default": false
}, },
"isGitlabCompatibilityEnabled": {
"description": "Markdown-Enable GitLab compatibility mode.",
"type": "boolean",
"default": false
},
"sequenceTheme": { "sequenceTheme": {
"description": "Markdown--Sequence diagram theme", "description": "Markdown--Sequence diagram theme",
"enum": [ "enum": [

View File

@ -282,7 +282,8 @@ export const MUYA_DEFAULT_OPTION = {
// Markdown extensions // Markdown extensions
superSubScript: false, superSubScript: false,
footnote: false footnote: false,
isGitlabCompatibilityEnabled: false
} }
// export const DIAGRAM_TEMPLATE = { // export const DIAGRAM_TEMPLATE = {

View File

@ -35,6 +35,9 @@ const codeBlockCtrl = ContentState => {
ContentState.prototype.selectLanguage = function (paragraph, lang) { ContentState.prototype.selectLanguage = function (paragraph, lang) {
const block = this.getBlock(paragraph.id) const block = this.getBlock(paragraph.id)
if (lang === 'math' && this.isGitlabCompatibilityEnabled && this.updateMathBlock(block)) {
return
}
this.updateCodeLanguage(block, lang) this.updateCodeLanguage(block, lang)
} }

View File

@ -8,11 +8,18 @@ const FUNCTION_TYPE_LANG = {
} }
const containerCtrl = ContentState => { const containerCtrl = ContentState => {
ContentState.prototype.createContainerBlock = function (functionType, value = '') { ContentState.prototype.createContainerBlock = function (functionType, value = '', style = undefined) {
const figureBlock = this.createBlock('figure', { const figureBlock = this.createBlock('figure', {
functionType functionType
}) })
if (functionType === 'multiplemath') {
if (style === undefined) {
figureBlock.mathStyle = this.isGitlabCompatibilityEnabled ? 'gitlab' : ''
}
figureBlock.mathStyle = style
}
const { preBlock, preview } = this.createPreAndPreview(functionType, value) const { preBlock, preview } = this.createPreAndPreview(functionType, value)
this.appendChild(figureBlock, preBlock) this.appendChild(figureBlock, preBlock)
this.appendChild(figureBlock, preview) this.appendChild(figureBlock, preview)
@ -56,11 +63,18 @@ const containerCtrl = ContentState => {
return { preBlock, preview } 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.type = 'figure'
block.functionType = functionType block.functionType = functionType
block.children = [] block.children = []
if (functionType === 'multiplemath') {
if (style === undefined) {
block.mathStyle = this.isGitlabCompatibilityEnabled ? 'gitlab' : ''
}
block.mathStyle = style
}
const { preBlock, preview } = this.createPreAndPreview(functionType) const { preBlock, preview } = this.createPreAndPreview(functionType)
this.appendChild(block, preBlock) this.appendChild(block, preBlock)
@ -84,11 +98,36 @@ const containerCtrl = ContentState => {
} }
ContentState.prototype.updateMathBlock = function (block) { ContentState.prototype.updateMathBlock = function (block) {
const { type } = block
if (type !== 'p') return false
const { text } = block.children[0]
const functionType = 'multiplemath' const functionType = 'multiplemath'
return text.trim() === '$$' ? this.initContainerBlock(functionType, block) : false const { type } = block
// 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]
return text.trim() === '$$' ? this.initContainerBlock(functionType, block, '') : false
} }
} }

View File

@ -244,8 +244,8 @@ const copyCutCtrl = ContentState => {
const table = this.createTableInFigure({ rows: row, columns: column }, tableContents) const table = this.createTableInFigure({ rows: row, columns: column }, tableContents)
this.appendChild(figureBlock, table) this.appendChild(figureBlock, table)
const listIndentation = this.listIndentation const { isGitlabCompatibilityEnabled, listIndentation } = this
const markdown = new ExportMarkdown([figureBlock], listIndentation).generate() const markdown = new ExportMarkdown([figureBlock], listIndentation, isGitlabCompatibilityEnabled).generate()
event.clipboardData.setData('text/html', '') event.clipboardData.setData('text/html', '')
event.clipboardData.setData('text/plain', markdown) event.clipboardData.setData('text/plain', markdown)
@ -281,7 +281,9 @@ const copyCutCtrl = ContentState => {
case 'copyAsHtml': { case 'copyAsHtml': {
event.clipboardData.setData('text/html', '') event.clipboardData.setData('text/html', '')
event.clipboardData.setData('text/plain', getSanitizeHtml(text, { 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 break
} }
@ -290,8 +292,8 @@ const copyCutCtrl = ContentState => {
const block = typeof copyInfo === 'string' ? this.getBlock(copyInfo) : copyInfo const block = typeof copyInfo === 'string' ? this.getBlock(copyInfo) : copyInfo
if (!block) return if (!block) return
const anchor = this.getAnchor(block) const anchor = this.getAnchor(block)
const listIndentation = this.listIndentation const { isGitlabCompatibilityEnabled, listIndentation } = this
const markdown = new ExportMarkdown([anchor], listIndentation).generate() const markdown = new ExportMarkdown([anchor], listIndentation, isGitlabCompatibilityEnabled).generate()
event.clipboardData.setData('text/html', '') event.clipboardData.setData('text/html', '')
event.clipboardData.setData('text/plain', markdown) event.clipboardData.setData('text/plain', markdown)
break break

View File

@ -321,8 +321,12 @@ const paragraphCtrl = ContentState => {
lang lang
}) })
const listIndentation = this.listIndentation const { isGitlabCompatibilityEnabled, listIndentation } = this
const markdown = new ExportMarkdown(children.slice(startIndex, endIndex + 1), listIndentation).generate() const markdown = new ExportMarkdown(
children.slice(startIndex, endIndex + 1),
listIndentation,
isGitlabCompatibilityEnabled
).generate()
const codeContent = this.createBlock('span', { const codeContent = this.createBlock('span', {
text: markdown, text: markdown,
lang, lang,

View File

@ -127,8 +127,8 @@ class Muya {
getMarkdown () { getMarkdown () {
const blocks = this.contentState.getBlocks() const blocks = this.contentState.getBlocks()
const listIndentation = this.contentState.listIndentation const { isGitlabCompatibilityEnabled, listIndentation } = this.contentState
return new ExportMarkdown(blocks, listIndentation).generate() return new ExportMarkdown(blocks, listIndentation, isGitlabCompatibilityEnabled).generate()
} }
getHistory () { getHistory () {

View File

@ -36,6 +36,7 @@ export const block = {
// extra // extra
frontmatter: /^(?:(?:---\n([\s\S]+?)---)|(?:\+\+\+\n([\s\S]+?)\+\+\+)|(?:;;;\n([\s\S]+?);;;)|(?:\{\n([\s\S]+?)\}))(?:\n{2,}|\n{1,2}$)/, frontmatter: /^(?:(?:---\n([\s\S]+?)---)|(?:\+\+\+\n([\s\S]+?)\+\+\+)|(?:;;;\n([\s\S]+?);;;)|(?:\{\n([\s\S]+?)\}))(?:\n{2,}|\n{1,2}$)/,
multiplemath: /^\$\$\n([\s\S]+?)\n\$\$(?:\n+|$)/, 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}[^ ]+|$)/ footnote: /^\[\^([^\^\[\]\s]+?)\]:[\s\S]+?(?=\n *\n {0,3}[^ ]+|$)/
} }

View File

@ -64,7 +64,12 @@ Lexer.prototype.lex = function (src) {
*/ */
Lexer.prototype.token = function (src, top) { Lexer.prototype.token = function (src, top) {
const { frontMatter, math, footnote } = this.options const {
footnote,
frontMatter,
isGitlabCompatibilityEnabled,
math
} = this.options
src = src.replace(/^ +$/gm, '') src = src.replace(/^ +$/gm, '')
let loose let loose
@ -149,10 +154,25 @@ Lexer.prototype.token = function (src, top) {
src = src.substring(cap[0].length) src = src.substring(cap[0].length)
this.tokens.push({ this.tokens.push({
type: 'multiplemath', type: 'multiplemath',
text: cap[1] text: cap[1],
mathStyle: ''
}) })
continue 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
}
}
} }
// footnote // footnote

View File

@ -29,5 +29,6 @@ export default {
math: true, math: true,
frontMatter: true, frontMatter: true,
superSubScript: false, superSubScript: false,
footnote: false footnote: false,
isGitlabCompatibilityEnabled: false
} }

View File

@ -564,6 +564,9 @@
}, },
"latex": { "latex": {
"title": "LaTeX", "title": "LaTeX",
"alias": [
"math"
],
"ext": [ "ext": [
"text", "text",
"ltx", "ltx",

View File

@ -114,6 +114,7 @@ class ExportHtml {
let html = marked(this.markdown, { let html = marked(this.markdown, {
superSubScript: this.muya ? this.muya.options.superSubScript : false, superSubScript: this.muya ? this.muya.options.superSubScript : false,
footnote: this.muya ? this.muya.options.footnote : false, footnote: this.muya ? this.muya.options.footnote : false,
isGitlabCompatibilityEnabled: this.muya ? this.muya.options.isGitlabCompatibilityEnabled : false,
highlight (code, lang) { highlight (code, lang) {
// Language may be undefined (GH#591) // Language may be undefined (GH#591)
if (!lang) { if (!lang) {

View File

@ -10,11 +10,12 @@
*/ */
class ExportMarkdown { class ExportMarkdown {
constructor (blocks, listIndentation = 1) { constructor (blocks, listIndentation = 1, isGitlabCompatibilityEnabled = false) {
this.blocks = blocks this.blocks = blocks
this.listType = [] // 'ul' or 'ol' this.listType = [] // 'ul' or 'ol'
// helper to translate the first tight item in a nested list // helper to translate the first tight item in a nested list
this.isLooseParentList = true this.isLooseParentList = true
this.isGitlabCompatibilityEnabled = !!isGitlabCompatibilityEnabled
// set and validate settings // set and validate settings
this.listIndentation = 'number' this.listIndentation = 'number'
@ -229,12 +230,20 @@ class ExportMarkdown {
} }
normalizeMultipleMath (block, /* figure */ indent) { normalizeMultipleMath (block, /* figure */ indent) {
const { isGitlabCompatibilityEnabled } = this
let startToken = '$$'
let endToken = '$$'
if (isGitlabCompatibilityEnabled && block.mathStyle === 'gitlab') {
startToken = '```math'
endToken = '```'
}
const result = [] const result = []
result.push(`${indent}$$\n`) result.push(`${indent}${startToken}\n`)
for (const line of block.children[0].children[0].children) { for (const line of block.children[0].children[0].children) {
result.push(`${indent}${line.text}\n`) result.push(`${indent}${line.text}\n`)
} }
result.push(`${indent}$$\n`) result.push(`${indent}${endToken}\n`)
return result.join('') return result.join('')
} }

View File

@ -77,9 +77,19 @@ const importRegister = ContentState => {
nextSibling: null, nextSibling: null,
children: [] children: []
} }
const {
footnote,
isGitlabCompatibilityEnabled,
superSubScript,
trimUnnecessaryCodeBlockEmptyLines
} = this.muya.options
const { trimUnnecessaryCodeBlockEmptyLines, footnote } = this.muya.options const tokens = new Lexer({
const tokens = new Lexer({ disableInline: true, footnote }).lex(markdown) disableInline: true,
footnote,
isGitlabCompatibilityEnabled,
superSubScript
}).lex(markdown)
let token let token
let block let block
@ -152,7 +162,7 @@ const importRegister = ContentState => {
case 'multiplemath': { case 'multiplemath': {
value = token.text value = token.text
block = this.createContainerBlock(token.type, value) block = this.createContainerBlock(token.type, value, token.mathStyle)
this.appendChild(parentList[0], block) this.appendChild(parentList[0], block)
break break
} }
@ -457,8 +467,8 @@ const importRegister = ContentState => {
focusBlock.text = focusText.substring(0, focus.offset) + CURSOR_FOCUS_DNA + focusText.substring(focus.offset) focusBlock.text = focusText.substring(0, focus.offset) + CURSOR_FOCUS_DNA + focusText.substring(focus.offset)
} }
const listIndentation = this.listIndentation const { isGitlabCompatibilityEnabled, listIndentation } = this
const markdown = new ExportMarkdown(blocks, listIndentation).generate() const markdown = new ExportMarkdown(blocks, listIndentation, isGitlabCompatibilityEnabled).generate()
const cursor = markdown.split('\n').reduce((acc, line, index) => { const cursor = markdown.split('\n').reduce((acc, line, index) => {
const ach = line.indexOf(CURSOR_ANCHOR_DNA) const ach = line.indexOf(CURSOR_ANCHOR_DNA)
const fch = line.indexOf(CURSOR_FOCUS_DNA) const fch = line.indexOf(CURSOR_FOCUS_DNA)

View File

@ -140,6 +140,7 @@ export default {
frontmatterType: state => state.preferences.frontmatterType, frontmatterType: state => state.preferences.frontmatterType,
superSubScript: state => state.preferences.superSubScript, superSubScript: state => state.preferences.superSubScript,
footnote: state => state.preferences.footnote, footnote: state => state.preferences.footnote,
isGitlabCompatibilityEnabled: state => state.preferences.isGitlabCompatibilityEnabled,
lineHeight: state => state.preferences.lineHeight, lineHeight: state => state.preferences.lineHeight,
fontSize: state => state.preferences.fontSize, fontSize: state => state.preferences.fontSize,
codeFontSize: state => state.preferences.codeFontSize, 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) { hideQuickInsertHint: function (value, oldValue) {
const { editor } = this const { editor } = this
if (value !== oldValue && editor) { if (value !== oldValue && editor) {
@ -517,6 +525,7 @@ export default {
frontmatterType, frontmatterType,
superSubScript, superSubScript,
footnote, footnote,
isGitlabCompatibilityEnabled,
hideQuickInsertHint, hideQuickInsertHint,
editorLineWidth, editorLineWidth,
theme, theme,
@ -564,6 +573,7 @@ export default {
frontmatterType, frontmatterType,
superSubScript, superSubScript,
footnote, footnote,
isGitlabCompatibilityEnabled,
hideQuickInsertHint, hideQuickInsertHint,
hideLinkPopup, hideLinkPopup,
autoCheck, autoCheck,

View File

@ -61,6 +61,13 @@
more="https://pandoc.org/MANUAL.html#footnotes" more="https://pandoc.org/MANUAL.html#footnotes"
></bool> ></bool>
<separator></separator> <separator></separator>
<h5>Compatibility</h5>
<bool
description="Enable GitLab compatibility mode."
:bool="isGitlabCompatibilityEnabled"
:onChange="value => onSelectChange('isGitlabCompatibilityEnabled', value)"
></bool>
<separator></separator>
<h5>Diagram theme</h5> <h5>Diagram theme</h5>
<cus-select <cus-select
description="Sequence diagram theme" description="Sequence diagram theme"
@ -114,6 +121,7 @@ export default {
frontmatterType: state => state.preferences.frontmatterType, frontmatterType: state => state.preferences.frontmatterType,
superSubScript: state => state.preferences.superSubScript, superSubScript: state => state.preferences.superSubScript,
footnote: state => state.preferences.footnote, footnote: state => state.preferences.footnote,
isGitlabCompatibilityEnabled: state => state.preferences.isGitlabCompatibilityEnabled,
sequenceTheme: state => state.preferences.sequenceTheme sequenceTheme: state => state.preferences.sequenceTheme
}) })
}, },

View File

@ -47,6 +47,7 @@ const state = {
frontmatterType: '-', frontmatterType: '-',
superSubScript: false, superSubScript: false,
footnote: false, footnote: false,
isGitlabCompatibilityEnabled: false,
sequenceTheme: 'hand', sequenceTheme: 'hand',
theme: 'light', theme: 'light',

View File

@ -43,6 +43,7 @@
"frontmatterType": "-", "frontmatterType": "-",
"superSubScript": false, "superSubScript": false,
"footnote": false, "footnote": false,
"isGitlabCompatibilityEnabled": false,
"sequenceTheme": "hand", "sequenceTheme": "hand",
"theme": "light", "theme": "light",