"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parse_md_to_html = exports.parse_md_to_html_with_warnings = void 0; //some minor differences with markdown spec? function parse_md_to_html_with_warnings(md) { let html = ""; let html_line = ""; let warnings = []; let line_number = 1; //markdown parsing vars let is_first_line = true; let backslashed = false; let heading_level = 0; let in_heading = false; let header_num = 0; let asterisk_num = 0; let asterisk_out_num = 0; let in_asterisk = false; let horizontal_num = 0; let horizontal_rule = false; let was_image = false; let image_alt = undefined; let image_src = undefined; let was_link = false; let link_content = undefined; let link_href = undefined; let in_code = false; let in_code_block = false; let first_line_code_block = false; let code_block_lang = undefined; let space_start = false; let in_blockquote = false; let in_unordered_list = false; let blockquote_list = false; let in_ordered_list = false; let ordered_list_num = 0; let in_superscript = false; let in_table = false; let in_table_header = false; let table_item = ""; //loop through characters let chars = md; for (let i = 0; i < chars.length; i++) { let char = chars[i]; let end_add_char = true; //sanitize input if (char === "<") { char = "<"; } else if (char === ">") { char = ">"; } //handle backslashes if (backslashed) { backslashed = false; if (i !== chars.length - 1) { html_line += char; continue; } } if (char === "\\" && chars[i + 1] !== "\n") { backslashed = true; if (i === 0 || chars[i - 1] === "\n") { html_line = "
" + html_line;
}
continue;
}
//end of text or newline
if (char === "\n" || i === chars.length - 1) {
if (is_first_line) {
//it can only be the first line once :)
is_first_line = false;
}
//preserving the newlines/linebreaks of the code block
if (in_code_block && char === "\n" && !first_line_code_block) {
html_line += "
\n";
space_start = true;
}
//if first line of code block, create the code block div
if (first_line_code_block) {
code_block_lang = code_block_lang.toLowerCase().trim();
let known_langs = ["python", "py", "rust", "rs", "javascript", "js", "typescript", "ts", "java", "c", "cpp", "csharp", "html", "css", "markdown", "md", "brainfuck", "php", "bash", "perl", "sql", "ruby", "basic", "assembly", "asm", "wasm", "r", "go", "swift"];
if (!known_langs.includes(code_block_lang) && code_block_lang !== "") {
warnings.push({
type: "unknown-language",
message: `Unknown language '${code_block_lang}' for code block`,
line_number,
});
}
if (code_block_lang === "") {
//if no code block language specified, don't put it as a css class obviously
html_line = `
")) { html_line = "
" + html_line; } html_line += "`", line_number, }); } else { warnings.push({ type: "image-incomplete", message: "Image incomplete, missing `]` or `(`", line_number, }); } image_alt = undefined; image_src = undefined; } //if link was never completed if (link_content !== undefined) { if (!html_line.startsWith("
")) { html_line = "
" + html_line; } html_line += "[" + link_content; if (link_href !== undefined) { html_line += "](" + link_href; warnings.push({ type: "link-incomplete", message: "Link incomplete, missing `)`", line_number, }); } else { warnings.push({ type: "link-incomplete", message: "Link incomplete, missing `]` or `(`", line_number, }); } link_content = undefined; link_href = undefined; } //if last character if (i === chars.length - 1 && char !== "\n") { let add_char = true; //close code block div if (in_code_block && i === chars.length - 1) { in_code_block = false; add_char = false; html_line = "
, so add it! if (chars[i - 1] === "\n") { html_line = "
";
}
//ending a bold/italic?
if (in_asterisk && char === "*") {
if (asterisk_num === 2 && chars[i - 1] === "*") {
html_line += "";
in_asterisk = false;
asterisk_num = 0;
add_char = false;
}
else if (asterisk_num === 1) {
html_line += "";
in_asterisk = false;
asterisk_num = 0;
add_char = false;
}
}
//ending superscript
if (in_superscript && char === "^") {
html_line += "";
in_superscript = false;
add_char = false;
}
if (add_char) {
html_line += char;
}
}
if (in_asterisk) {
//bold/italic never ended
if (asterisk_num === 1) {
//remove the last and replace it with a *
let split = html_line.split("");
html_line = "";
for (let ii = 0; ii < split.length; ii++) {
html_line += split[ii];
if (ii !== split.length - 1) {
if (ii === split.length - 2) {
html_line += "*";
}
else {
html_line += "";
}
}
}
warnings.push({
type: "italic-not-closed",
message: "Italic not closed, may be missing closing '*'? Backslash the '*' if this is intentional",
line_number,
});
}
else if (asterisk_num === 2) {
//remove the last and replace it with a **
let split = html_line.split("");
html_line = "";
for (let ii = 0; ii < split.length; ii++) {
html_line += split[ii];
if (ii !== split.length - 1) {
if (ii === split.length - 2) {
html_line += "**";
}
else {
html_line += "";
}
}
}
warnings.push({
type: "bold-not-closed",
message: "Bold not closed, may be missing closing '**'? Backslash the '**' if this is intentional",
line_number,
});
}
asterisk_num = 0;
asterisk_out_num = 0;
in_asterisk = false;
}
if (in_superscript) {
//superscript never ended
//remove the last and replace it with a ^
let split = html_line.split("");
html_line = "";
for (let ii = 0; ii < split.length; ii++) {
html_line += split[ii];
if (ii !== split.length - 1) {
if (ii === split.length - 2) {
html_line += "^";
}
else {
html_line += "";
}
}
}
in_superscript = false;
warnings.push({
type: "superscript-not-closed",
message: "Superscript not closed, may be missing closing '^'? Backslash the '^' if this is intentional",
line_number,
});
}
//ending table row
if (in_table) {
in_table_header = false;
html_line += "\n";
}
html += html_line;
if (html_line.startsWith(" ")) {
html += "") || html_line.startsWith("
")) && (in_unordered_list || in_ordered_list)) {
html += "
\n";
continue;
}
else if (in_blockquote && chars[i - 1] === "\n" && (char !== ">" || chars[i + 1] !== " ")) {
if (blockquote_list) {
//end list if list started in blockquote and blockquote ends
blockquote_list = false;
if (in_unordered_list) {
html += "\n
\n";
}
else if (in_ordered_list) {
html += "\n\n";
}
ordered_list_num = 0;
in_ordered_list = false;
in_unordered_list = false;
}
else {
html += "\n";
}
in_blockquote = false;
}
else if (char === ">" && chars[i + 1] === " " && (chars[i - 1] === "\n" || i === 0)) {
//do not add the '>' to the html
end_add_char = false;
}
else if (char === " " && chars[i - 1] === ">" && chars[i - 2] === "\n") {
//do not add the ' ' in '> ' to the html
end_add_char = false;
}
else if (char === ">" && chars[i + 1] !== " " && (chars[i - 1] === "\n" || i === 0)) {
warnings.push({
type: "blockquote-broken",
message: "Missing space after `>` for blockquote?",
line_number,
});
}
//code blocks
if (char === "`" && chars[i + 1] !== "`" && ((chars.slice(i - 3, i) === "\n``" || (i === 2 && chars.slice(0, i) === "``")) || (in_blockquote && (chars.slice(i - 5, i) === "\n> ``" || (i === 4 && chars.slice(0, i) === "> ``"))))) {
if (!in_code_block) {
//make sure there is ``` further on, that is not backslashed
let skip_next = false;
let end_found = false;
for (let ii = 1; ii < chars.length - i; ii++) {
let adjusted_index = i + ii;
if (skip_next) {
skip_next = false;
continue;
}
if (chars[adjusted_index] === "\\") {
skip_next = true;
}
else if (chars.slice(adjusted_index - 3, adjusted_index + 1) === "\n```" && (adjusted_index === chars.length - 1 || chars[adjusted_index + 1] === "\n")) {
end_found = true;
break;
}
else if (in_blockquote && chars.slice(adjusted_index - 5, adjusted_index + 1) === "\n> ```" && (adjusted_index === chars.length - 1 || chars[adjusted_index + 1] === "\n")) {
end_found = true;
break;
}
else if (in_blockquote && chars[adjusted_index] === "\n" && (chars[adjusted_index + 1] !== ">" || chars[adjusted_index + 2] !== " ")) {
//blockquote ended without finding end
break;
}
}
if (end_found) {
in_code = false;
in_code_block = true;
first_line_code_block = true;
code_block_lang = "";
//at this point html_line would have two backticks (probably a actually) in it
html_line = "";
continue;
}
else {
warnings.push({
type: "code-block-not-closed",
message: "Code block not closed, may be missing closing backticks?",
line_number,
});
}
}
else if (in_code_block && chars[i + 1] === "\n") { // || i === chars.length-1 will be handled by a different part
in_code = false;
in_code_block = false;
html_line = "
";
//we have to repeat some code from later on and add a
if (i === 0 || chars[i - 1] === "\n") {
html_line = "
" + html_line;
}
continue;
}
else {
warnings.push({
type: "code-snippet-not-closed",
message: "Code snippet not closed, may be missing closing backtick?",
line_number,
});
}
}
else if (char === "`" && in_code) {
in_code = false;
html_line += "
";
continue;
}
else if (in_code) {
html_line += char;
continue;
}
//handle tables
if (char === "|" && (chars[i - 1] === "\n" || i === 0 || (in_blockquote && chars.slice(i - 2, i) === "> " && (chars[i - 3] === "\n" || i === 3)))) {
if (!in_table) {
//start of table
in_table = true;
in_table_header = true;
html += "| ${table_item} | \n`; } else { html_line += `${table_item} | \n`; } table_item = ""; continue; } else if (in_table && ((chars[i - 1] === "\n" && char !== "|") || (in_blockquote && chars[i - 3] === "\n" && char !== "|"))) { in_table = false; table_item = ""; //table ends html += "
|---|
" + "#".repeat(heading_level); heading_level = 0; warnings.push({ type: "heading-broken", message: "Missing space after `#` for heading?", line_number, }); } } //handle horizontal rules //similar code as headings to ensure beginning of the line, continuous if (chars.slice(i - horizontal_num - 1, i) === "\n" + "-".repeat(horizontal_num) || (is_first_line && chars.slice(0, i) === "-".repeat(horizontal_num))) { if (char === "-") { horizontal_num++; if (horizontal_num === 3 || (horizontal_num === 2 && chars[chars.length - 1] === "-" && i === chars.length - 2)) { horizontal_rule = true; html_line = "
" + "-".repeat(horizontal_num); } continue; } else if (horizontal_num > 0) { //no longer a horizontal line html_line = "
" + "-".repeat(horizontal_num);
if (horizontal_num > 2) {
warnings.push({
type: "horizontal-rule-broken",
message: "Horizontal rule broken",
line_number,
});
}
}
}
//handle images
if (char === "!" && chars[i + 1] === "[") {
continue;
}
else if (char === "]" && chars[i + 1] === "(" && image_alt !== undefined && image_src === undefined) {
continue;
}
else if (char === "[" && chars[i - 1] === "!" && image_alt === undefined && image_src === undefined) {
image_alt = "";
continue;
}
else if (char === "(" && chars[i - 1] === "]" && image_alt !== undefined && image_src === undefined) {
image_src = "";
continue;
}
else if ((char === ")" || (chars[i + 1] === ")" && i + 1 === chars.length - 1)) && image_src !== undefined) {
if (chars[i + 1] === ")" && i + 1 === chars.length - 1) {
image_src += char;
}
if (image_alt === "") {
warnings.push({
type: "missing-image-alt",
message: "Image is missing alt text, this is bad for accessibility",
line_number,
});
}
html_line += ``;
was_image = true;
image_alt = undefined;
image_src = undefined;
continue;
}
else if (image_alt !== undefined && image_src === undefined && !(char === "]" && chars[i + 1] === "(")) {
image_alt += char;
continue;
}
else if (image_src !== undefined) {
image_src += char;
continue;
}
else if (was_image) {
was_image = false;
}
//handle links
if (char === "[") {
link_content = "";
continue;
}
else if (char === "]" && chars[i + 1] === "(" && link_content !== undefined && link_href === undefined) {
continue;
}
else if (char === "(" && chars[i - 1] === "]" && link_content !== undefined && link_href === undefined) {
link_href = "";
continue;
}
else if ((char === ")" || (chars[i + 1] === ")" && i + 1 === chars.length - 1)) && link_href !== undefined && link_content !== undefined) {
let before_link;
if (chars[i + 1] === ")" && i + 1 === chars.length - 1) {
link_href += char;
before_link = i - link_href.length - link_content.length - 3;
}
else {
before_link = i - link_href.length - link_content.length - 4;
}
if (chars[before_link] === "\n" || before_link === -1) {
html_line = "
"; } if (link_content === "") { warnings.push({ type: "empty-link", message: "Link missing text", line_number, }); } //":" includes protocols like http:// https:// wss:// and app uris if (!link_href.includes(":") && !link_href.startsWith("./") && !link_href.startsWith("/")) { warnings.push({ type: "weird-href", message: "Link href does not start with './' or '/' or contain ':', please double check it", line_number, }); } html_line += `${link_content}`; was_link = true; link_content = undefined; link_href = undefined; continue; } else if (link_content !== undefined && link_href === undefined) { link_content += char; continue; } else if (link_href !== undefined) { link_href += char; continue; } else if (was_link) { was_link = false; } //add beginning paragraph if (i === 0 || chars[i - 1] === "\n") { html_line = "
" + html_line; } //handle italics and bolds if (char === "*" && asterisk_num < 2 && !in_asterisk) { asterisk_num++; if (asterisk_num === 1 && chars[i + 1] !== "*") { html_line += ""; in_asterisk = true; } else if (asterisk_num === 2) { html_line += ""; in_asterisk = true; } continue; } else if (char === "*" && in_asterisk) { asterisk_out_num++; if (asterisk_out_num === asterisk_num) { if (asterisk_num === 1) { html_line += ""; } else if (asterisk_num === 2) { html_line += ""; } in_asterisk = false; asterisk_num = 0; asterisk_out_num = 0; continue; } else if (asterisk_out_num === 1 && chars[i + 1] === "*") { //implied that asterisk_num === 2 here due to previous if statement continue; } } else if (char !== "*" && in_asterisk) { asterisk_out_num = 0; } //handle superscripts if (char === "^") { if (in_superscript) { in_superscript = false; html_line += ""; continue; } else { in_superscript = true; html_line += ""; continue; } } // if (end_add_char) { html_line += char; } } return { html, warnings, }; } exports.parse_md_to_html_with_warnings = parse_md_to_html_with_warnings; function parse_md_to_html(md) { return parse_md_to_html_with_warnings(md).html; } exports.parse_md_to_html = parse_md_to_html;