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"