diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 0af16e31..d9ff7074 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -16,6 +16,8 @@ import convertModeName from 'browser/lib/convertModeName' import copy from 'copy-to-clipboard' import mdurl from 'mdurl' import exportNote from 'browser/main/lib/dataApi/exportNote' +import { escapeHtmlCharacters } from 'browser/lib/utils' +import yaml from 'js-yaml' import context from 'browser/lib/context' import i18n from 'browser/lib/i18n' import fs from 'fs' @@ -767,7 +769,8 @@ export default class MarkdownPreview extends React.Component { this.refs.root.contentWindow.document.querySelectorAll('.chart'), el => { try { - const chartConfig = JSON.parse(el.innerHTML) + const format = el.attributes.getNamedItem('data-format').value + const chartConfig = format === 'yaml' ? yaml.load(el.innerHTML) : JSON.parse(el.innerHTML) el.innerHTML = '' const canvas = document.createElement('canvas') diff --git a/browser/lib/markdown-it-fence.js b/browser/lib/markdown-it-fence.js index 983dc45c..fd1c759d 100644 --- a/browser/lib/markdown-it-fence.js +++ b/browser/lib/markdown-it-fence.js @@ -1,6 +1,8 @@ 'use strict' module.exports = function (md, renderers, defaultRenderer) { + const paramsRE = /^[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/ + function fence (state, startLine, endLine) { let pos = state.bMarks[startLine] + state.tShift[startLine] let max = state.eMarks[startLine] @@ -66,7 +68,7 @@ module.exports = function (md, renderers, defaultRenderer) { let fileName = '' let firstLineNumber = 1 - let match = /^(\w[-\w]*)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/.exec(params) + let match = paramsRE.exec(params) if (match) { if (match[1]) { langType = match[1] diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 0c97a4d5..be04fb08 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -133,9 +133,13 @@ class Markdown { this.md.use(require('./markdown-it-fence'), { chart: token => { + if (token.parameters.hasOwnProperty('yaml')) { + token.parameters.format = 'yaml' + } + return `
${token.fileName}
- ${token.content}
+ ${token.content}
`
},
flowchart: token => {
diff --git a/extra_scripts/codemirror/mode/bfm/bfm.js b/extra_scripts/codemirror/mode/bfm/bfm.js
index baf65d18..80f797b9 100644
--- a/extra_scripts/codemirror/mode/bfm/bfm.js
+++ b/extra_scripts/codemirror/mode/bfm/bfm.js
@@ -1,28 +1,170 @@
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../codemirror/lib/codemirror"), require("../codemirror/mode/gfm/gfm"))
+ mod(require("../codemirror/lib/codemirror"), require("../codemirror/mode/gfm/gfm"), require("../codemirror/mode/yaml-frontmatter/yaml-frontmatter"))
else if (typeof define == "function" && define.amd) // AMD
- define(["../codemirror/lib/codemirror", "../codemirror/mode/gfm/gfm"], mod)
+ define(["../codemirror/lib/codemirror", "../codemirror/mode/gfm/gfm", "../codemirror/mode/yaml-frontmatter/yaml-frontmatter"], mod)
else // Plain browser env
mod(CodeMirror)
})(function(CodeMirror) {
'use strict'
- CodeMirror.defineMode('bfm', function(config, gfmConfig) {
- const bfmOverlay = {
- startState() {
+ const fencedCodeRE = /^(~~~+|```+)[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/
+
+ function getMode(name, params, config, cm) {
+ if (!name) {
+ return null
+ }
+
+ const parameters = {}
+ if (params) {
+ const regex = /(\w[-\w]*)(?:=(?:'(.*?[^\\])?'|"(.*?[^\\])?"|([^'"][^\s]*)))?/g
+
+ let match
+ while ((match = regex.exec(params))) {
+ parameters[match[1]] = match[2] || match[3] || match[4] || null
+ }
+ }
+
+ if (name === 'chart') {
+ name = parameters.hasOwnProperty('yaml') ? 'yaml' : 'json'
+ }
+
+ const found = CodeMirror.findModeByName(name)
+ if (!found) {
+ return null
+ }
+
+ if (CodeMirror.modes.hasOwnProperty(found.mode)) {
+ const mode = CodeMirror.getMode(config, found.mode)
+
+ return mode.name === 'null' ? null : mode
+ } else {
+ CodeMirror.requireMode(found.mode, () => {
+ cm.setOption('mode', cm.getOption('mode'))
+ })
+ }
+ }
+
+ CodeMirror.defineMode('bfm', function (config, baseConfig) {
+ baseConfig.name = 'yaml-frontmatter'
+ const baseMode = CodeMirror.getMode(config, baseConfig)
+
+ return {
+ startState: function() {
return {
+ baseState: CodeMirror.startState(baseMode),
+
+ basePos: 0,
+ baseCur: null,
+ overlayPos: 0,
+ overlayCur: null,
+ streamSeen: null,
+
+ fencedEndRE: null,
+
inTable: false,
rowIndex: 0
}
},
- copyState(s) {
+ copyState: function(s) {
return {
+ baseState: CodeMirror.copyState(baseMode, s.baseState),
+
+ basePos: s.basePos,
+ baseCur: null,
+ overlayPos: s.overlayPos,
+ overlayCur: null,
+
+ fencedMode: s.fencedMode,
+ fencedState: s.fencedMode ? CodeMirror.copyState(s.fencedMode, s.fencedState) : null,
+
+ fencedEndRE: s.fencedEndRE,
+
inTable: s.inTable,
rowIndex: s.rowIndex
}
},
- token(stream, state) {
+ token: function(stream, state) {
+ const initialPos = stream.pos
+
+ if (state.fencedEndRE && stream.match(state.fencedEndRE)) {
+ state.fencedEndRE = null
+ state.fencedMode = null
+ state.fencedState = null
+
+ stream.pos = initialPos
+ }
+ else {
+ if (state.fencedMode) {
+ return state.fencedMode.token(stream, state.fencedState)
+ }
+
+ const match = stream.match(fencedCodeRE, true)
+ if (match) {
+ state.fencedEndRE = new RegExp(match[1] + '+ *$')
+
+ state.fencedMode = getMode(match[2], match[3], config, stream.lineOracle.doc.cm)
+ if (state.fencedMode) {
+ state.fencedState = CodeMirror.startState(state.fencedMode)
+ }
+
+ stream.pos = initialPos
+ }
+ }
+
+ if (stream != state.streamSeen || Math.min(state.basePos, state.overlayPos) < stream.start) {
+ state.streamSeen = stream
+ state.basePos = state.overlayPos = stream.start
+ }
+
+ if (stream.start == state.basePos) {
+ state.baseCur = baseMode.token(stream, state.baseState)
+ state.basePos = stream.pos
+ }
+ if (stream.start == state.overlayPos) {
+ stream.pos = stream.start
+ state.overlayCur = this.overlayToken(stream, state)
+ state.overlayPos = stream.pos
+ }
+ stream.pos = Math.min(state.basePos, state.overlayPos)
+
+ if (state.overlayCur == null) {
+ return state.baseCur
+ }
+ else if (state.baseCur != null && state.combineTokens) {
+ return state.baseCur + ' ' + state.overlayCur
+ }
+ else {
+ return state.overlayCur
+ }
+ },
+ overlayToken: function(stream, state) {
+ state.combineTokens = false
+
+ if (state.fencedEndRE && stream.match(state.fencedEndRE)) {
+ state.fencedEndRE = null
+ state.localMode = null
+ state.localState = null
+
+ return null
+ }
+
+ if (state.localMode) {
+ return state.localMode.token(stream, state.localState) || ''
+ }
+
+ const match = stream.match(fencedCodeRE, true)
+ if (match) {
+ state.fencedEndRE = new RegExp(match[1] + '+ *$')
+
+ state.localMode = getMode(match[2], match[3], config, stream.lineOracle.doc.cm)
+ if (state.localMode) {
+ state.localState = CodeMirror.startState(state.localMode)
+ }
+
+ return null
+ }
+
state.combineTokens = true
if (state.inTable) {
@@ -55,14 +197,31 @@
stream.skipToEnd()
return null
},
- blankLine(state) {
+ electricChars: baseMode.electricChars,
+ innerMode: function(state) {
+ if (state.fencedMode) {
+ return {
+ mode: state.fencedMode,
+ state: state.fencedState
+ }
+ } else {
+ return {
+ mode: baseMode,
+ state: state.baseState
+ }
+ }
+ },
+ blankLine: function(state) {
state.inTable = false
+
+ if (state.fencedMode) {
+ return state.fencedMode.blankLine && state.fencedMode.blankLine(state.fencedState)
+ } else {
+ return baseMode.blankLine(state.baseState)
+ }
}
}
-
- gfmConfig.name = 'gfm'
- return CodeMirror.overlayMode(CodeMirror.getMode(config, gfmConfig), bfmOverlay)
- })
+ }, 'yaml-frontmatter')
CodeMirror.defineMIME('text/x-bfm', 'bfm')
diff --git a/lib/main.html b/lib/main.html
index 65c540a3..64add406 100644
--- a/lib/main.html
+++ b/lib/main.html
@@ -98,8 +98,11 @@
+
+
+
diff --git a/package.json b/package.json
index feaefb9f..8c1c6dc8 100644
--- a/package.json
+++ b/package.json
@@ -68,6 +68,7 @@
"iconv-lite": "^0.4.19",
"immutable": "^3.8.1",
"js-sequence-diagrams": "^1000000.0.6",
+ "js-yaml": "^3.12.0",
"katex": "^0.9.0",
"lodash": "^4.11.1",
"lodash-move": "^1.1.1",
diff --git a/yarn.lock b/yarn.lock
index 54046200..b05daf78 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5326,7 +5326,7 @@ js-yaml@^3.10.0, js-yaml@^3.5.1, js-yaml@^3.7.0:
argparse "^1.0.7"
esprima "^4.0.0"
-js-yaml@^3.8.1:
+js-yaml@^3.12.0, js-yaml@^3.8.1:
version "3.12.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
dependencies:
@@ -7349,8 +7349,8 @@ rcedit@^1.0.0:
resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-1.1.0.tgz#ae21c28d4efdd78e95fcab7309a5dd084920b16a"
react-autosuggest@^9.4.0:
- version "9.4.0"
- resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-9.4.0.tgz#3146bc9afa4f171bed067c542421edec5ca94294"
+ version "9.4.2"
+ resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-9.4.2.tgz#18cc0bebeebda3d24328e3da301f061a444ae223"
dependencies:
prop-types "^15.5.10"
react-autowhatever "^10.1.2"