317 lines
7.3 KiB
JavaScript
317 lines
7.3 KiB
JavaScript
'use strict';
|
|
|
|
const attr_name = "[a-zA-Z_:@][a-zA-Z0-9:._-]*";
|
|
const unquoted = "[^\"'=<>`\\x00-\\x20]+";
|
|
const single_quoted = "'[^']*'";
|
|
const double_quoted = '"[^"]*"';
|
|
const attr_value = "(?:" + unquoted + "|" + single_quoted + "|" + double_quoted + ")";
|
|
const attribute = "(?:\\s+" + attr_name + "(?:\\s*=\\s*" + attr_value + ")?)";
|
|
const open_tag = "<[A-Za-z][A-Za-z0-9\\-]*" + attribute + "*\\s*\\/?>";
|
|
const close_tag = "<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>";
|
|
const comment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->";
|
|
const processing = "<[?][\\s\\S]*?[?]>";
|
|
const declaration = "<![A-Z]+\\s+[^>]*>";
|
|
const cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>";
|
|
const HTML_TAG_RE = new RegExp(
|
|
"^(?:" + open_tag + "|" + close_tag + "|" + comment + "|" + processing + "|" + declaration + "|" + cdata + ")"
|
|
);
|
|
const HTML_OPEN_CLOSE_TAG_RE = new RegExp(
|
|
"^(?:" + open_tag + "|" + close_tag + ")"
|
|
);
|
|
const HTML_SELF_CLOSING_TAG_RE = new RegExp(
|
|
"^<[A-Za-z][A-Za-z0-9\\-]*" + attribute + "*\\s*\\/>"
|
|
);
|
|
const HTML_OPEN_AND_CLOSE_TAG_IN_THE_SAME_LINE_RE = new RegExp(
|
|
"^<([A-Za-z][A-Za-z0-9\\-]*)" + attribute + "*\\s*>.*<\\/\\1\\s*>"
|
|
);
|
|
|
|
const TAGS_BLOCK = [
|
|
"address",
|
|
"article",
|
|
"aside",
|
|
"base",
|
|
"basefont",
|
|
"blockquote",
|
|
"body",
|
|
"caption",
|
|
"center",
|
|
"col",
|
|
"colgroup",
|
|
"dd",
|
|
"details",
|
|
"dialog",
|
|
"dir",
|
|
"div",
|
|
"dl",
|
|
"dt",
|
|
"fieldset",
|
|
"figcaption",
|
|
"figure",
|
|
"footer",
|
|
"form",
|
|
"frame",
|
|
"frameset",
|
|
"h1",
|
|
"h2",
|
|
"h3",
|
|
"h4",
|
|
"h5",
|
|
"h6",
|
|
"head",
|
|
"header",
|
|
"hr",
|
|
"html",
|
|
"iframe",
|
|
"legend",
|
|
"li",
|
|
"link",
|
|
"main",
|
|
"menu",
|
|
"menuitem",
|
|
"nav",
|
|
"noframes",
|
|
"ol",
|
|
"optgroup",
|
|
"option",
|
|
"p",
|
|
"param",
|
|
"search",
|
|
"section",
|
|
"summary",
|
|
"table",
|
|
"tbody",
|
|
"td",
|
|
"tfoot",
|
|
"th",
|
|
"thead",
|
|
"title",
|
|
"tr",
|
|
"track",
|
|
"ul"
|
|
];
|
|
const TAGS_INLINE = [
|
|
"a",
|
|
"abbr",
|
|
"acronym",
|
|
"audio",
|
|
"b",
|
|
"bdi",
|
|
"bdo",
|
|
"big",
|
|
"br",
|
|
"button",
|
|
"canvas",
|
|
"cite",
|
|
"code",
|
|
"data",
|
|
"datalist",
|
|
"del",
|
|
"dfn",
|
|
"em",
|
|
"embed",
|
|
"i",
|
|
"iframe",
|
|
"img",
|
|
"input",
|
|
"ins",
|
|
"kbd",
|
|
"label",
|
|
"map",
|
|
"mark",
|
|
"meter",
|
|
"noscript",
|
|
"object",
|
|
"output",
|
|
"picture",
|
|
"progress",
|
|
"q",
|
|
"ruby",
|
|
"s",
|
|
"samp",
|
|
"script",
|
|
"select",
|
|
"slot",
|
|
"small",
|
|
"span",
|
|
"strong",
|
|
"sub",
|
|
"sup",
|
|
"svg",
|
|
"template",
|
|
"textarea",
|
|
"time",
|
|
"u",
|
|
"tt",
|
|
"var",
|
|
"video",
|
|
"wbr"
|
|
];
|
|
const TAGS_VUE_RESERVED = [
|
|
"template",
|
|
"component",
|
|
"transition",
|
|
"transition-group",
|
|
"keep-alive",
|
|
"slot",
|
|
"teleport"
|
|
];
|
|
|
|
const createHtmlSequences = ({
|
|
blockTags,
|
|
inlineTags
|
|
}) => {
|
|
const forceBlockTags = [...blockTags, ...TAGS_BLOCK];
|
|
const forceInlineTags = [
|
|
...inlineTags,
|
|
...TAGS_INLINE.filter((item) => !TAGS_VUE_RESERVED.includes(item))
|
|
];
|
|
const HTML_SEQUENCES = [
|
|
[/^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true],
|
|
[/^<!--/, /-->/, true],
|
|
[/^<\?/, /\?>/, true],
|
|
[/^<![A-Z]/, />/, true],
|
|
[/^<!\[CDATA\[/, /\]\]>/, true],
|
|
// MODIFIED: Support extra block tags from user options
|
|
[
|
|
new RegExp("^</?(" + forceBlockTags.join("|") + ")(?=(\\s|/?>|$))", "i"),
|
|
/^$/,
|
|
true
|
|
],
|
|
// ADDED: Matching component tags (all unknown tags) (i = 6)
|
|
[
|
|
new RegExp(
|
|
"^</?(?!(" + forceInlineTags.join("|") + ")(?![\\w-]))[A-Za-z][A-Za-z0-9\\-]*(?=(\\s|/?>|$))"
|
|
),
|
|
/^$/,
|
|
true
|
|
],
|
|
// this line is to treat a line that only have a single self-closed html tag
|
|
// as html_block, even if it's a self-closed inline tag
|
|
// MODIFIED: Tweak the original HTML_OPEN_CLOSE_TAG_RE
|
|
[new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + "\\s*$"), /^$/, false]
|
|
];
|
|
return HTML_SEQUENCES;
|
|
};
|
|
const createHtmlBlockRule = (options) => {
|
|
const HTML_SEQUENCES = createHtmlSequences(options);
|
|
return (state, startLine, endLine, silent) => {
|
|
let i;
|
|
let nextLine;
|
|
let lineText;
|
|
let pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
let max = state.eMarks[startLine];
|
|
if (state.sCount[startLine] - state.blkIndent >= 4) {
|
|
return false;
|
|
}
|
|
if (!state.md.options.html) {
|
|
return false;
|
|
}
|
|
if (state.src.charCodeAt(pos) !== 60) {
|
|
return false;
|
|
}
|
|
lineText = state.src.slice(pos, max);
|
|
for (i = 0; i < HTML_SEQUENCES.length; i++) {
|
|
if (HTML_SEQUENCES[i][0].test(lineText)) {
|
|
break;
|
|
}
|
|
}
|
|
if (i === HTML_SEQUENCES.length) {
|
|
return false;
|
|
}
|
|
if (silent) {
|
|
return HTML_SEQUENCES[i][2];
|
|
}
|
|
if (i === 6) {
|
|
const match = (
|
|
// if the component tag is self-closing
|
|
lineText.match(HTML_SELF_CLOSING_TAG_RE) ?? // or has open and close tag in the same line
|
|
lineText.match(HTML_OPEN_AND_CLOSE_TAG_IN_THE_SAME_LINE_RE)
|
|
);
|
|
if (match) {
|
|
state.line = startLine + 1;
|
|
let token2 = state.push("html_inline", "", 0);
|
|
token2.content = match[0];
|
|
token2.map = [startLine, state.line];
|
|
token2 = state.push("inline", "", 0);
|
|
token2.content = lineText.slice(match[0].length);
|
|
token2.map = [startLine, state.line];
|
|
token2.children = [];
|
|
return true;
|
|
}
|
|
}
|
|
nextLine = startLine + 1;
|
|
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;
|
|
};
|
|
};
|
|
|
|
const isLetter = (ch) => {
|
|
const lc = ch | 32;
|
|
return lc >= 97 && lc <= 122;
|
|
};
|
|
const htmlInlineRule = (state, silent) => {
|
|
const { pos } = state;
|
|
if (!state.md.options.html) {
|
|
return false;
|
|
}
|
|
const max = state.posMax;
|
|
if (state.src.charCodeAt(pos) !== 60 || pos + 2 >= max) {
|
|
return false;
|
|
}
|
|
const ch = state.src.charCodeAt(pos + 1);
|
|
if (ch !== 33 && ch !== 63 && ch !== 47 && !isLetter(ch)) {
|
|
return false;
|
|
}
|
|
const match = state.src.slice(pos).match(HTML_TAG_RE);
|
|
if (!match) {
|
|
return false;
|
|
}
|
|
if (!silent) {
|
|
const token = state.push("html_inline", "", 0);
|
|
token.content = state.src.slice(pos, pos + match[0].length);
|
|
}
|
|
state.pos += match[0].length;
|
|
return true;
|
|
};
|
|
|
|
const componentPlugin = (md, { blockTags = [], inlineTags = [] } = {}) => {
|
|
const htmlBlockRule = createHtmlBlockRule({
|
|
blockTags,
|
|
inlineTags
|
|
});
|
|
md.block.ruler.at("html_block", htmlBlockRule, {
|
|
alt: ["paragraph", "reference", "blockquote"]
|
|
});
|
|
md.inline.ruler.at("html_inline", htmlInlineRule);
|
|
};
|
|
|
|
exports.HTML_OPEN_AND_CLOSE_TAG_IN_THE_SAME_LINE_RE = HTML_OPEN_AND_CLOSE_TAG_IN_THE_SAME_LINE_RE;
|
|
exports.HTML_OPEN_CLOSE_TAG_RE = HTML_OPEN_CLOSE_TAG_RE;
|
|
exports.HTML_SELF_CLOSING_TAG_RE = HTML_SELF_CLOSING_TAG_RE;
|
|
exports.HTML_TAG_RE = HTML_TAG_RE;
|
|
exports.TAGS_BLOCK = TAGS_BLOCK;
|
|
exports.TAGS_INLINE = TAGS_INLINE;
|
|
exports.TAGS_VUE_RESERVED = TAGS_VUE_RESERVED;
|
|
exports.componentPlugin = componentPlugin;
|
|
exports.createHtmlBlockRule = createHtmlBlockRule;
|
|
exports.htmlInlineRule = htmlInlineRule;
|