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:
2026-03-21 11:59:53 +01:00
parent 89cf92eaf5
commit f2876f877e
1491 changed files with 265550 additions and 1292 deletions
+209
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}