diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 5376a773..493b1fc4 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -738,10 +738,17 @@ export default class MarkdownPreview extends React.Component { try { const chartConfig = JSON.parse(el.innerHTML) el.innerHTML = '' - var canvas = document.createElement('canvas') + + const canvas = document.createElement('canvas') el.appendChild(canvas) - /* eslint-disable no-new */ - new Chart(canvas, chartConfig) + + const height = el.attributes.getNamedItem('data-height') + if (height && height.value !== 'undefined') { + el.style.height = height.value + 'vh' + canvas.height = height.value + 'vh' + } + + const chart = new Chart(canvas, chartConfig) } catch (e) { console.error(e) el.className = 'chart-error' diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl index fb30742d..ea46b0b6 100644 --- a/browser/components/markdown.styl +++ b/browser/components/markdown.styl @@ -213,10 +213,12 @@ pre margin 0 0 1em display flex line-height 1.4em - &.flowchart, &.sequence, &.chart - display flex - justify-content center - background-color white + code + background-color inherit + margin 0 + padding 0 + border none + border-radius 0 &.CodeMirror height initial flex-wrap wrap @@ -230,12 +232,12 @@ pre border none border-radius 0 &>span.filename - width 100% - border-radius: 5px 0px 0px 0px - margin -8px 100% 8px -8px - padding 0px 6px + margin -8px -7px 8px -8px + padding 2px 6px background-color #777; color white + &:empty + display none &>span.lineNumber display none font-size 1em @@ -361,7 +363,7 @@ for name, val in admonition_types .admonition.{name} @extend $admonition border-left-color: val[color] - + .admonition.{name}>.admonition-title @extend $admonition-title border-bottom-color: .1rem solid rgba(val[color], 0.2) @@ -372,6 +374,18 @@ for name, val in admonition_types color: val[color] content: val[icon] +pre.fence + flex-direction column + + .chart, .flowchart, .mermaid, .sequence + display flex + justify-content center + background-color white + max-width 100% + + canvas, svg + max-width 100% !important + themeDarkBackground = darken(#21252B, 10%) themeDarkText = #f9f9f9 themeDarkBorder = lighten(themeDarkBackground, 20%) diff --git a/browser/components/render/MermaidRender.js b/browser/components/render/MermaidRender.js index bac52b2c..f04d8cd0 100644 --- a/browser/components/render/MermaidRender.js +++ b/browser/components/render/MermaidRender.js @@ -2,8 +2,8 @@ import mermaidAPI from 'mermaid' // fixes bad styling in the mermaid dark theme const darkThemeStyling = ` -.loopText tspan { - fill: white; +.loopText tspan { + fill: white; }` function getRandomInt (min, max) { @@ -11,9 +11,9 @@ function getRandomInt (min, max) { } function getId () { - var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' - var id = 'm-' - for (var i = 0; i < 7; i++) { + const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + let id = 'm-' + for (let i = 0; i < 7; i++) { id += pool[getRandomInt(0, 16)] } return id @@ -21,14 +21,18 @@ function getId () { function render (element, content, theme) { try { + const height = element.attributes.getNamedItem('data-height') + if (height && height.value !== 'undefined') { + element.style.height = height.value + 'vh' + } let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' mermaidAPI.initialize({ theme: isDarkTheme ? 'dark' : 'default', - themeCSS: isDarkTheme ? darkThemeStyling : '' + themeCSS: isDarkTheme ? darkThemeStyling : '', + useMaxWidth: false }) mermaidAPI.render(getId(), content, (svgGraph) => { element.innerHTML = svgGraph - element.style.height = element.attributes.getNamedItem('data-height').value + 'vh' }) } catch (e) { console.error(e) diff --git a/browser/lib/markdown-it-fence.js b/browser/lib/markdown-it-fence.js new file mode 100644 index 00000000..040ac540 --- /dev/null +++ b/browser/lib/markdown-it-fence.js @@ -0,0 +1,130 @@ +'use strict' + +module.exports = function (md, renderers, defaultRenderer) { + function fence (state, startLine, endLine) { + let pos = state.bMarks[startLine] + state.tShift[startLine] + let max = state.eMarks[startLine] + + if (state.sCount[startLine] - state.blkIndent >= 4 || pos + 3 > max) { + return false + } + + const marker = state.src.charCodeAt(pos) + if (!(marker === 96 || marker === 126)) { + return false + } + + let mem = pos + pos = state.skipChars(pos, marker) + + let len = pos - mem + if (len < 3) { + return false + } + + const markup = state.src.slice(mem, pos) + const params = state.src.slice(pos, max) + + let nextLine = startLine + let haveEndMarker = false + + while (true) { + nextLine++ + if (nextLine >= endLine) { + break + } + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine] + max = state.eMarks[nextLine] + + if (pos < max && state.sCount[nextLine] < state.blkIndent) { + break + } + if (state.src.charCodeAt(pos) !== marker || state.sCount[nextLine] - state.blkIndent >= 4) { + continue + } + + pos = state.skipChars(pos, marker) + + if (pos - mem < len) { + continue + } + + pos = state.skipSpaces(pos) + + if (pos >= max) { + haveEndMarker = true + break + } + } + + len = state.sCount[startLine] + state.line = nextLine + (haveEndMarker ? 1 : 0) + + const parameters = {} + let langType = '' + let fileName = '' + let firstLineNumber = 0 + + let match = /^(\w[-\w]*)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/.exec(params) + if (match) { + if (match[1]) { + langType = match[1] + } + if (match[3]) { + fileName = match[3] + } + if (match[4]) { + firstLineNumber = parseInt(match[4], 10) + } + + if (match[2]) { + const params = match[2] + const regex = /(\w[-\w]*)(?:=(?:'(.*?[^\\])?'|"(.*?[^\\])?"|([^'"][^\s]*)))?/g + + let name, value + while ((match = regex.exec(params))) { + name = match[1] + value = match[2] || match[3] || match[4] || null + + const height = /^(\d+)h$/.exec(name) + if (height && !value) { + parameters.height = height[1] + } else { + parameters[name] = value + } + } + } + } + + let token + if (renderers[langType]) { + token = state.push(`${langType}_fence`, 'div', 0) + } else { + token = state.push('_fence', 'code', 0) + } + + token.langType = langType + token.fileName = fileName + token.firstLineNumber = firstLineNumber + token.parameters = parameters + + token.content = state.getLines(startLine + 1, nextLine, len, true) + token.markup = markup + token.map = [startLine, state.line] + + return true + } + + md.block.ruler.before('fence', '_fence', fence, { + alt: ['paragraph', 'reference', 'blockquote', 'list'] + }) + + for (let name in renderers) { + md.renderer.rules[`${name}_fence`] = (tokens, index) => renderers[name](tokens[index]) + } + + if (defaultRenderer) { + md.renderer.rules['_fence'] = (tokens, index) => defaultRenderer(tokens[index]) + } +} diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index d4bc72e4..40c47aac 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -27,32 +27,6 @@ class Markdown { html: true, xhtmlOut: true, breaks: config.preview.breaks, - highlight: function (str, lang) { - const delimiter = ':' - const langInfo = lang.split(delimiter) - const langType = langInfo[0] - const fileName = langInfo[1] || '' - const firstLineNumber = parseInt(langInfo[2], 10) - - if (langType === 'flowchart') { - return `
${str}
` - } - if (langType === 'sequence') { - return `
${str}
` - } - if (langType === 'chart') { - return `
${str}
` - } - if (langType === 'mermaid') { - return `
${str}
` - } - return '
' +
-          '' + fileName + '' +
-          createGutter(str, firstLineNumber) +
-          '' +
-          str +
-          '
' - }, sanitize: 'STRICT' } @@ -150,6 +124,39 @@ class Markdown { this.md.use(require('markdown-it-kbd')) this.md.use(require('markdown-it-admonition')) + this.md.use(require('./markdown-it-fence'), { + chart: token => { + return `
+          ${token.fileName}
+          
${token.content}
+
` + }, + flowchart: token => { + return `
+          ${token.fileName}
+          
${token.content}
+
` + }, + mermaid: token => { + return `
+          ${token.fileName}
+          
${token.content}
+
` + }, + sequence: token => { + return `
+          ${token.fileName}
+          
${token.content}
+
` + } + }, token => { + return `
+        ${token.fileName}
+        ${createGutter(token.content, token.firstLineNumber)}
+        ${token.content}
+      
` + }) + const deflate = require('markdown-it-plantuml/lib/deflate') this.md.use(require('markdown-it-plantuml'), '', { generateSource: function (umlCode) {