foremark
Version:
A technology for writing semi-plain text documents that extends upon the concept of Markdeep.
644 lines • 29.8 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
var dom_1 = require("../utils/dom");
var string_1 = require("../utils/string");
var table_1 = require("./table");
var blocks_1 = require("./blocks");
var common_1 = require("./common");
var ARROWS = [
['<==', '⇐'],
['->', '→'],
['-->', '⟶'],
['==>', '⇒'],
['<-', '←'],
['<--', '⟵'],
['<==>', '⇔'],
['<->', '↔'],
];
var ARROWS_MAP = new Map(ARROWS);
var ARROWS_REGEX = new RegExp('(\\s|^)(' + ARROWS.map(function (e) { return e[0]; }).join('|') + ')(?=\\s)', 'g');
var FENCE_REGEX = /^((?:[ \t]|>)*)(~{3,}|`{3,})\s*([-_0-9a-zA-Z+/\s]*)$/;
var DIAGRAM_REGEX = /^((?:[ \t]|>)*)::(.*)$/;
var PHRASING_ELEMENTS = [
"mf-ph" /* Placeholder */,
"mf-ref" /* Ref */,
"mf-eq" /* Equation */,
'addr', 'audio', 'b', 'bdo', 'br', 'button', 'canvas', 'cite', 'code',
'command', 'data', 'datalist', 'dfn', 'em', 'embed', 'i', 'iframe', 'img',
'input', 'kbd', 'keygen', 'label', 'mark', 'math', 'meter', 'noscript',
'object', 'output', 'progress', 'q', 'ruby', 'samp', 'script', 'select',
'small', 'span', 'strong', 'sub', 'sup', 'svg', 'textarea', 'time', 'var',
'video', 'wbr',
// The following elements are conditional, but we assume they belong to
// this category
'a', 'del', 'ins',
];
var PHRASING_ELEMENTS_MAP = new Map(PHRASING_ELEMENTS.map(function (n) { return [n, true]; }));
var VERBATIM_ELEMENTS = [
'code', "mf-code" /* Code */, 'svg', "mf-eq" /* Equation */, "mf-eq-display" /* DisplayEquation */,
"mf-link-target" /* LinkTarget */, "mf-diagram" /* Diagram */,
];
var VERBATIM_ELEMENTS_MAP = new Map(VERBATIM_ELEMENTS.map(function (n) { return [n, true]; }));
/**
* Expands all `<mf-text>` in a given DOM node.
*/
function expandMfText(node) {
if (node.tagName.toLowerCase() !== "mf-text" /* Text */) {
for (var n = node.firstChild; n; n = n.nextSibling) {
if (n instanceof dom_1.DomTypes.Element) {
expandMfText(n);
}
}
return;
}
// Merge consecutive text nodes
//
// Without this step, constructs spanning across multiple text nodes are
// not recognized properly. For example, "`]]><![CDATA[]>][`" inside CDATA
// (which is intended to display a code fragment "]]>") splits the current
// CDATA section into two: one ending with "`]" and one starting with
// "]>`". `transformTextNodeWith` calls a supplied function in
// per text node basis, so this example won't be recognized as a hyperlink
// without this step.
dom_1.forEachNodePreorder(node, function (node) {
if (!(node instanceof dom_1.DomTypes.Text) || node.nextSibling instanceof dom_1.DomTypes.Text) {
return;
}
// This is the last one of one or more consecutive text nodes.
if (!(node.previousSibling instanceof dom_1.DomTypes.Text)) {
// One consecutive text node.
return;
}
// Merge all and remove all but the last one.
var parts = [node.textContent];
var parent = node.parentElement;
for (var n = node.previousSibling; n instanceof dom_1.DomTypes.Text;) {
var next = n.previousSibling;
parts.unshift(n.textContent);
parent.removeChild(n);
n = next;
}
node.textContent = parts.join('');
});
// Fenced code blocks
//
// This step preserves the indentation of code blocks. For example:
//
// > ~~~~~~~~~~~~~~~~
// > code...
// > ~~~~~~~~~~~~~~~~
//
// will be:
//
// > <mf-code-block><mf-code>code...</mf-code></mf-code-block>
//
dom_1.transformHtmlWith(node, function (html) {
var lines = html.split('\n');
var output = [];
var inCodeBlock = false;
var currentCodeBlockIndentation = '';
var currentCodeBlockFence = '';
for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) {
var line = lines_1[_i];
var matches = line.match(FENCE_REGEX);
if (matches) {
matches[2] = matches[2].substr(0, 1);
matches[3] = matches[3].trim();
}
if (inCodeBlock) {
if (matches &&
matches[2] === currentCodeBlockFence &&
matches[1] === currentCodeBlockIndentation) {
if (output[output.length - 1] === '\n') {
output.pop(); // Remove trailing newline
}
if (matches[3] === '') {
// End of code blocks
output.push("</" + "mf-code" /* Code */ + "></" + "mf-codeblock" /* CodeBlock */ + ">\n");
inCodeBlock = false;
}
else {
// Language switch
output.push("</" + "mf-code" /* Code */ + "><" + "mf-code" /* Code */ + " type=\"" + matches[3] + "\">");
output.push('\n');
currentCodeBlockIndentation = matches[1];
}
}
else {
output.push(string_1.removePrefix(line, currentCodeBlockIndentation));
output.push('\n');
}
}
else {
if (matches) {
output.push(matches[1]); // Preserve indentation
output.push("<" + "mf-codeblock" /* CodeBlock */ + ">");
output.push("<" + "mf-code" /* Code */ + " type=\"" + matches[3] + "\">");
inCodeBlock = true;
currentCodeBlockFence = matches[2];
currentCodeBlockIndentation = matches[1];
}
else {
output.push(line);
output.push('\n');
}
}
}
if (inCodeBlock) {
if (output[output.length - 1] === '\n') {
output.pop(); // Remove trailing newline
}
output.push("</" + "mf-code" /* Code */ + "></" + "mf-codeblock" /* CodeBlock */ + ">");
output.push("<" + "mf-error" /* Error */ + ">Unclosed fenced code block</" + "mf-error" /* Error */ + ">");
}
else {
output.pop(); // Remove trailing newline
}
return output.join('');
});
// Diagrams
//
// This step preserves the indentation of code blocks. For example,
// the both of: (1) (the original style)
//
// > ::: code...
// > ::: code...
//
// and (2) (the style newly introduced in 0.1.2)
//
// > :::::::::::::
// > :: code... ::
// > :: code... ::
// > :::::::::::::
//
// will be:
//
// > <mf-diagram>...</mf-diagram>
//
dom_1.transformHtmlWith(node, function (html) {
var lines = html.split('\n');
var output = [];
var inDiagram = false;
var diagramStart = -1;
var currentDiagramIndentation = '';
function closeDiagram() {
// Remove borders if any
var HORZ_BORDER_RE = /^:{4,}\s*$/;
if (HORZ_BORDER_RE.test(output[output.length - 2])) {
output.length -= 2;
}
if (diagramStart < output.length && HORZ_BORDER_RE.test(output[diagramStart])) {
output.splice(diagramStart, 2);
}
// `hasLeftBorder` handles the first example's style. (Note that
// `DIAGRAM_REGEX` only removes the two leftmost colons.)
// `hasRightBorder` handles the second example's style.
var hasLeftBorder = true, hasRightBorder = true;
for (var i = diagramStart; i < output.length; i += 2) {
hasLeftBorder = hasLeftBorder && output[i].startsWith(':');
hasRightBorder = hasRightBorder && output[i].endsWith('::');
}
for (var i = diagramStart; i < output.length; i += 2) {
var s = output[i];
output[i] = s.substring(hasLeftBorder ? 1 : 0, hasRightBorder ? s.length - 2 : s.length);
}
if (output[output.length - 1] === '\n') {
output.pop(); // Remove trailing newline
}
output.push("</" + "mf-diagram" /* Diagram */ + ">\n");
inDiagram = false;
}
for (var _i = 0, lines_2 = lines; _i < lines_2.length; _i++) {
var line = lines_2[_i];
var matches = line.match(DIAGRAM_REGEX);
if (inDiagram) {
if (!matches || matches[1] !== currentDiagramIndentation) {
closeDiagram();
}
else {
output.push(matches[2]);
output.push('\n');
}
}
if (!inDiagram) {
if (matches) {
output.push(matches[1]); // Preserve indentation
output.push("<" + "mf-diagram" /* Diagram */ + ">");
diagramStart = output.length;
output.push(matches[2]);
output.push('\n');
inDiagram = true;
currentDiagramIndentation = matches[1];
}
else {
output.push(line);
output.push('\n');
}
}
}
if (inDiagram) {
closeDiagram();
}
else {
output.pop(); // Remove trailing newline
}
return output.join('');
});
// Block quotations
var BLOCKQUOTE_LINE_RE = />.*(?:\n(?!>).*\S+.*$)*/.source;
var BLOCKQUOTE_RE = new RegExp("^" + BLOCKQUOTE_LINE_RE + "(\n" + BLOCKQUOTE_LINE_RE + ")*", 'gm');
var replaceSingleLevelBlockquote = function (html) { return ('\n' + html)
// Replace the top-level blockquotes in this level
.replace(BLOCKQUOTE_RE, function (bq) { return '<blockquote>' + replaceSingleLevelBlockquote(bq.replace(/^[ \t]*> ?/gm, '')) + '</blockquote>'; }).substr(1); };
dom_1.transformHtmlWith(node, replaceSingleLevelBlockquote);
var isBlockquote = function (e) { return e.tagName === 'blockquote'; };
// Inline code
dom_1.transformTextNodeWith(node, function (html) { return html.replace(/`(.+?(?:\n.+?)?)`(?!\d)/g, '<code>$1</code>'); }, function (e) { return e === node || isBlockquote(e); }, false);
// Wrapped URLs
var WRAPPED_URL_RE = new RegExp(
// url
"<([a-z]+://(?:[^&<>]|&(?!gt;|lt;))+)>|" +
// mail
"<((?:[^&<>@]|&(?!gt;|lt;))+@(?:[^&<>@]|&(?!gt;))+)>", 'g');
dom_1.transformTextNodeWith(node, function (html) { return html.replace(WRAPPED_URL_RE, function (_, url, mail) {
var text = dom_1.unescapeXmlText(url || mail);
var href = (mail && 'mailto:' + dom_1.unescapeXmlText(mail)) || text;
return "<a href=\"" + dom_1.escapeXmlText(href) + "\"><code>" + dom_1.escapeXmlText(text) + "</code></a>";
}); }, function (e) { return e === node || isBlockquote(e); }, false);
// Parse HTML comments
dom_1.transformHtmlWith(node, function (html) { return html.replace(/<!--(\s[\s\S]*?)-->/g, function (_, inner) { return "<!--" + inner.replace(/--/g, '--') + "-->"; }); }, function (e) { return e === node || isBlockquote(e); });
// Replace well-formed HTML tags
// The regex performs all validation of individual tags. The code checks
// if opening tags are matched by closing tags.
var ident = '[a-z][-a-z0-9]*';
var permittedEntities = 'amp|quot|lt|gt';
var tagRE = new RegExp("(<" +
("(?:(" + ident + ")(?:\\s+" + ident + "=\"(?:[^\"<>&]|&" + permittedEntities + ";)*\")*") +
// ^^^^^^^^
// $2 opening tag name
"\\s*/?|" +
(
// ^^
// self-closing
"/(" + ident + "))>)"),
// ^^^^^^^^
// $3 closing tag name
'g');
dom_1.transformHtmlWith(node, function (html) {
var output = [];
/// A stack tracking unvalidated opening tags.
var stack = [];
var parts = html.split(tagRE);
output.push(parts[0]);
for (var i = 1; i < parts.length; i += 4) {
var tag = parts[i];
var openingTagName = parts[i + 1];
var closingTagName = parts[i + 2];
var post = parts[i + 3];
if (closingTagName) {
// `</a>`
var i_1 = stack.length;
while (i_1 > 0 && stack[i_1 - 1][0] !== closingTagName) {
i_1--;
}
if (i_1 > 0) {
// Found a matching opening tag. Materialize the opening tag,
// and forget about potential opening tags (`stack[i..]`)
// which turned out to have no closing tags.
while (stack.length > i_1) {
stack.pop();
}
var e = stack.pop(); // `stack[i - 1]`
output[e[1]] = dom_1.unescapeXmlText(output[e[1]]);
output.push(dom_1.unescapeXmlText(tag));
}
else {
// A matching opening tag wasn't found.
output.push(tag);
}
}
else if (tag.endsWith('/>')) {
// `<a />`
output.push(dom_1.unescapeXmlText(tag));
}
else {
// `<a>`
stack.push([openingTagName, output.length]);
output.push(tag);
}
output.push(post);
}
return output.join('');
}, isBlockquote);
// LaTeX display equations
dom_1.transformTextNodeWith(node, function (html) { return html.replace(/\$\$([^<>]*?)\$\$/g, "<" + "mf-eq-display" /* DisplayEquation */ + ">$1</" + "mf-eq-display" /* DisplayEquation */ + ">"); }, function (e) { return e === node || isBlockquote(e); }, false);
dom_1.transformTextNodeWith(node, function (html) { return html.replace(/\\begin{(equation\*?|eqnarray)}[^<>]*?\\end{\1}/g, "<" + "mf-eq-display" /* DisplayEquation */ + ">$&</" + "mf-eq-display" /* DisplayEquation */ + ">"); }, function (e) { return e === node || isBlockquote(e); }, false);
// LaTeX inline equations
dom_1.transformTextNodeWith(node, function (html) { return html.replace(/(^|[^\w\d])\$(\S(?:[^\$]*?\S(?!US|Can))??)\$(?![\w\d])/g, "$1<" + "mf-eq" /* Equation */ + ">$2</" + "mf-eq" /* Equation */ + ">"); }, function (e) { return e === node || isBlockquote(e); }, false);
dom_1.transformTextNodeWith(node, function (html) { return html.replace(/(^|[^\w\d])\$([ \t][^\$]+?[ \t])\$(?![\w\d])/g, "$1<" + "mf-eq" /* Equation */ + ">$2</" + "mf-eq" /* Equation */ + ">"); }, function (e) { return e === node || isBlockquote(e); }, false);
// Headings
dom_1.transformHtmlWith(node, function (html) { return html.replace(/^(.+?)\n[ \t]*={3,}[ \t]*$/gm, function (_, inner) { return "<h1>" + inner + "</h1>"; }); }, isBlockquote);
dom_1.transformHtmlWith(node, function (html) { return html.replace(/^(.+?)\n[ \t]*-{3,}[ \t]*$/gm, function (_, inner) { return "<h2>" + inner + "</h2>"; }); }, isBlockquote);
var _loop_1 = function (i) {
// ATX-style header (`## h2`)
dom_1.transformHtmlWith(node, function (html) { return html.replace(new RegExp("^[ \t]*#{" + i + "," + i + "}(?:[ \t])([^\n]+?)(?:#{" + i + "," + i + "})?[ \t]*$", 'gm'), function (_, inner) { return "<h" + i + ">" + inner + "</h" + i + ">"; }); }, isBlockquote);
};
for (var i = 6; i >= 1; --i) {
_loop_1(i);
}
// Horizontal rule: `* * *`, `- - -`, `_ _ _`
dom_1.transformHtmlWith(node, function (html) { return html.replace(/^[ \t]*((\*|-|_)[ \t]*){3,}[ \t]*$/gm, '<hr />'); }, isBlockquote);
// Nestable block elements
// - `<ul>`, `<ol>`, `<dl>`
// - `<mf-admonition>`
dom_1.transformHtmlWith(node, blocks_1.replaceBlocks, isBlockquote);
var isBlock = function (e) {
var tagName = e.tagName;
return tagName === "mf-admonition" /* Admonition */ ||
tagName === "mf-figure" /* Figure */ ||
tagName === "mf-figure-caption" /* FigureCaption */ ||
tagName === "mf-note" /* Note */ ||
tagName === "mf-cite" /* Cite */ ||
tagName === "mf-block" /* Block */ ||
tagName.match(/^(?:ul|ol|dl|li|dt|dd|blockquote)$/i) != null;
};
// Tables
dom_1.transformHtmlWith(node, table_1.replaceTables, isBlock);
// Paragraphs
dom_1.transformHtmlWith(node, function (html) {
var TAG = /<([-_a-zA-Z0-9]+)\s+.*?>/g;
var lines = html.split('\n');
var output = [];
var inParagraph = false;
for (var _i = 0, lines_3 = lines; _i < lines_3.length; _i++) {
var line = lines_3[_i];
if (line.length === 0) {
// Empty line - insert a paragraph break
if (inParagraph) {
output.push('</p>');
output.push('\n');
inParagraph = false;
}
}
while (line.length > 0) {
// Non-phrasing element terminates the current paragraph
var until = -1;
var restartFrom = 0;
var foundVisualElement = false;
TAG.lastIndex = 0;
var match = void 0;
while ((match = TAG.exec(line)) !== null) {
until = TAG.lastIndex - match[0].length;
if (until > restartFrom) {
// A text node was found
foundVisualElement = true;
}
restartFrom = TAG.lastIndex;
if (!PHRASING_ELEMENTS_MAP.has(match[1].toLowerCase())) {
// Non-phrasing element found - stop right here
break;
}
if (match[1] !== "mf-ph" /* Placeholder */) {
// Element possibly including a text node was found
foundVisualElement = true;
}
}
if (match === null) {
if (line.length > restartFrom) {
foundVisualElement = true;
}
until = line.length;
restartFrom = -1;
}
// A part of the current line to be included in the current paragraph
var cur = line.substr(0, until).trim();
if (cur !== '') {
// Do not start a paragraph if we only encountered non-visual
// nodes such as comments
if (foundVisualElement && !inParagraph) {
// Start a paragraph
output.push('<p>');
output.push('\n');
inParagraph = true;
}
output.push(cur);
output.push('\n');
}
// Processs the rest of the line
if (restartFrom >= 0) {
if (inParagraph) {
output.push('</p>');
output.push('\n');
inParagraph = false;
}
output.push(line.substring(until, restartFrom));
output.push('\n');
line = line.substr(restartFrom);
}
else {
break;
}
}
}
if (inParagraph) {
output.push('</p>');
}
if (output[output.length - 1] === '\n') {
output.pop(); // Remove trailing newline
}
return output.join('');
}, isBlock);
var isNonVerbatimElement = function (e) {
return !VERBATIM_ELEMENTS_MAP.has(e.tagName.toLowerCase());
};
var isNonVerbatimElementAndNotLink = function (e) {
return isNonVerbatimElement(e) && e.tagName !== 'a';
};
// Reference to a figure or endnote: `[^link]`
dom_1.transformHtmlWith(node, function (html) { return html.replace(new RegExp("\\[(?:" +
(
// id of endnote or figure
common_1.FLOATING_SIZE_RE.source + "(" + common_1.ENDNOTE_ID_RE.source + "|" + common_1.FIGURE_ID_RE.source + ")|") +
(
// id of citation
"#(" + common_1.CITE_ID_RE.source + ")") +
")\\]", 'g'), function (_, id1, id2) {
var id = dom_1.unescapeXmlText(id1 || id2);
return "<" + "mf-ref" /* Ref */ + " to=\"" + dom_1.escapeXmlText(id) + "\" />";
}); }, isNonVerbatimElement);
// Hyperlink: `[text](url)`
dom_1.transformHtmlWith(node, function (html) { return html.replace(/(^|[^!])\[([^\[\]]+?)\]\(("?)([^<>\s"]+?)\3(\s+[^\)]*?)?\)/g, function (match, pre, text, maybeQuote, url, attribs) {
if (attribs === void 0) { attribs = ''; }
url = dom_1.escapeXmlText(url);
attribs = dom_1.legalizeAttributes(attribs, ['href'], function (e) { return console.warn(e); }, ['title']);
return pre + ("<a href=\"" + url + "\"" + attribs + ">" + text + "</a>");
}); }, isNonVerbatimElement);
// Inline media: ``
dom_1.transformHtmlWith(node, function (html) { return html.replace(new RegExp("!\\[([^\\[\\]]*)]\\s*\\(" + common_1.MEDIA_PARAM_RE.source + "\\)", 'g'), function (_, altRaw, urlRaw, attribsRaw) {
if (attribsRaw === void 0) { attribsRaw = ''; }
if (urlRaw.startsWith('"')) {
urlRaw = urlRaw.substring(1, urlRaw.length - 1);
}
var alt = dom_1.unescapeXmlText(altRaw);
var url = dom_1.unescapeXmlText(urlRaw), attribs = dom_1.unescapeXmlText(attribsRaw);
return "<" + "mf-media" /* Media */ + " src=\"" + dom_1.escapeXmlText(url) + "\" alt=\"" + dom_1.escapeXmlText(alt) + "\"" +
(dom_1.legalizeAttributes(attribs, ['src', 'alt'], function (m) { return console.warn(m); }, ['title']) + " />");
}); }, isNonVerbatimElement);
// Symbolic hyperlink: `[text][linkname]` `![alt][linkname]`
var linkSymbolTable = new Map();
dom_1.transformHtmlWith(node, function (html, ctx) {
if (linkSymbolTable.size === 0) {
return html;
}
// Can't use `String#replace` here because it only scans a string in
// the forward direction.
var parts = html.split(/((!)?\[([^\]]+)\]\s*\[([^\^#!<>[\]][^<>[\]]*)?\])/g);
// ^ ^^^^^^ ^^^^^^^^^^^^^^^^^^^^
// image? text symbol name
if (parts.length === 1) {
return html;
}
var output = [];
output.unshift(parts.pop());
for (var i = parts.length; i > 0; i -= 5) {
var pre = parts[i - 5];
var source = parts[i - 4];
var isImage = parts[i - 3];
var text = parts[i - 2];
var symbolName = parts[i - 1];
if (symbolName == null) {
symbolName = ctx.expand(text);
}
var linkTarget = linkSymbolTable.get(symbolName);
if (linkTarget != null) {
linkSymbolTable.delete(symbolName);
linkTarget = linkTarget.trim();
var match = linkTarget.match(/^(?:(")?([^"]*?)\1)( .*)?$/);
if (match) {
var _1 = match[0], _2 = match[1], url = match[2], attribs = match[3];
if (isImage) {
attribs = dom_1.legalizeAttributes(attribs || '', ['src', 'alt'], function (e) { return console.warn(e); }, ['title']);
output.unshift("<" + "mf-media" /* Media */ + " src=\"" + dom_1.escapeXmlText(url) + "\" alt=\"" + dom_1.escapeXmlText(text) + "\"" + attribs + " />");
}
else {
attribs = dom_1.legalizeAttributes(attribs || '', ['href'], function (e) { return console.warn(e); }, ['title']);
output.unshift("<a href=\"" + dom_1.escapeXmlText(url) + "\"" + attribs + ">" + text + "</a>");
}
}
else {
output.unshift("<" + "mf-error" /* Error */ + ">Failed to parse the link target: <code>" +
dom_1.escapeXmlText(linkTarget) +
("</code></" + "mf-error" /* Error */ + ">"));
}
}
else {
// Link target wasn't found; treat the fragment as a normal text
output.unshift(source);
}
output.unshift(pre);
}
return output.join('');
}, function (element) {
if (element.tagName === "mf-link-target" /* LinkTarget */) {
var symbolName = element.getAttribute('link-id');
var linkTarget = element.textContent.replace(/[\r\n]/g, '');
linkSymbolTable.set(symbolName, linkTarget);
// Consume `<TextInternalTagNames.LinkTarget>`
element.parentElement.removeChild(element);
return false;
}
return isNonVerbatimElementAndNotLink(element);
},
// Traverse in the reverse order so that
true);
// Bare URLs
var PROTOCOL_RE = /(?:https?|ftps?|sftp|ipfs|ipns|dweb|mailto):\/\//.source;
var MAIL_CHR_RE = /[-0-9a-zA-Z#%+=_]/.source; // doesn't handle uncommon cases
var MAIL_CHR_INNER_RE = "(?:" + MAIL_CHR_RE + "|\\.(?!\\.))";
var URL_RE = new RegExp(
// url
"(" + PROTOCOL_RE + "(?:[^ \n\t<>&]|&)*)|" +
(
// mail
"(" + MAIL_CHR_RE + MAIL_CHR_INNER_RE + "*@" + MAIL_CHR_INNER_RE + "*" + MAIL_CHR_RE + ")"), 'g');
dom_1.transformTextNodeWith(node, function (html) { return html.replace(URL_RE, function (_, url, mail) {
var text = dom_1.unescapeXmlText(url || mail);
var href = (mail && 'mailto:' + dom_1.unescapeXmlText(mail)) || text;
return "<a href=\"" + dom_1.escapeXmlText(href) + "\"><code>" + dom_1.escapeXmlText(text) + "</code></a>";
}); }, isNonVerbatimElementAndNotLink, false);
dom_1.transformTextNodeWith(node, function (html) {
// Arrows
html = html.replace(ARROWS_REGEX, function (_, start, arrow) { return start + ARROWS_MAP.get(arrow); });
// Em dash: `---`
html = html.replace(/\b---\b/g, '—');
// En dash: `--`
html = html.replace(/\b--\b/g, '–');
// Number x number
html = html.replace(/\b(\d+\s?)x(\s?\d+)\b/g, '$1×$2');
return html;
}, isNonVerbatimElement, false);
// Strikethrough
dom_1.transformHtmlWith(node, function (html) { return html.replace(/~~([^]+?)~~/g, '<del>$1</del>'); }, isNonVerbatimElement);
// Boldfaces
dom_1.transformHtmlWith(node, function (html) { return html.replace(/\*\*([^]+?)\*\*/g, '<b>$1</b>'); }, isNonVerbatimElement);
dom_1.transformHtmlWith(node, function (html) { return html.replace(/__([^]+?)__/g, '<b>$1</b>'); }, isNonVerbatimElement);
// Italics
dom_1.transformHtmlWith(node, function (html) { return html.replace(/\*([^]+?)\*/g, '<i>$1</i>'); }, isNonVerbatimElement);
dom_1.transformHtmlWith(node, function (html) { return html.replace(/_([^]+?)_/g, '<i>$1</i>'); }, isNonVerbatimElement);
// The first paragraph starting with `<b>...</b>` is treated as title
(function () {
var firstPara = node.getElementsByTagName('p')[0];
if (!firstPara) {
return;
}
var bold = null;
for (var n = firstPara.firstChild; n; n = n.nextSibling) {
if ((n instanceof dom_1.DomTypes.Text) && n.textContent.match(/^\s*$/)) {
continue;
}
if (!(n instanceof dom_1.DomTypes.Element) || (n.tagName !== 'B' && n.tagName !== 'b')) {
return;
}
bold = n;
break;
}
if (!bold) {
return;
}
// Replace `<p><b>...</b></p>` with `<mf-title>...</mf-title>`
var title = node.ownerDocument.createElement("mf-title" /* Title */);
for (var n = bold.firstChild; n;) {
var next = n.nextSibling;
title.appendChild(n);
n = next;
}
firstPara.parentElement.insertBefore(title, firstPara);
firstPara.parentElement.removeChild(firstPara);
// If there are remaining contents, put them in `<mf-lead>...</mf-lead>`
var lead = node.ownerDocument.createElement("mf-lead" /* Lead */);
var isEmpty = true;
for (var n = bold.nextSibling; n;) {
if (!(n instanceof dom_1.DomTypes.Text) || !n.textContent.match(/^\s*$/)) {
isEmpty = false;
}
var next = n.nextSibling;
lead.appendChild(n);
n = next;
}
if (isEmpty) {
return;
}
title.parentElement.insertBefore(lead, title.nextSibling);
})();
// Unwrap `<mf-text>`
var parent = node.parentElement;
if (!parent) {
throw new Error("mf-text" /* Text */ + " must bave a parent element.");
}
while (node.firstChild) {
parent.insertBefore(node.firstChild, node);
}
parent.removeChild(node);
}
exports.expandMfText = expandMfText;
//# sourceMappingURL=mftext.js.map
;