inital
This commit is contained in:
209
node_modules/markdown-it/lib/rules_block/blockquote.mjs
generated
vendored
Normal file
209
node_modules/markdown-it/lib/rules_block/blockquote.mjs
generated
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
// Block quotes
|
||||
|
||||
import { isSpace } from '../common/utils.mjs'
|
||||
|
||||
export default function blockquote (state, startLine, endLine, silent) {
|
||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
let max = state.eMarks[startLine]
|
||||
|
||||
const oldLineMax = state.lineMax
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
|
||||
|
||||
// check the block quote marker
|
||||
if (state.src.charCodeAt(pos) !== 0x3E/* > */) { return false }
|
||||
|
||||
// we know that it's going to be a valid blockquote,
|
||||
// so no point trying to find the end of it in silent mode
|
||||
if (silent) { return true }
|
||||
|
||||
const oldBMarks = []
|
||||
const oldBSCount = []
|
||||
const oldSCount = []
|
||||
const oldTShift = []
|
||||
|
||||
const terminatorRules = state.md.block.ruler.getRules('blockquote')
|
||||
|
||||
const oldParentType = state.parentType
|
||||
state.parentType = 'blockquote'
|
||||
let lastLineEmpty = false
|
||||
let nextLine
|
||||
|
||||
// Search the end of the block
|
||||
//
|
||||
// Block ends with either:
|
||||
// 1. an empty line outside:
|
||||
// ```
|
||||
// > test
|
||||
//
|
||||
// ```
|
||||
// 2. an empty line inside:
|
||||
// ```
|
||||
// >
|
||||
// test
|
||||
// ```
|
||||
// 3. another tag:
|
||||
// ```
|
||||
// > test
|
||||
// - - -
|
||||
// ```
|
||||
for (nextLine = startLine; nextLine < endLine; nextLine++) {
|
||||
// check if it's outdented, i.e. it's inside list item and indented
|
||||
// less than said list item:
|
||||
//
|
||||
// ```
|
||||
// 1. anything
|
||||
// > current blockquote
|
||||
// 2. checking this line
|
||||
// ```
|
||||
const isOutdented = state.sCount[nextLine] < state.blkIndent
|
||||
|
||||
pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
max = state.eMarks[nextLine]
|
||||
|
||||
if (pos >= max) {
|
||||
// Case 1: line is not inside the blockquote, and this line is empty.
|
||||
break
|
||||
}
|
||||
|
||||
if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !isOutdented) {
|
||||
// This line is inside the blockquote.
|
||||
|
||||
// set offset past spaces and ">"
|
||||
let initial = state.sCount[nextLine] + 1
|
||||
let spaceAfterMarker
|
||||
let adjustTab
|
||||
|
||||
// skip one optional space after '>'
|
||||
if (state.src.charCodeAt(pos) === 0x20 /* space */) {
|
||||
// ' > test '
|
||||
// ^ -- position start of line here:
|
||||
pos++
|
||||
initial++
|
||||
adjustTab = false
|
||||
spaceAfterMarker = true
|
||||
} else if (state.src.charCodeAt(pos) === 0x09 /* tab */) {
|
||||
spaceAfterMarker = true
|
||||
|
||||
if ((state.bsCount[nextLine] + initial) % 4 === 3) {
|
||||
// ' >\t test '
|
||||
// ^ -- position start of line here (tab has width===1)
|
||||
pos++
|
||||
initial++
|
||||
adjustTab = false
|
||||
} else {
|
||||
// ' >\t test '
|
||||
// ^ -- position start of line here + shift bsCount slightly
|
||||
// to make extra space appear
|
||||
adjustTab = true
|
||||
}
|
||||
} else {
|
||||
spaceAfterMarker = false
|
||||
}
|
||||
|
||||
let offset = initial
|
||||
oldBMarks.push(state.bMarks[nextLine])
|
||||
state.bMarks[nextLine] = pos
|
||||
|
||||
while (pos < max) {
|
||||
const ch = state.src.charCodeAt(pos)
|
||||
|
||||
if (isSpace(ch)) {
|
||||
if (ch === 0x09) {
|
||||
offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4
|
||||
} else {
|
||||
offset++
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
pos++
|
||||
}
|
||||
|
||||
lastLineEmpty = pos >= max
|
||||
|
||||
oldBSCount.push(state.bsCount[nextLine])
|
||||
state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0)
|
||||
|
||||
oldSCount.push(state.sCount[nextLine])
|
||||
state.sCount[nextLine] = offset - initial
|
||||
|
||||
oldTShift.push(state.tShift[nextLine])
|
||||
state.tShift[nextLine] = pos - state.bMarks[nextLine]
|
||||
continue
|
||||
}
|
||||
|
||||
// Case 2: line is not inside the blockquote, and the last line was empty.
|
||||
if (lastLineEmpty) { break }
|
||||
|
||||
// Case 3: another tag found.
|
||||
let terminate = false
|
||||
for (let i = 0, l = terminatorRules.length; i < l; i++) {
|
||||
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||
terminate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (terminate) {
|
||||
// Quirk to enforce "hard termination mode" for paragraphs;
|
||||
// normally if you call `tokenize(state, startLine, nextLine)`,
|
||||
// paragraphs will look below nextLine for paragraph continuation,
|
||||
// but if blockquote is terminated by another tag, they shouldn't
|
||||
state.lineMax = nextLine
|
||||
|
||||
if (state.blkIndent !== 0) {
|
||||
// state.blkIndent was non-zero, we now set it to zero,
|
||||
// so we need to re-calculate all offsets to appear as
|
||||
// if indent wasn't changed
|
||||
oldBMarks.push(state.bMarks[nextLine])
|
||||
oldBSCount.push(state.bsCount[nextLine])
|
||||
oldTShift.push(state.tShift[nextLine])
|
||||
oldSCount.push(state.sCount[nextLine])
|
||||
state.sCount[nextLine] -= state.blkIndent
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
oldBMarks.push(state.bMarks[nextLine])
|
||||
oldBSCount.push(state.bsCount[nextLine])
|
||||
oldTShift.push(state.tShift[nextLine])
|
||||
oldSCount.push(state.sCount[nextLine])
|
||||
|
||||
// A negative indentation means that this is a paragraph continuation
|
||||
//
|
||||
state.sCount[nextLine] = -1
|
||||
}
|
||||
|
||||
const oldIndent = state.blkIndent
|
||||
state.blkIndent = 0
|
||||
|
||||
const token_o = state.push('blockquote_open', 'blockquote', 1)
|
||||
token_o.markup = '>'
|
||||
const lines = [startLine, 0]
|
||||
token_o.map = lines
|
||||
|
||||
state.md.block.tokenize(state, startLine, nextLine)
|
||||
|
||||
const token_c = state.push('blockquote_close', 'blockquote', -1)
|
||||
token_c.markup = '>'
|
||||
|
||||
state.lineMax = oldLineMax
|
||||
state.parentType = oldParentType
|
||||
lines[1] = state.line
|
||||
|
||||
// Restore original tShift; this might not be necessary since the parser
|
||||
// has already been here, but just to make sure we can do that.
|
||||
for (let i = 0; i < oldTShift.length; i++) {
|
||||
state.bMarks[i + startLine] = oldBMarks[i]
|
||||
state.tShift[i + startLine] = oldTShift[i]
|
||||
state.sCount[i + startLine] = oldSCount[i]
|
||||
state.bsCount[i + startLine] = oldBSCount[i]
|
||||
}
|
||||
state.blkIndent = oldIndent
|
||||
|
||||
return true
|
||||
}
|
30
node_modules/markdown-it/lib/rules_block/code.mjs
generated
vendored
Normal file
30
node_modules/markdown-it/lib/rules_block/code.mjs
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Code block (4 spaces padded)
|
||||
|
||||
export default function code (state, startLine, endLine/*, silent */) {
|
||||
if (state.sCount[startLine] - state.blkIndent < 4) { return false }
|
||||
|
||||
let nextLine = startLine + 1
|
||||
let last = nextLine
|
||||
|
||||
while (nextLine < endLine) {
|
||||
if (state.isEmpty(nextLine)) {
|
||||
nextLine++
|
||||
continue
|
||||
}
|
||||
|
||||
if (state.sCount[nextLine] - state.blkIndent >= 4) {
|
||||
nextLine++
|
||||
last = nextLine
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
state.line = last
|
||||
|
||||
const token = state.push('code_block', 'code', 0)
|
||||
token.content = state.getLines(startLine, last, 4 + state.blkIndent, false) + '\n'
|
||||
token.map = [startLine, state.line]
|
||||
|
||||
return true
|
||||
}
|
94
node_modules/markdown-it/lib/rules_block/fence.mjs
generated
vendored
Normal file
94
node_modules/markdown-it/lib/rules_block/fence.mjs
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
// fences (``` lang, ~~~ lang)
|
||||
|
||||
export default function fence (state, startLine, endLine, silent) {
|
||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
let max = state.eMarks[startLine]
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
|
||||
|
||||
if (pos + 3 > max) { return false }
|
||||
|
||||
const marker = state.src.charCodeAt(pos)
|
||||
|
||||
if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
|
||||
return false
|
||||
}
|
||||
|
||||
// scan marker length
|
||||
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)
|
||||
|
||||
if (marker === 0x60 /* ` */) {
|
||||
if (params.indexOf(String.fromCharCode(marker)) >= 0) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Since start is found, we can report success here in validation mode
|
||||
if (silent) { return true }
|
||||
|
||||
// search end of block
|
||||
let nextLine = startLine
|
||||
let haveEndMarker = false
|
||||
|
||||
for (;;) {
|
||||
nextLine++
|
||||
if (nextLine >= endLine) {
|
||||
// unclosed block should be autoclosed by end of document.
|
||||
// also block seems to be autoclosed by end of parent
|
||||
break
|
||||
}
|
||||
|
||||
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
max = state.eMarks[nextLine]
|
||||
|
||||
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
|
||||
// non-empty line with negative indent should stop the list:
|
||||
// - ```
|
||||
// test
|
||||
break
|
||||
}
|
||||
|
||||
if (state.src.charCodeAt(pos) !== marker) { continue }
|
||||
|
||||
if (state.sCount[nextLine] - state.blkIndent >= 4) {
|
||||
// closing fence should be indented less than 4 spaces
|
||||
continue
|
||||
}
|
||||
|
||||
pos = state.skipChars(pos, marker)
|
||||
|
||||
// closing code fence must be at least as long as the opening one
|
||||
if (pos - mem < len) { continue }
|
||||
|
||||
// make sure tail has spaces only
|
||||
pos = state.skipSpaces(pos)
|
||||
|
||||
if (pos < max) { continue }
|
||||
|
||||
haveEndMarker = true
|
||||
// found!
|
||||
break
|
||||
}
|
||||
|
||||
// If a fence has heading spaces, they should be removed from its inner block
|
||||
len = state.sCount[startLine]
|
||||
|
||||
state.line = nextLine + (haveEndMarker ? 1 : 0)
|
||||
|
||||
const token = state.push('fence', 'code', 0)
|
||||
token.info = params
|
||||
token.content = state.getLines(startLine + 1, nextLine, len, true)
|
||||
token.markup = markup
|
||||
token.map = [startLine, state.line]
|
||||
|
||||
return true
|
||||
}
|
51
node_modules/markdown-it/lib/rules_block/heading.mjs
generated
vendored
Normal file
51
node_modules/markdown-it/lib/rules_block/heading.mjs
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// heading (#, ##, ...)
|
||||
|
||||
import { isSpace } from '../common/utils.mjs'
|
||||
|
||||
export default function heading (state, startLine, endLine, silent) {
|
||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
let max = state.eMarks[startLine]
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
|
||||
|
||||
let ch = state.src.charCodeAt(pos)
|
||||
|
||||
if (ch !== 0x23/* # */ || pos >= max) { return false }
|
||||
|
||||
// count heading level
|
||||
let level = 1
|
||||
ch = state.src.charCodeAt(++pos)
|
||||
while (ch === 0x23/* # */ && pos < max && level <= 6) {
|
||||
level++
|
||||
ch = state.src.charCodeAt(++pos)
|
||||
}
|
||||
|
||||
if (level > 6 || (pos < max && !isSpace(ch))) { return false }
|
||||
|
||||
if (silent) { return true }
|
||||
|
||||
// Let's cut tails like ' ### ' from the end of string
|
||||
|
||||
max = state.skipSpacesBack(max, pos)
|
||||
const tmp = state.skipCharsBack(max, 0x23, pos) // #
|
||||
if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) {
|
||||
max = tmp
|
||||
}
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
const token_o = state.push('heading_open', 'h' + String(level), 1)
|
||||
token_o.markup = '########'.slice(0, level)
|
||||
token_o.map = [startLine, state.line]
|
||||
|
||||
const token_i = state.push('inline', '', 0)
|
||||
token_i.content = state.src.slice(pos, max).trim()
|
||||
token_i.map = [startLine, state.line]
|
||||
token_i.children = []
|
||||
|
||||
const token_c = state.push('heading_close', 'h' + String(level), -1)
|
||||
token_c.markup = '########'.slice(0, level)
|
||||
|
||||
return true
|
||||
}
|
40
node_modules/markdown-it/lib/rules_block/hr.mjs
generated
vendored
Normal file
40
node_modules/markdown-it/lib/rules_block/hr.mjs
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Horizontal rule
|
||||
|
||||
import { isSpace } from '../common/utils.mjs'
|
||||
|
||||
export default function hr (state, startLine, endLine, silent) {
|
||||
const max = state.eMarks[startLine]
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
|
||||
|
||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
const marker = state.src.charCodeAt(pos++)
|
||||
|
||||
// Check hr marker
|
||||
if (marker !== 0x2A/* * */ &&
|
||||
marker !== 0x2D/* - */ &&
|
||||
marker !== 0x5F/* _ */) {
|
||||
return false
|
||||
}
|
||||
|
||||
// markers can be mixed with spaces, but there should be at least 3 of them
|
||||
|
||||
let cnt = 1
|
||||
while (pos < max) {
|
||||
const ch = state.src.charCodeAt(pos++)
|
||||
if (ch !== marker && !isSpace(ch)) { return false }
|
||||
if (ch === marker) { cnt++ }
|
||||
}
|
||||
|
||||
if (cnt < 3) { return false }
|
||||
|
||||
if (silent) { return true }
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
const token = state.push('hr', 'hr', 0)
|
||||
token.map = [startLine, state.line]
|
||||
token.markup = Array(cnt + 1).join(String.fromCharCode(marker))
|
||||
|
||||
return true
|
||||
}
|
69
node_modules/markdown-it/lib/rules_block/html_block.mjs
generated
vendored
Normal file
69
node_modules/markdown-it/lib/rules_block/html_block.mjs
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// HTML block
|
||||
|
||||
import block_names from '../common/html_blocks.mjs'
|
||||
import { HTML_OPEN_CLOSE_TAG_RE } from '../common/html_re.mjs'
|
||||
|
||||
// An array of opening and corresponding closing sequences for html tags,
|
||||
// last argument defines whether it can terminate a paragraph or not
|
||||
//
|
||||
const HTML_SEQUENCES = [
|
||||
[/^<(script|pre|style|textarea)(?=(\s|>|$))/i, /<\/(script|pre|style|textarea)>/i, true],
|
||||
[/^<!--/, /-->/, true],
|
||||
[/^<\?/, /\?>/, true],
|
||||
[/^<![A-Z]/, />/, true],
|
||||
[/^<!\[CDATA\[/, /\]\]>/, true],
|
||||
[new RegExp('^</?(' + block_names.join('|') + ')(?=(\\s|/?>|$))', 'i'), /^$/, true],
|
||||
[new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false]
|
||||
]
|
||||
|
||||
export default function html_block (state, startLine, endLine, silent) {
|
||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
let max = state.eMarks[startLine]
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
|
||||
|
||||
if (!state.md.options.html) { return false }
|
||||
|
||||
if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false }
|
||||
|
||||
let lineText = state.src.slice(pos, max)
|
||||
|
||||
let i = 0
|
||||
for (; i < HTML_SEQUENCES.length; i++) {
|
||||
if (HTML_SEQUENCES[i][0].test(lineText)) { break }
|
||||
}
|
||||
if (i === HTML_SEQUENCES.length) { return false }
|
||||
|
||||
if (silent) {
|
||||
// true if this sequence can be a terminator, false otherwise
|
||||
return HTML_SEQUENCES[i][2]
|
||||
}
|
||||
|
||||
let nextLine = startLine + 1
|
||||
|
||||
// If we are here - we detected HTML block.
|
||||
// Let's roll down till block end.
|
||||
if (!HTML_SEQUENCES[i][1].test(lineText)) {
|
||||
for (; nextLine < endLine; nextLine++) {
|
||||
if (state.sCount[nextLine] < state.blkIndent) { break }
|
||||
|
||||
pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
max = state.eMarks[nextLine]
|
||||
lineText = state.src.slice(pos, max)
|
||||
|
||||
if (HTML_SEQUENCES[i][1].test(lineText)) {
|
||||
if (lineText.length !== 0) { nextLine++ }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.line = nextLine
|
||||
|
||||
const token = state.push('html_block', '', 0)
|
||||
token.map = [startLine, nextLine]
|
||||
token.content = state.getLines(startLine, nextLine, state.blkIndent, true)
|
||||
|
||||
return true
|
||||
}
|
82
node_modules/markdown-it/lib/rules_block/lheading.mjs
generated
vendored
Normal file
82
node_modules/markdown-it/lib/rules_block/lheading.mjs
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// lheading (---, ===)
|
||||
|
||||
export default function lheading (state, startLine, endLine/*, silent */) {
|
||||
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
|
||||
|
||||
const oldParentType = state.parentType
|
||||
state.parentType = 'paragraph' // use paragraph to match terminatorRules
|
||||
|
||||
// jump line-by-line until empty one or EOF
|
||||
let level = 0
|
||||
let marker
|
||||
let nextLine = startLine + 1
|
||||
|
||||
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||
// this would be a code block normally, but after paragraph
|
||||
// it's considered a lazy continuation regardless of what's there
|
||||
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
|
||||
|
||||
//
|
||||
// Check for underline in setext header
|
||||
//
|
||||
if (state.sCount[nextLine] >= state.blkIndent) {
|
||||
let pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
const max = state.eMarks[nextLine]
|
||||
|
||||
if (pos < max) {
|
||||
marker = state.src.charCodeAt(pos)
|
||||
|
||||
if (marker === 0x2D/* - */ || marker === 0x3D/* = */) {
|
||||
pos = state.skipChars(pos, marker)
|
||||
pos = state.skipSpaces(pos)
|
||||
|
||||
if (pos >= max) {
|
||||
level = (marker === 0x3D/* = */ ? 1 : 2)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// quirk for blockquotes, this line should already be checked by that rule
|
||||
if (state.sCount[nextLine] < 0) { continue }
|
||||
|
||||
// Some tags can terminate paragraph without empty line.
|
||||
let terminate = false
|
||||
for (let i = 0, l = terminatorRules.length; i < l; i++) {
|
||||
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||
terminate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (terminate) { break }
|
||||
}
|
||||
|
||||
if (!level) {
|
||||
// Didn't find valid underline
|
||||
return false
|
||||
}
|
||||
|
||||
const content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
|
||||
|
||||
state.line = nextLine + 1
|
||||
|
||||
const token_o = state.push('heading_open', 'h' + String(level), 1)
|
||||
token_o.markup = String.fromCharCode(marker)
|
||||
token_o.map = [startLine, state.line]
|
||||
|
||||
const token_i = state.push('inline', '', 0)
|
||||
token_i.content = content
|
||||
token_i.map = [startLine, state.line - 1]
|
||||
token_i.children = []
|
||||
|
||||
const token_c = state.push('heading_close', 'h' + String(level), -1)
|
||||
token_c.markup = String.fromCharCode(marker)
|
||||
|
||||
state.parentType = oldParentType
|
||||
|
||||
return true
|
||||
}
|
331
node_modules/markdown-it/lib/rules_block/list.mjs
generated
vendored
Normal file
331
node_modules/markdown-it/lib/rules_block/list.mjs
generated
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
// Lists
|
||||
|
||||
import { isSpace } from '../common/utils.mjs'
|
||||
|
||||
// Search `[-+*][\n ]`, returns next pos after marker on success
|
||||
// or -1 on fail.
|
||||
function skipBulletListMarker (state, startLine) {
|
||||
const max = state.eMarks[startLine]
|
||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
|
||||
const marker = state.src.charCodeAt(pos++)
|
||||
// Check bullet
|
||||
if (marker !== 0x2A/* * */ &&
|
||||
marker !== 0x2D/* - */ &&
|
||||
marker !== 0x2B/* + */) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if (pos < max) {
|
||||
const ch = state.src.charCodeAt(pos)
|
||||
|
||||
if (!isSpace(ch)) {
|
||||
// " -test " - is not a list item
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
// Search `\d+[.)][\n ]`, returns next pos after marker on success
|
||||
// or -1 on fail.
|
||||
function skipOrderedListMarker (state, startLine) {
|
||||
const start = state.bMarks[startLine] + state.tShift[startLine]
|
||||
const max = state.eMarks[startLine]
|
||||
let pos = start
|
||||
|
||||
// List marker should have at least 2 chars (digit + dot)
|
||||
if (pos + 1 >= max) { return -1 }
|
||||
|
||||
let ch = state.src.charCodeAt(pos++)
|
||||
|
||||
if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1 }
|
||||
|
||||
for (;;) {
|
||||
// EOL -> fail
|
||||
if (pos >= max) { return -1 }
|
||||
|
||||
ch = state.src.charCodeAt(pos++)
|
||||
|
||||
if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) {
|
||||
// List marker should have no more than 9 digits
|
||||
// (prevents integer overflow in browsers)
|
||||
if (pos - start >= 10) { return -1 }
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// found valid marker
|
||||
if (ch === 0x29/* ) */ || ch === 0x2e/* . */) {
|
||||
break
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
if (pos < max) {
|
||||
ch = state.src.charCodeAt(pos)
|
||||
|
||||
if (!isSpace(ch)) {
|
||||
// " 1.test " - is not a list item
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
function markTightParagraphs (state, idx) {
|
||||
const level = state.level + 2
|
||||
|
||||
for (let i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
|
||||
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
|
||||
state.tokens[i + 2].hidden = true
|
||||
state.tokens[i].hidden = true
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function list (state, startLine, endLine, silent) {
|
||||
let max, pos, start, token
|
||||
let nextLine = startLine
|
||||
let tight = true
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[nextLine] - state.blkIndent >= 4) { return false }
|
||||
|
||||
// Special case:
|
||||
// - item 1
|
||||
// - item 2
|
||||
// - item 3
|
||||
// - item 4
|
||||
// - this one is a paragraph continuation
|
||||
if (state.listIndent >= 0 &&
|
||||
state.sCount[nextLine] - state.listIndent >= 4 &&
|
||||
state.sCount[nextLine] < state.blkIndent) {
|
||||
return false
|
||||
}
|
||||
|
||||
let isTerminatingParagraph = false
|
||||
|
||||
// limit conditions when list can interrupt
|
||||
// a paragraph (validation mode only)
|
||||
if (silent && state.parentType === 'paragraph') {
|
||||
// Next list item should still terminate previous list item;
|
||||
//
|
||||
// This code can fail if plugins use blkIndent as well as lists,
|
||||
// but I hope the spec gets fixed long before that happens.
|
||||
//
|
||||
if (state.sCount[nextLine] >= state.blkIndent) {
|
||||
isTerminatingParagraph = true
|
||||
}
|
||||
}
|
||||
|
||||
// Detect list type and position after marker
|
||||
let isOrdered
|
||||
let markerValue
|
||||
let posAfterMarker
|
||||
if ((posAfterMarker = skipOrderedListMarker(state, nextLine)) >= 0) {
|
||||
isOrdered = true
|
||||
start = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
markerValue = Number(state.src.slice(start, posAfterMarker - 1))
|
||||
|
||||
// If we're starting a new ordered list right after
|
||||
// a paragraph, it should start with 1.
|
||||
if (isTerminatingParagraph && markerValue !== 1) return false
|
||||
} else if ((posAfterMarker = skipBulletListMarker(state, nextLine)) >= 0) {
|
||||
isOrdered = false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we're starting a new unordered list right after
|
||||
// a paragraph, first line should not be empty.
|
||||
if (isTerminatingParagraph) {
|
||||
if (state.skipSpaces(posAfterMarker) >= state.eMarks[nextLine]) return false
|
||||
}
|
||||
|
||||
// For validation mode we can terminate immediately
|
||||
if (silent) { return true }
|
||||
|
||||
// We should terminate list on style change. Remember first one to compare.
|
||||
const markerCharCode = state.src.charCodeAt(posAfterMarker - 1)
|
||||
|
||||
// Start list
|
||||
const listTokIdx = state.tokens.length
|
||||
|
||||
if (isOrdered) {
|
||||
token = state.push('ordered_list_open', 'ol', 1)
|
||||
if (markerValue !== 1) {
|
||||
token.attrs = [['start', markerValue]]
|
||||
}
|
||||
} else {
|
||||
token = state.push('bullet_list_open', 'ul', 1)
|
||||
}
|
||||
|
||||
const listLines = [nextLine, 0]
|
||||
token.map = listLines
|
||||
token.markup = String.fromCharCode(markerCharCode)
|
||||
|
||||
//
|
||||
// Iterate list items
|
||||
//
|
||||
|
||||
let prevEmptyEnd = false
|
||||
const terminatorRules = state.md.block.ruler.getRules('list')
|
||||
|
||||
const oldParentType = state.parentType
|
||||
state.parentType = 'list'
|
||||
|
||||
while (nextLine < endLine) {
|
||||
pos = posAfterMarker
|
||||
max = state.eMarks[nextLine]
|
||||
|
||||
const initial = state.sCount[nextLine] + posAfterMarker - (state.bMarks[nextLine] + state.tShift[nextLine])
|
||||
let offset = initial
|
||||
|
||||
while (pos < max) {
|
||||
const ch = state.src.charCodeAt(pos)
|
||||
|
||||
if (ch === 0x09) {
|
||||
offset += 4 - (offset + state.bsCount[nextLine]) % 4
|
||||
} else if (ch === 0x20) {
|
||||
offset++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
pos++
|
||||
}
|
||||
|
||||
const contentStart = pos
|
||||
let indentAfterMarker
|
||||
|
||||
if (contentStart >= max) {
|
||||
// trimming space in "- \n 3" case, indent is 1 here
|
||||
indentAfterMarker = 1
|
||||
} else {
|
||||
indentAfterMarker = offset - initial
|
||||
}
|
||||
|
||||
// If we have more than 4 spaces, the indent is 1
|
||||
// (the rest is just indented code block)
|
||||
if (indentAfterMarker > 4) { indentAfterMarker = 1 }
|
||||
|
||||
// " - test"
|
||||
// ^^^^^ - calculating total length of this thing
|
||||
const indent = initial + indentAfterMarker
|
||||
|
||||
// Run subparser & write tokens
|
||||
token = state.push('list_item_open', 'li', 1)
|
||||
token.markup = String.fromCharCode(markerCharCode)
|
||||
const itemLines = [nextLine, 0]
|
||||
token.map = itemLines
|
||||
if (isOrdered) {
|
||||
token.info = state.src.slice(start, posAfterMarker - 1)
|
||||
}
|
||||
|
||||
// change current state, then restore it after parser subcall
|
||||
const oldTight = state.tight
|
||||
const oldTShift = state.tShift[nextLine]
|
||||
const oldSCount = state.sCount[nextLine]
|
||||
|
||||
// - example list
|
||||
// ^ listIndent position will be here
|
||||
// ^ blkIndent position will be here
|
||||
//
|
||||
const oldListIndent = state.listIndent
|
||||
state.listIndent = state.blkIndent
|
||||
state.blkIndent = indent
|
||||
|
||||
state.tight = true
|
||||
state.tShift[nextLine] = contentStart - state.bMarks[nextLine]
|
||||
state.sCount[nextLine] = offset
|
||||
|
||||
if (contentStart >= max && state.isEmpty(nextLine + 1)) {
|
||||
// workaround for this case
|
||||
// (list item is empty, list terminates before "foo"):
|
||||
// ~~~~~~~~
|
||||
// -
|
||||
//
|
||||
// foo
|
||||
// ~~~~~~~~
|
||||
state.line = Math.min(state.line + 2, endLine)
|
||||
} else {
|
||||
state.md.block.tokenize(state, nextLine, endLine, true)
|
||||
}
|
||||
|
||||
// If any of list item is tight, mark list as tight
|
||||
if (!state.tight || prevEmptyEnd) {
|
||||
tight = false
|
||||
}
|
||||
// Item become loose if finish with empty line,
|
||||
// but we should filter last element, because it means list finish
|
||||
prevEmptyEnd = (state.line - nextLine) > 1 && state.isEmpty(state.line - 1)
|
||||
|
||||
state.blkIndent = state.listIndent
|
||||
state.listIndent = oldListIndent
|
||||
state.tShift[nextLine] = oldTShift
|
||||
state.sCount[nextLine] = oldSCount
|
||||
state.tight = oldTight
|
||||
|
||||
token = state.push('list_item_close', 'li', -1)
|
||||
token.markup = String.fromCharCode(markerCharCode)
|
||||
|
||||
nextLine = state.line
|
||||
itemLines[1] = nextLine
|
||||
|
||||
if (nextLine >= endLine) { break }
|
||||
|
||||
//
|
||||
// Try to check if list is terminated or continued.
|
||||
//
|
||||
if (state.sCount[nextLine] < state.blkIndent) { break }
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[nextLine] - state.blkIndent >= 4) { break }
|
||||
|
||||
// fail if terminating block found
|
||||
let terminate = false
|
||||
for (let i = 0, l = terminatorRules.length; i < l; i++) {
|
||||
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||
terminate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (terminate) { break }
|
||||
|
||||
// fail if list has another type
|
||||
if (isOrdered) {
|
||||
posAfterMarker = skipOrderedListMarker(state, nextLine)
|
||||
if (posAfterMarker < 0) { break }
|
||||
start = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
} else {
|
||||
posAfterMarker = skipBulletListMarker(state, nextLine)
|
||||
if (posAfterMarker < 0) { break }
|
||||
}
|
||||
|
||||
if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break }
|
||||
}
|
||||
|
||||
// Finalize list
|
||||
if (isOrdered) {
|
||||
token = state.push('ordered_list_close', 'ol', -1)
|
||||
} else {
|
||||
token = state.push('bullet_list_close', 'ul', -1)
|
||||
}
|
||||
token.markup = String.fromCharCode(markerCharCode)
|
||||
|
||||
listLines[1] = nextLine
|
||||
state.line = nextLine
|
||||
|
||||
state.parentType = oldParentType
|
||||
|
||||
// mark paragraphs tight if needed
|
||||
if (tight) {
|
||||
markTightParagraphs(state, listTokIdx)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
46
node_modules/markdown-it/lib/rules_block/paragraph.mjs
generated
vendored
Normal file
46
node_modules/markdown-it/lib/rules_block/paragraph.mjs
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Paragraph
|
||||
|
||||
export default function paragraph (state, startLine, endLine) {
|
||||
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||
const oldParentType = state.parentType
|
||||
let nextLine = startLine + 1
|
||||
state.parentType = 'paragraph'
|
||||
|
||||
// jump line-by-line until empty one or EOF
|
||||
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||
// this would be a code block normally, but after paragraph
|
||||
// it's considered a lazy continuation regardless of what's there
|
||||
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
|
||||
|
||||
// quirk for blockquotes, this line should already be checked by that rule
|
||||
if (state.sCount[nextLine] < 0) { continue }
|
||||
|
||||
// Some tags can terminate paragraph without empty line.
|
||||
let terminate = false
|
||||
for (let i = 0, l = terminatorRules.length; i < l; i++) {
|
||||
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||
terminate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (terminate) { break }
|
||||
}
|
||||
|
||||
const content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
|
||||
|
||||
state.line = nextLine
|
||||
|
||||
const token_o = state.push('paragraph_open', 'p', 1)
|
||||
token_o.map = [startLine, state.line]
|
||||
|
||||
const token_i = state.push('inline', '', 0)
|
||||
token_i.content = content
|
||||
token_i.map = [startLine, state.line]
|
||||
token_i.children = []
|
||||
|
||||
state.push('paragraph_close', 'p', -1)
|
||||
|
||||
state.parentType = oldParentType
|
||||
|
||||
return true
|
||||
}
|
212
node_modules/markdown-it/lib/rules_block/reference.mjs
generated
vendored
Normal file
212
node_modules/markdown-it/lib/rules_block/reference.mjs
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
import { isSpace, normalizeReference } from '../common/utils.mjs'
|
||||
|
||||
export default function reference (state, startLine, _endLine, silent) {
|
||||
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
let max = state.eMarks[startLine]
|
||||
let nextLine = startLine + 1
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
|
||||
|
||||
if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false }
|
||||
|
||||
function getNextLine (nextLine) {
|
||||
const endLine = state.lineMax
|
||||
|
||||
if (nextLine >= endLine || state.isEmpty(nextLine)) {
|
||||
// empty line or end of input
|
||||
return null
|
||||
}
|
||||
|
||||
let isContinuation = false
|
||||
|
||||
// this would be a code block normally, but after paragraph
|
||||
// it's considered a lazy continuation regardless of what's there
|
||||
if (state.sCount[nextLine] - state.blkIndent > 3) { isContinuation = true }
|
||||
|
||||
// quirk for blockquotes, this line should already be checked by that rule
|
||||
if (state.sCount[nextLine] < 0) { isContinuation = true }
|
||||
|
||||
if (!isContinuation) {
|
||||
const terminatorRules = state.md.block.ruler.getRules('reference')
|
||||
const oldParentType = state.parentType
|
||||
state.parentType = 'reference'
|
||||
|
||||
// Some tags can terminate paragraph without empty line.
|
||||
let terminate = false
|
||||
for (let i = 0, l = terminatorRules.length; i < l; i++) {
|
||||
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||
terminate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
state.parentType = oldParentType
|
||||
if (terminate) {
|
||||
// terminated by another block
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
const max = state.eMarks[nextLine]
|
||||
|
||||
// max + 1 explicitly includes the newline
|
||||
return state.src.slice(pos, max + 1)
|
||||
}
|
||||
|
||||
let str = state.src.slice(pos, max + 1)
|
||||
|
||||
max = str.length
|
||||
let labelEnd = -1
|
||||
|
||||
for (pos = 1; pos < max; pos++) {
|
||||
const ch = str.charCodeAt(pos)
|
||||
if (ch === 0x5B /* [ */) {
|
||||
return false
|
||||
} else if (ch === 0x5D /* ] */) {
|
||||
labelEnd = pos
|
||||
break
|
||||
} else if (ch === 0x0A /* \n */) {
|
||||
const lineContent = getNextLine(nextLine)
|
||||
if (lineContent !== null) {
|
||||
str += lineContent
|
||||
max = str.length
|
||||
nextLine++
|
||||
}
|
||||
} else if (ch === 0x5C /* \ */) {
|
||||
pos++
|
||||
if (pos < max && str.charCodeAt(pos) === 0x0A) {
|
||||
const lineContent = getNextLine(nextLine)
|
||||
if (lineContent !== null) {
|
||||
str += lineContent
|
||||
max = str.length
|
||||
nextLine++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false }
|
||||
|
||||
// [label]: destination 'title'
|
||||
// ^^^ skip optional whitespace here
|
||||
for (pos = labelEnd + 2; pos < max; pos++) {
|
||||
const ch = str.charCodeAt(pos)
|
||||
if (ch === 0x0A) {
|
||||
const lineContent = getNextLine(nextLine)
|
||||
if (lineContent !== null) {
|
||||
str += lineContent
|
||||
max = str.length
|
||||
nextLine++
|
||||
}
|
||||
} else if (isSpace(ch)) {
|
||||
/* eslint no-empty:0 */
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// [label]: destination 'title'
|
||||
// ^^^^^^^^^^^ parse this
|
||||
const destRes = state.md.helpers.parseLinkDestination(str, pos, max)
|
||||
if (!destRes.ok) { return false }
|
||||
|
||||
const href = state.md.normalizeLink(destRes.str)
|
||||
if (!state.md.validateLink(href)) { return false }
|
||||
|
||||
pos = destRes.pos
|
||||
|
||||
// save cursor state, we could require to rollback later
|
||||
const destEndPos = pos
|
||||
const destEndLineNo = nextLine
|
||||
|
||||
// [label]: destination 'title'
|
||||
// ^^^ skipping those spaces
|
||||
const start = pos
|
||||
for (; pos < max; pos++) {
|
||||
const ch = str.charCodeAt(pos)
|
||||
if (ch === 0x0A) {
|
||||
const lineContent = getNextLine(nextLine)
|
||||
if (lineContent !== null) {
|
||||
str += lineContent
|
||||
max = str.length
|
||||
nextLine++
|
||||
}
|
||||
} else if (isSpace(ch)) {
|
||||
/* eslint no-empty:0 */
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// [label]: destination 'title'
|
||||
// ^^^^^^^ parse this
|
||||
let titleRes = state.md.helpers.parseLinkTitle(str, pos, max)
|
||||
while (titleRes.can_continue) {
|
||||
const lineContent = getNextLine(nextLine)
|
||||
if (lineContent === null) break
|
||||
str += lineContent
|
||||
pos = max
|
||||
max = str.length
|
||||
nextLine++
|
||||
titleRes = state.md.helpers.parseLinkTitle(str, pos, max, titleRes)
|
||||
}
|
||||
let title
|
||||
|
||||
if (pos < max && start !== pos && titleRes.ok) {
|
||||
title = titleRes.str
|
||||
pos = titleRes.pos
|
||||
} else {
|
||||
title = ''
|
||||
pos = destEndPos
|
||||
nextLine = destEndLineNo
|
||||
}
|
||||
|
||||
// skip trailing spaces until the rest of the line
|
||||
while (pos < max) {
|
||||
const ch = str.charCodeAt(pos)
|
||||
if (!isSpace(ch)) { break }
|
||||
pos++
|
||||
}
|
||||
|
||||
if (pos < max && str.charCodeAt(pos) !== 0x0A) {
|
||||
if (title) {
|
||||
// garbage at the end of the line after title,
|
||||
// but it could still be a valid reference if we roll back
|
||||
title = ''
|
||||
pos = destEndPos
|
||||
nextLine = destEndLineNo
|
||||
while (pos < max) {
|
||||
const ch = str.charCodeAt(pos)
|
||||
if (!isSpace(ch)) { break }
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pos < max && str.charCodeAt(pos) !== 0x0A) {
|
||||
// garbage at the end of the line
|
||||
return false
|
||||
}
|
||||
|
||||
const label = normalizeReference(str.slice(1, labelEnd))
|
||||
if (!label) {
|
||||
// CommonMark 0.20 disallows empty labels
|
||||
return false
|
||||
}
|
||||
|
||||
// Reference can not terminate anything. This check is for safety only.
|
||||
/* istanbul ignore if */
|
||||
if (silent) { return true }
|
||||
|
||||
if (typeof state.env.references === 'undefined') {
|
||||
state.env.references = {}
|
||||
}
|
||||
if (typeof state.env.references[label] === 'undefined') {
|
||||
state.env.references[label] = { title, href }
|
||||
}
|
||||
|
||||
state.line = nextLine
|
||||
return true
|
||||
}
|
220
node_modules/markdown-it/lib/rules_block/state_block.mjs
generated
vendored
Normal file
220
node_modules/markdown-it/lib/rules_block/state_block.mjs
generated
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
// Parser state class
|
||||
|
||||
import Token from '../token.mjs'
|
||||
import { isSpace } from '../common/utils.mjs'
|
||||
|
||||
function StateBlock (src, md, env, tokens) {
|
||||
this.src = src
|
||||
|
||||
// link to parser instance
|
||||
this.md = md
|
||||
|
||||
this.env = env
|
||||
|
||||
//
|
||||
// Internal state vartiables
|
||||
//
|
||||
|
||||
this.tokens = tokens
|
||||
|
||||
this.bMarks = [] // line begin offsets for fast jumps
|
||||
this.eMarks = [] // line end offsets for fast jumps
|
||||
this.tShift = [] // offsets of the first non-space characters (tabs not expanded)
|
||||
this.sCount = [] // indents for each line (tabs expanded)
|
||||
|
||||
// An amount of virtual spaces (tabs expanded) between beginning
|
||||
// of each line (bMarks) and real beginning of that line.
|
||||
//
|
||||
// It exists only as a hack because blockquotes override bMarks
|
||||
// losing information in the process.
|
||||
//
|
||||
// It's used only when expanding tabs, you can think about it as
|
||||
// an initial tab length, e.g. bsCount=21 applied to string `\t123`
|
||||
// means first tab should be expanded to 4-21%4 === 3 spaces.
|
||||
//
|
||||
this.bsCount = []
|
||||
|
||||
// block parser variables
|
||||
|
||||
// required block content indent (for example, if we are
|
||||
// inside a list, it would be positioned after list marker)
|
||||
this.blkIndent = 0
|
||||
this.line = 0 // line index in src
|
||||
this.lineMax = 0 // lines count
|
||||
this.tight = false // loose/tight mode for lists
|
||||
this.ddIndent = -1 // indent of the current dd block (-1 if there isn't any)
|
||||
this.listIndent = -1 // indent of the current list block (-1 if there isn't any)
|
||||
|
||||
// can be 'blockquote', 'list', 'root', 'paragraph' or 'reference'
|
||||
// used in lists to determine if they interrupt a paragraph
|
||||
this.parentType = 'root'
|
||||
|
||||
this.level = 0
|
||||
|
||||
// Create caches
|
||||
// Generate markers.
|
||||
const s = this.src
|
||||
|
||||
for (let start = 0, pos = 0, indent = 0, offset = 0, len = s.length, indent_found = false; pos < len; pos++) {
|
||||
const ch = s.charCodeAt(pos)
|
||||
|
||||
if (!indent_found) {
|
||||
if (isSpace(ch)) {
|
||||
indent++
|
||||
|
||||
if (ch === 0x09) {
|
||||
offset += 4 - offset % 4
|
||||
} else {
|
||||
offset++
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
indent_found = true
|
||||
}
|
||||
}
|
||||
|
||||
if (ch === 0x0A || pos === len - 1) {
|
||||
if (ch !== 0x0A) { pos++ }
|
||||
this.bMarks.push(start)
|
||||
this.eMarks.push(pos)
|
||||
this.tShift.push(indent)
|
||||
this.sCount.push(offset)
|
||||
this.bsCount.push(0)
|
||||
|
||||
indent_found = false
|
||||
indent = 0
|
||||
offset = 0
|
||||
start = pos + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Push fake entry to simplify cache bounds checks
|
||||
this.bMarks.push(s.length)
|
||||
this.eMarks.push(s.length)
|
||||
this.tShift.push(0)
|
||||
this.sCount.push(0)
|
||||
this.bsCount.push(0)
|
||||
|
||||
this.lineMax = this.bMarks.length - 1 // don't count last fake line
|
||||
}
|
||||
|
||||
// Push new token to "stream".
|
||||
//
|
||||
StateBlock.prototype.push = function (type, tag, nesting) {
|
||||
const token = new Token(type, tag, nesting)
|
||||
token.block = true
|
||||
|
||||
if (nesting < 0) this.level-- // closing tag
|
||||
token.level = this.level
|
||||
if (nesting > 0) this.level++ // opening tag
|
||||
|
||||
this.tokens.push(token)
|
||||
return token
|
||||
}
|
||||
|
||||
StateBlock.prototype.isEmpty = function isEmpty (line) {
|
||||
return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]
|
||||
}
|
||||
|
||||
StateBlock.prototype.skipEmptyLines = function skipEmptyLines (from) {
|
||||
for (let max = this.lineMax; from < max; from++) {
|
||||
if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return from
|
||||
}
|
||||
|
||||
// Skip spaces from given position.
|
||||
StateBlock.prototype.skipSpaces = function skipSpaces (pos) {
|
||||
for (let max = this.src.length; pos < max; pos++) {
|
||||
const ch = this.src.charCodeAt(pos)
|
||||
if (!isSpace(ch)) { break }
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
// Skip spaces from given position in reverse.
|
||||
StateBlock.prototype.skipSpacesBack = function skipSpacesBack (pos, min) {
|
||||
if (pos <= min) { return pos }
|
||||
|
||||
while (pos > min) {
|
||||
if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1 }
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
// Skip char codes from given position
|
||||
StateBlock.prototype.skipChars = function skipChars (pos, code) {
|
||||
for (let max = this.src.length; pos < max; pos++) {
|
||||
if (this.src.charCodeAt(pos) !== code) { break }
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
// Skip char codes reverse from given position - 1
|
||||
StateBlock.prototype.skipCharsBack = function skipCharsBack (pos, code, min) {
|
||||
if (pos <= min) { return pos }
|
||||
|
||||
while (pos > min) {
|
||||
if (code !== this.src.charCodeAt(--pos)) { return pos + 1 }
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
// cut lines range from source.
|
||||
StateBlock.prototype.getLines = function getLines (begin, end, indent, keepLastLF) {
|
||||
if (begin >= end) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const queue = new Array(end - begin)
|
||||
|
||||
for (let i = 0, line = begin; line < end; line++, i++) {
|
||||
let lineIndent = 0
|
||||
const lineStart = this.bMarks[line]
|
||||
let first = lineStart
|
||||
let last
|
||||
|
||||
if (line + 1 < end || keepLastLF) {
|
||||
// No need for bounds check because we have fake entry on tail.
|
||||
last = this.eMarks[line] + 1
|
||||
} else {
|
||||
last = this.eMarks[line]
|
||||
}
|
||||
|
||||
while (first < last && lineIndent < indent) {
|
||||
const ch = this.src.charCodeAt(first)
|
||||
|
||||
if (isSpace(ch)) {
|
||||
if (ch === 0x09) {
|
||||
lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4
|
||||
} else {
|
||||
lineIndent++
|
||||
}
|
||||
} else if (first - lineStart < this.tShift[line]) {
|
||||
// patched tShift masked characters to look like spaces (blockquotes, list markers)
|
||||
lineIndent++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
first++
|
||||
}
|
||||
|
||||
if (lineIndent > indent) {
|
||||
// partially expanding tabs in code blocks, e.g '\t\tfoobar'
|
||||
// with indent=2 becomes ' \tfoobar'
|
||||
queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last)
|
||||
} else {
|
||||
queue[i] = this.src.slice(first, last)
|
||||
}
|
||||
}
|
||||
|
||||
return queue.join('')
|
||||
}
|
||||
|
||||
// re-export Token class to use in block rules
|
||||
StateBlock.prototype.Token = Token
|
||||
|
||||
export default StateBlock
|
228
node_modules/markdown-it/lib/rules_block/table.mjs
generated
vendored
Normal file
228
node_modules/markdown-it/lib/rules_block/table.mjs
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
// GFM table, https://github.github.com/gfm/#tables-extension-
|
||||
|
||||
import { isSpace } from '../common/utils.mjs'
|
||||
|
||||
// Limit the amount of empty autocompleted cells in a table,
|
||||
// see https://github.com/markdown-it/markdown-it/issues/1000,
|
||||
//
|
||||
// Both pulldown-cmark and commonmark-hs limit the number of cells this way to ~200k.
|
||||
// We set it to 65k, which can expand user input by a factor of x370
|
||||
// (256x256 square is 1.8kB expanded into 650kB).
|
||||
const MAX_AUTOCOMPLETED_CELLS = 0x10000
|
||||
|
||||
function getLine (state, line) {
|
||||
const pos = state.bMarks[line] + state.tShift[line]
|
||||
const max = state.eMarks[line]
|
||||
|
||||
return state.src.slice(pos, max)
|
||||
}
|
||||
|
||||
function escapedSplit (str) {
|
||||
const result = []
|
||||
const max = str.length
|
||||
|
||||
let pos = 0
|
||||
let ch = str.charCodeAt(pos)
|
||||
let isEscaped = false
|
||||
let lastPos = 0
|
||||
let current = ''
|
||||
|
||||
while (pos < max) {
|
||||
if (ch === 0x7c/* | */) {
|
||||
if (!isEscaped) {
|
||||
// pipe separating cells, '|'
|
||||
result.push(current + str.substring(lastPos, pos))
|
||||
current = ''
|
||||
lastPos = pos + 1
|
||||
} else {
|
||||
// escaped pipe, '\|'
|
||||
current += str.substring(lastPos, pos - 1)
|
||||
lastPos = pos
|
||||
}
|
||||
}
|
||||
|
||||
isEscaped = (ch === 0x5c/* \ */)
|
||||
pos++
|
||||
|
||||
ch = str.charCodeAt(pos)
|
||||
}
|
||||
|
||||
result.push(current + str.substring(lastPos))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export default function table (state, startLine, endLine, silent) {
|
||||
// should have at least two lines
|
||||
if (startLine + 2 > endLine) { return false }
|
||||
|
||||
let nextLine = startLine + 1
|
||||
|
||||
if (state.sCount[nextLine] < state.blkIndent) { return false }
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[nextLine] - state.blkIndent >= 4) { return false }
|
||||
|
||||
// first character of the second line should be '|', '-', ':',
|
||||
// and no other characters are allowed but spaces;
|
||||
// basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp
|
||||
|
||||
let pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
if (pos >= state.eMarks[nextLine]) { return false }
|
||||
|
||||
const firstCh = state.src.charCodeAt(pos++)
|
||||
if (firstCh !== 0x7C/* | */ && firstCh !== 0x2D/* - */ && firstCh !== 0x3A/* : */) { return false }
|
||||
|
||||
if (pos >= state.eMarks[nextLine]) { return false }
|
||||
|
||||
const secondCh = state.src.charCodeAt(pos++)
|
||||
if (secondCh !== 0x7C/* | */ && secondCh !== 0x2D/* - */ && secondCh !== 0x3A/* : */ && !isSpace(secondCh)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// if first character is '-', then second character must not be a space
|
||||
// (due to parsing ambiguity with list)
|
||||
if (firstCh === 0x2D/* - */ && isSpace(secondCh)) { return false }
|
||||
|
||||
while (pos < state.eMarks[nextLine]) {
|
||||
const ch = state.src.charCodeAt(pos)
|
||||
|
||||
if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false }
|
||||
|
||||
pos++
|
||||
}
|
||||
|
||||
let lineText = getLine(state, startLine + 1)
|
||||
let columns = lineText.split('|')
|
||||
const aligns = []
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
const t = columns[i].trim()
|
||||
if (!t) {
|
||||
// allow empty columns before and after table, but not in between columns;
|
||||
// e.g. allow ` |---| `, disallow ` ---||--- `
|
||||
if (i === 0 || i === columns.length - 1) {
|
||||
continue
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^:?-+:?$/.test(t)) { return false }
|
||||
if (t.charCodeAt(t.length - 1) === 0x3A/* : */) {
|
||||
aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right')
|
||||
} else if (t.charCodeAt(0) === 0x3A/* : */) {
|
||||
aligns.push('left')
|
||||
} else {
|
||||
aligns.push('')
|
||||
}
|
||||
}
|
||||
|
||||
lineText = getLine(state, startLine).trim()
|
||||
if (lineText.indexOf('|') === -1) { return false }
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
|
||||
columns = escapedSplit(lineText)
|
||||
if (columns.length && columns[0] === '') columns.shift()
|
||||
if (columns.length && columns[columns.length - 1] === '') columns.pop()
|
||||
|
||||
// header row will define an amount of columns in the entire table,
|
||||
// and align row should be exactly the same (the rest of the rows can differ)
|
||||
const columnCount = columns.length
|
||||
if (columnCount === 0 || columnCount !== aligns.length) { return false }
|
||||
|
||||
if (silent) { return true }
|
||||
|
||||
const oldParentType = state.parentType
|
||||
state.parentType = 'table'
|
||||
|
||||
// use 'blockquote' lists for termination because it's
|
||||
// the most similar to tables
|
||||
const terminatorRules = state.md.block.ruler.getRules('blockquote')
|
||||
|
||||
const token_to = state.push('table_open', 'table', 1)
|
||||
const tableLines = [startLine, 0]
|
||||
token_to.map = tableLines
|
||||
|
||||
const token_tho = state.push('thead_open', 'thead', 1)
|
||||
token_tho.map = [startLine, startLine + 1]
|
||||
|
||||
const token_htro = state.push('tr_open', 'tr', 1)
|
||||
token_htro.map = [startLine, startLine + 1]
|
||||
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
const token_ho = state.push('th_open', 'th', 1)
|
||||
if (aligns[i]) {
|
||||
token_ho.attrs = [['style', 'text-align:' + aligns[i]]]
|
||||
}
|
||||
|
||||
const token_il = state.push('inline', '', 0)
|
||||
token_il.content = columns[i].trim()
|
||||
token_il.children = []
|
||||
|
||||
state.push('th_close', 'th', -1)
|
||||
}
|
||||
|
||||
state.push('tr_close', 'tr', -1)
|
||||
state.push('thead_close', 'thead', -1)
|
||||
|
||||
let tbodyLines
|
||||
let autocompletedCells = 0
|
||||
|
||||
for (nextLine = startLine + 2; nextLine < endLine; nextLine++) {
|
||||
if (state.sCount[nextLine] < state.blkIndent) { break }
|
||||
|
||||
let terminate = false
|
||||
for (let i = 0, l = terminatorRules.length; i < l; i++) {
|
||||
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||
terminate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (terminate) { break }
|
||||
lineText = getLine(state, nextLine).trim()
|
||||
if (!lineText) { break }
|
||||
if (state.sCount[nextLine] - state.blkIndent >= 4) { break }
|
||||
columns = escapedSplit(lineText)
|
||||
if (columns.length && columns[0] === '') columns.shift()
|
||||
if (columns.length && columns[columns.length - 1] === '') columns.pop()
|
||||
|
||||
// note: autocomplete count can be negative if user specifies more columns than header,
|
||||
// but that does not affect intended use (which is limiting expansion)
|
||||
autocompletedCells += columnCount - columns.length
|
||||
if (autocompletedCells > MAX_AUTOCOMPLETED_CELLS) { break }
|
||||
|
||||
if (nextLine === startLine + 2) {
|
||||
const token_tbo = state.push('tbody_open', 'tbody', 1)
|
||||
token_tbo.map = tbodyLines = [startLine + 2, 0]
|
||||
}
|
||||
|
||||
const token_tro = state.push('tr_open', 'tr', 1)
|
||||
token_tro.map = [nextLine, nextLine + 1]
|
||||
|
||||
for (let i = 0; i < columnCount; i++) {
|
||||
const token_tdo = state.push('td_open', 'td', 1)
|
||||
if (aligns[i]) {
|
||||
token_tdo.attrs = [['style', 'text-align:' + aligns[i]]]
|
||||
}
|
||||
|
||||
const token_il = state.push('inline', '', 0)
|
||||
token_il.content = columns[i] ? columns[i].trim() : ''
|
||||
token_il.children = []
|
||||
|
||||
state.push('td_close', 'td', -1)
|
||||
}
|
||||
state.push('tr_close', 'tr', -1)
|
||||
}
|
||||
|
||||
if (tbodyLines) {
|
||||
state.push('tbody_close', 'tbody', -1)
|
||||
tbodyLines[1] = nextLine
|
||||
}
|
||||
|
||||
state.push('table_close', 'table', -1)
|
||||
tableLines[1] = nextLine
|
||||
|
||||
state.parentType = oldParentType
|
||||
state.line = nextLine
|
||||
return true
|
||||
}
|
Reference in New Issue
Block a user