Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add attributes to fence blocks #2394

Merged
merged 15 commits into from
Nov 6, 2018
Merged
16 changes: 10 additions & 6 deletions browser/components/MarkdownPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down
47 changes: 29 additions & 18 deletions browser/components/markdown.styl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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%)
Expand Down
14 changes: 9 additions & 5 deletions browser/components/render/MermaidRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,30 @@ 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
}

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
}
Expand Down
130 changes: 130 additions & 0 deletions browser/lib/markdown-it-fence.js
Original file line number Diff line number Diff line change
@@ -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])
}
}
2 changes: 1 addition & 1 deletion browser/lib/markdown-it-sanitize-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,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,
Expand Down
59 changes: 33 additions & 26 deletions browser/lib/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<pre class="flowchart">${str}</pre>`
}
if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
if (langType === 'chart') {
return `<pre class="chart">${str}</pre>`
}
if (langType === 'mermaid') {
return `<pre class="mermaid">${str}</pre>`
}
return '<pre class="code CodeMirror">' +
'<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
str +
'</code></pre>'
},
sanitize: 'STRICT'
}

Expand Down Expand Up @@ -153,6 +127,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 `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="chart" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
flowchart: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
mermaid: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
sequence: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
}
}, token => {
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
${createGutter(token.content, token.firstLineNumber)}
<code class="${token.langType}">${token.content}</code>
</pre>`
})

const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
Expand Down
2 changes: 1 addition & 1 deletion gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ module.exports = function (grunt) {
bgColor = Color(declaration.value.split(' ')[0])
} else if (declaration.property === 'color') {
const value = /^(.*?)(?:\s*!important)?$/.exec(declaration.value)[1]
let match = /^rgba\((.*?),\s*1\)$/.exec(value)
const match = /^rgba\((.*?),\s*1\)$/.exec(value)
if (match) {
fgColor = Color(`rgb(${match[1]})`)
} else {
Expand Down
9 changes: 6 additions & 3 deletions tests/lib/snapshots/markdown-test.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ Generated by [AVA](https://ava.li).

> Snapshot 1

`<pre class="code CodeMirror"><span class="filename">filename.js</span><span class="lineNumber CodeMirror-gutters"><span class="CodeMirror-linenumber">2</span></span><code class="js">var project = 'boostnote';␊
</code></pre>␊
`
`<pre class="code CodeMirror" data-line="1">␊
<span class="filename">filename.js</span>␊
<span class="lineNumber CodeMirror-gutters"><span class="CodeMirror-linenumber">2</span></span>␊
<code class="js">var project = 'boostnote';␊
</code>␊
</pre>`

## Markdown.render() should renders markdown correctly

Expand Down
Binary file modified tests/lib/snapshots/markdown-test.js.snap
Binary file not shown.