|
| 1 | +const PJSON = require("./pseudo_json") |
| 2 | +const markdownIt = require("markdown-it") |
| 3 | + |
| 4 | +function parseData(str) { |
| 5 | + let tag = /^\s*(\w+)\s*(,\s*)?/.exec(str), obj |
| 6 | + if (!tag) return null |
| 7 | + if (tag[0].length == str.length) { |
| 8 | + obj = {} |
| 9 | + } else if (tag[2]) { |
| 10 | + try { obj = PJSON.parse("{" + str.slice(tag[0].length) + "}") } |
| 11 | + catch(_) { return null } |
| 12 | + } else { |
| 13 | + obj = {} |
| 14 | + try { obj.args = PJSON.parse("[" + str.slice(tag[0].length) + "]") } |
| 15 | + catch(_) { return null } |
| 16 | + } |
| 17 | + obj._ = tag[1] |
| 18 | + return obj |
| 19 | +} |
| 20 | + |
| 21 | +function parseBlockMeta(state, startLine, endLine) { |
| 22 | + let pos = state.bMarks[startLine] + state.tShift[startLine] |
| 23 | + let max = state.eMarks[startLine] |
| 24 | + // Check for code block indentation or end of input |
| 25 | + if (state.sCount[startLine] - state.blkIndent >= 4 || pos + 4 > max) return false |
| 26 | + |
| 27 | + // Test for `{{` opening marker |
| 28 | + if (state.src.charCodeAt(pos) != 123 || state.src.charCodeAt(pos + 1) != 123) return false |
| 29 | + |
| 30 | + let content = state.src.slice(pos + 2, max), single |
| 31 | + |
| 32 | + if (single = /\}\}\s*/.exec(content)) { |
| 33 | + let data = parseData(content.slice(0, single.index)) |
| 34 | + if (!data) return false |
| 35 | + let token = state.push("meta", null, 0) |
| 36 | + token.map = [startLine, startLine + 1] |
| 37 | + token.attrs = [["data", data]] |
| 38 | + state.line++ |
| 39 | + return true |
| 40 | + } |
| 41 | + |
| 42 | + let data = parseData(content) |
| 43 | + if (!data) return false |
| 44 | + |
| 45 | + let line = startLine + 1, depth = 0 |
| 46 | + for (; line < endLine; line++) { |
| 47 | + if (line == endLine) throw new SyntaxError("Unterminated meta block") |
| 48 | + let start = state.bMarks[line] + state.tShift[line] |
| 49 | + let after = state.src.slice(start, start + 2) |
| 50 | + if (after == "{{" && !/\}\}\s*$/.test(state.src.slice(start, state.eMarks[line]))) depth++ |
| 51 | + else if (after == "}}") { |
| 52 | + if (depth) depth-- |
| 53 | + else break |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + let token = state.push("meta_open", null, 1) |
| 58 | + token.map = [startLine, line + 1] |
| 59 | + token.attrs = [["data", data]] |
| 60 | + state.md.block.tokenize(state, startLine + 1, line) |
| 61 | + state.push("meta_close", null, -1) |
| 62 | + state.line = line + 1 |
| 63 | + |
| 64 | + return true |
| 65 | +} |
| 66 | + |
| 67 | +function parseInlineMeta(state) { |
| 68 | + if (state.src.charCodeAt(state.pos) != 91) return false // '[' |
| 69 | + |
| 70 | + let max = state.posMax |
| 71 | + let end = state.md.helpers.parseLinkLabel(state, state.pos, false) |
| 72 | + if (end < 0) return false |
| 73 | + |
| 74 | + let pos = end + 1 |
| 75 | + if (pos >= max || state.src.charCodeAt(pos) != 123) return false // '{' |
| 76 | + |
| 77 | + let metaEnd = pos + 1, depth = 0 |
| 78 | + for (;; metaEnd++) { |
| 79 | + if (metaEnd == max) return false |
| 80 | + let code = state.src.charCodeAt(metaEnd) |
| 81 | + if (code == 125) { // '}' |
| 82 | + if (depth) depth-- |
| 83 | + else break |
| 84 | + } else if (code == 123) { |
| 85 | + depth++ |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + let data = parseData(state.src.slice(pos + 1, metaEnd)) |
| 90 | + if (!data) return false |
| 91 | + |
| 92 | + state.pos++ |
| 93 | + state.posMax = end |
| 94 | + state.push("meta_open", null, 1).attrs = [["data", data]] |
| 95 | + state.md.inline.tokenize(state) |
| 96 | + state.push("meta_close", null, -1) |
| 97 | + state.pos = metaEnd + 1 |
| 98 | + state.posMax = max |
| 99 | + |
| 100 | + return true |
| 101 | +} |
| 102 | + |
| 103 | +function parseIndexTerm(state) { |
| 104 | + let max = state.posMax |
| 105 | + // Check for opening '((' |
| 106 | + if (state.pos >= max + 4 || state.src.charCodeAt(state.pos) != 40 || state.src.charCodeAt(state.pos + 1) != 40) return false |
| 107 | + |
| 108 | + let start = state.pos + 2, end = start |
| 109 | + for (;; end++) { |
| 110 | + if (end >= max - 1) return false |
| 111 | + if (state.src.charCodeAt(end) == 41 && state.src.charCodeAt(end + 1)) break |
| 112 | + } |
| 113 | + |
| 114 | + let term = state.src.slice(start, end) |
| 115 | + |
| 116 | + state.push("meta", null, 0).attrs = [["data", {_: "index", args: [term]}]] |
| 117 | + state.pending += term |
| 118 | + state.pos = end + 2 |
| 119 | + return true |
| 120 | +} |
| 121 | + |
| 122 | +let TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]|\(\(/ |
| 123 | + |
| 124 | +function newText(state) { |
| 125 | + let len = state.src.slice(state.pos).search(TERMINATOR_RE) |
| 126 | + if (len == 0) return false |
| 127 | + if (len == -1) len = state.src.length - state.pos |
| 128 | + state.pending += state.src.slice(state.pos, state.pos + len) |
| 129 | + state.pos += len |
| 130 | + return true |
| 131 | +} |
| 132 | + |
| 133 | +function plugin(md) { |
| 134 | + md.block.ruler.before("code", "meta", parseBlockMeta) |
| 135 | + md.inline.ruler.before("link", "meta", parseInlineMeta) |
| 136 | + md.inline.ruler.at("text", newText) |
| 137 | + md.inline.ruler.before("strikethrough", "index_term", parseIndexTerm) |
| 138 | +} |
| 139 | + |
| 140 | +module.exports = markdownIt().use(plugin) |
0 commit comments