diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 6643d796..0af16e31 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -737,7 +737,6 @@ export default class MarkdownPreview extends React.Component { el.addEventListener('click', this.linkClickHandler) }) } catch (e) { - console.error(e) el.className = 'flowchart-error' el.innerHTML = 'Flowchart parse error: ' + e.message } @@ -758,7 +757,6 @@ export default class MarkdownPreview extends React.Component { el.addEventListener('click', this.linkClickHandler) }) } catch (e) { - console.error(e) el.className = 'sequence-error' el.innerHTML = 'Sequence diagram parse error: ' + e.message } @@ -771,12 +769,18 @@ 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' el.innerHTML = 'chartjs diagram parse error: ' + e.message } diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl index e091331b..19132622 100644 --- a/browser/components/markdown.styl +++ b/browser/components/markdown.styl @@ -209,41 +209,39 @@ code text-decoration none margin-right 2px pre - padding 0.5em !important + padding 0.5rem !important border solid 1px #D1D1D1 border-radius 5px overflow-x auto - margin 0 0 1em + margin 0 0 1rem display flex line-height 1.4em - &.flowchart, &.sequence, &.chart - display flex - justify-content center - background-color white - &.CodeMirror - height initial - flex-wrap wrap - &>code - flex 1 - overflow-x auto code background-color inherit margin 0 padding 0 border none border-radius 0 + &.CodeMirror + height initial + flex-wrap wrap + &>code + flex 1 + overflow-x auto + &.mermaid svg + max-width 100% !important &>span.filename - width 100% - border-radius: 5px 0px 0px 0px - margin -8px 100% 8px -8px - padding 0px 6px + margin -0.5rem 100% 0.5rem -0.5rem + padding 0.125rem 0.375rem background-color #777; color white + &:empty + display none &>span.lineNumber display none font-size 1em - padding 0.5em 0 - margin -0.5em 0.5em -0.5em -0.5em + padding 0.5rem 0 + margin -0.5rem 0.5rem -0.5rem -0.5rem border-right 1px solid text-align right border-top-left-radius 4px @@ -375,6 +373,19 @@ for name, val in admonition_types color: val[color] content: val[icon] +pre.fence + flex-wrap wrap + + .chart, .flowchart, .mermaid, .sequence + display flex + justify-content center + background-color white + max-width 100% + flex-grow 1 + + 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 e8784d9d..7a3b3ea2 100644 --- a/browser/components/render/MermaidRender.js +++ b/browser/components/render/MermaidRender.js @@ -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,16 +21,20 @@ 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' || theme === 'dracula' mermaidAPI.initialize({ theme: isDarkTheme ? 'dark' : 'default', - themeCSS: isDarkTheme ? darkThemeStyling : '' + themeCSS: isDarkTheme ? darkThemeStyling : '', + useMaxWidth: false }) mermaidAPI.render(getId(), content, (svgGraph) => { element.innerHTML = svgGraph }) } catch (e) { - console.error(e) element.className = 'mermaid-error' element.innerHTML = 'mermaid diagram parse error: ' + e.message } diff --git a/browser/lib/markdown-it-fence.js b/browser/lib/markdown-it-fence.js new file mode 100644 index 00000000..983dc45c --- /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 = 1 + + 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 (const 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-it-sanitize-html.js b/browser/lib/markdown-it-sanitize-html.js index bd3d452e..8f6d86a8 100644 --- a/browser/lib/markdown-it-sanitize-html.js +++ b/browser/lib/markdown-it-sanitize-html.js @@ -15,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) { options ) } - if (state.tokens[tokenIdx].type === 'fence') { + if (state.tokens[tokenIdx].type === '_fence') { // escapeHtmlCharacters has better performance state.tokens[tokenIdx].content = escapeHtmlCharacters( state.tokens[tokenIdx].content, diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 7fbb5a38..0c97a4d5 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -28,32 +28,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' } @@ -157,6 +131,39 @@ class Markdown { this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']}) this.md.use(require('./markdown-it-frontmatter')) + 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) { diff --git a/tests/lib/snapshots/markdown-test.js.md b/tests/lib/snapshots/markdown-test.js.md index df5bad53..6fe17835 100644 --- a/tests/lib/snapshots/markdown-test.js.md +++ b/tests/lib/snapshots/markdown-test.js.md @@ -47,9 +47,12 @@ Generated by [AVA](https://ava.li). > Snapshot 1 - `
filename.js2var project = 'boostnote';␊
-    
␊ - ` + `
␊
+            filename.js␊
+            2␊
+            var project = 'boostnote';␊
+    ␊
+          
` ## Markdown.render() should renders markdown correctly diff --git a/tests/lib/snapshots/markdown-test.js.snap b/tests/lib/snapshots/markdown-test.js.snap index 5b8d1095..c1cf5903 100644 Binary files a/tests/lib/snapshots/markdown-test.js.snap and b/tests/lib/snapshots/markdown-test.js.snap differ diff --git a/yarn.lock b/yarn.lock index 06f2e010..54046200 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2826,20 +2826,6 @@ electron-config@^1.0.0: dependencies: conf "^1.0.0" -electron-download@^3.0.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-3.3.0.tgz#2cfd54d6966c019c4d49ad65fbe65cc9cdef68c8" - dependencies: - debug "^2.2.0" - fs-extra "^0.30.0" - home-path "^1.0.1" - minimist "^1.2.0" - nugget "^2.0.0" - path-exists "^2.1.0" - rc "^1.1.2" - semver "^5.3.0" - sumchecker "^1.2.0" - electron-download@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.0.tgz#bf932c746f2f87ffcc09d1dd472f2ff6b9187845" @@ -2854,7 +2840,21 @@ electron-download@^4.0.0: semver "^5.3.0" sumchecker "^2.0.1" -electron-gh-releases@^2.0.2: +electron-download@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" + dependencies: + debug "^3.0.0" + env-paths "^1.0.0" + fs-extra "^4.0.1" + minimist "^1.2.0" + nugget "^2.0.1" + path-exists "^3.0.0" + rc "^1.2.1" + semver "^5.4.1" + sumchecker "^2.0.2" + +electron-gh-releases@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/electron-gh-releases/-/electron-gh-releases-2.0.4.tgz#198c07a0970fb8e80fcc67bd0b4198a010923dc3" dependencies: @@ -2938,12 +2938,12 @@ electron-winstaller@^2.2.0: lodash.template "^4.2.2" temp "^0.8.3" -electron@2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/electron/-/electron-2.0.7.tgz#f7ce410433298e319032ce31f0e6ffd709ff052c" +electron@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/electron/-/electron-3.0.3.tgz#2857ed8d37c1b46e0a75a72684800252255f3243" dependencies: "@types/node" "^8.0.24" - electron-download "^3.0.1" + electron-download "^4.1.0" extract-zip "^1.0.3" emojis-list@^2.0.0: @@ -3058,10 +3058,6 @@ es6-promise@^3.1.2: version "3.3.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" -es6-promise@^4.0.5: - version "4.2.4" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" - es6-set@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" @@ -3782,16 +3778,6 @@ fs-extra@0.26.7, fs-extra@^0.26.0, fs-extra@^0.26.7: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - fs-extra@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" @@ -3807,7 +3793,7 @@ fs-extra@^2.0.0: graceful-fs "^4.1.2" jsonfile "^2.1.0" -fs-extra@^4.0.0: +fs-extra@^4.0.0, fs-extra@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" dependencies: @@ -4350,10 +4336,6 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" -home-path@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/home-path/-/home-path-1.0.6.tgz#d549dc2465388a7f8667242c5b31588d29af29fc" - hooker@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" @@ -6480,7 +6462,7 @@ npmlog@^4.0.2: gauge "~2.7.3" set-blocking "~2.0.0" -nugget@^2.0.0: +nugget@^2.0.0, nugget@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" dependencies: @@ -6782,7 +6764,7 @@ path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" -path-exists@^2.0.0, path-exists@^2.1.0: +path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" dependencies: @@ -7353,7 +7335,7 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" -rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7: +rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7, rc@^1.2.1: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" dependencies: @@ -8584,14 +8566,7 @@ stylus@^0.52.4: sax "0.5.x" source-map "0.1.x" -sumchecker@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d" - dependencies: - debug "^2.2.0" - es6-promise "^4.0.5" - -sumchecker@^2.0.1: +sumchecker@^2.0.1, sumchecker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" dependencies: