gdpr audit implemented, email log, vollmachten, pdf delete cancel data privacy and vollmachten, removed message no id card in engergy car, and other contracts that are not telecom contracts, added insert counter for engery
This commit is contained in:
+209
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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