287 lines
6.4 KiB
JavaScript
287 lines
6.4 KiB
JavaScript
const md2tokens = (markdown, newline) => {
|
|
// Simple lexer and parser
|
|
let i = 0;
|
|
let current_string = "";
|
|
newline = newline == null ? true : newline;
|
|
let newline_count = newline ? 1 : 0;
|
|
|
|
let tokens = [];
|
|
|
|
let match = (c) => {
|
|
if (c == markdown[i]) {
|
|
i++;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
let peek = (a) => {
|
|
return markdown[i + a];
|
|
}
|
|
|
|
let advance = () => {
|
|
i++;
|
|
if (i < markdown.length)
|
|
return true
|
|
|
|
return false
|
|
}
|
|
|
|
let capture_until = (c) => {
|
|
return capture_until_predicate((i) => markdown[i] == c);
|
|
}
|
|
|
|
let capture_until_predicate = (f) => {
|
|
let capture = "";
|
|
while (!f(i) && markdown.length > i) {
|
|
capture += markdown[i];
|
|
i++;
|
|
}
|
|
return capture;
|
|
}
|
|
|
|
let finish = (type) => {
|
|
if (current_string.trim().length == 0) {
|
|
current_string = "";
|
|
return;
|
|
}
|
|
|
|
tokens.push({ type: type == null ? "span" : type, content: current_string })
|
|
current_string = "";
|
|
}
|
|
|
|
let def = () => {
|
|
current_string += markdown[i];
|
|
newline = false;
|
|
newline_count = 0;
|
|
}
|
|
|
|
while (i < markdown.length) {
|
|
switch (markdown[i]) {
|
|
case '\\':
|
|
i++;
|
|
current_string += markdown[i];
|
|
break;
|
|
|
|
case '*':
|
|
finish();
|
|
advance();
|
|
newline=false;
|
|
newline_count=0;
|
|
let depth_em = 1;
|
|
let type = "em";
|
|
if (match("*")) {
|
|
type = "bold";
|
|
depth_em = 2;
|
|
|
|
if (match("*")) {
|
|
type = "embold";
|
|
depth_em = 3;
|
|
}
|
|
}
|
|
|
|
current_string = capture_until_predicate((i) => { let found = true; for (let j = 0; j < depth_em; j++) { if (markdown[i + j] != "*") { found = false; break; } } return found; })
|
|
|
|
for (let j=0;j<depth_em-1;j++) {
|
|
// console.log(markdown[i] == "\n");
|
|
advance();
|
|
}
|
|
|
|
finish(type);
|
|
break;
|
|
|
|
case "#":
|
|
if (!newline) {
|
|
def();
|
|
break;
|
|
}
|
|
|
|
finish();
|
|
let depth_he = 0;
|
|
do {
|
|
depth_he++;
|
|
} while (match("#"));
|
|
|
|
while (markdown[i] != "\n" && markdown.length > i) {
|
|
current_string += markdown[i];
|
|
i++
|
|
}
|
|
|
|
tokens.push({ type: "header", level: depth_he, content: current_string });
|
|
current_string = "";
|
|
break;
|
|
|
|
case '!':
|
|
if (markdown[i + 1] != "[") {
|
|
def();
|
|
break;
|
|
}
|
|
|
|
finish();
|
|
advance();
|
|
advance();
|
|
let alt_text = capture_until("]");
|
|
advance();
|
|
if (!match("(")) {
|
|
current_string = `![${alt_text}]`;
|
|
def();
|
|
break;
|
|
}
|
|
|
|
let url = capture_until_predicate((i) => markdown[i] == "\"" || markdown[i] == ")");
|
|
let title = "";
|
|
if (match("\"")) { // has title
|
|
title = capture_until("\"");
|
|
advance();
|
|
}
|
|
if (!match(")")) {
|
|
current_string = `;
|
|
break;
|
|
}
|
|
|
|
tokens.push({ type: "image", alt: alt_text, url: url, title: title })
|
|
break;
|
|
|
|
case '-':
|
|
if (!newline) {
|
|
def();
|
|
break;
|
|
}
|
|
|
|
let items = [];
|
|
|
|
advance();
|
|
|
|
do {
|
|
let text = capture_until("\n");
|
|
advance();
|
|
items.push(md2tokens(text, false));
|
|
} while (match("-"));
|
|
|
|
tokens.push({ type: "ul", items: items });
|
|
|
|
break;
|
|
|
|
case '\n':
|
|
if (newline_count > 2)
|
|
break;
|
|
|
|
newline = true;
|
|
newline_count++;
|
|
finish();
|
|
tokens.push({ type: "newline" })
|
|
break;
|
|
|
|
case '[':
|
|
finish();
|
|
advance();
|
|
let text = capture_until("]");
|
|
advance();
|
|
|
|
if (!match("(")) {
|
|
current_string = `[${text}]`;
|
|
def();
|
|
break;
|
|
}
|
|
|
|
let link = capture_until(")");
|
|
|
|
tokens.push({ type: "link", link: link, text: text });
|
|
break;
|
|
|
|
case '`':
|
|
finish();
|
|
advance();
|
|
newline=false;
|
|
newline_count=0;
|
|
let stop = (i) => markdown[i] == "`";
|
|
let type_c = "inlinecode";
|
|
let language = "";
|
|
if (peek(0) == "`" && peek(1) == "`") { // multiline code block
|
|
advance(); advance();
|
|
language = capture_until("\n");
|
|
let f = (i) => markdown[i] == "`";
|
|
stop = (i) => f(i) && f(i + 1) && f(i + 2);
|
|
type_c = "codeblock";
|
|
}
|
|
current_string = capture_until_predicate(stop);
|
|
tokens.push({ type: type_c, content: current_string, language: language });
|
|
current_string = "";
|
|
if (type_c == "codeblock") { advance(); advance(); newline_count=2; } // remove trailing `
|
|
break;
|
|
|
|
default:
|
|
def()
|
|
}
|
|
i++;
|
|
}
|
|
|
|
finish();
|
|
|
|
console.log(tokens);
|
|
|
|
return tokens;
|
|
};
|
|
|
|
const highlight_code = (language, code) => {
|
|
let map = maps[language];
|
|
if (map == undefined)
|
|
return code;
|
|
|
|
return highlight(map, code);
|
|
}
|
|
|
|
const tokens2html = (tokens) => {
|
|
let output = "";
|
|
|
|
for (let token of tokens) {
|
|
switch (token.type) {
|
|
case "span":
|
|
output += `<span>${token.content}</span>`;
|
|
break
|
|
case "bold":
|
|
output += `<b>${token.content}</b>`
|
|
break;
|
|
case "em":
|
|
output += `<i>${token.content}</i>`
|
|
break;
|
|
case "embold":
|
|
output += `<b><i>${token.content}</i></b>`
|
|
break;
|
|
case "header":
|
|
output += `<h${token.level}>${token.content}</h${token.level}>`
|
|
break;
|
|
case "newline":
|
|
output += "<br>"
|
|
break;
|
|
case "image":
|
|
output += `<p style="text-align:center; font-size: 0.85em; cursor: pointer"><img src="${token.url}" alt="${token.alt}" onclick="enlarge(this, event)"><br>${token.title}</p>`;
|
|
break
|
|
case "ul":
|
|
console.log(token);
|
|
output += "<ul>";
|
|
for (let item of token.items) {
|
|
output += `<li>${tokens2html(item)}</li>`
|
|
}
|
|
output += "</ul>";
|
|
break;
|
|
case "inlinecode":
|
|
output += `<code>${token.content}</code>`;
|
|
break;
|
|
case "codeblock":
|
|
output += `<pre class="codeblock" data-language="${token.language}">${highlight_code(token.language, token.content)}</pre>`
|
|
break;
|
|
case "link":
|
|
output += `<a href="${token.link}">${token.text}</a>`;
|
|
break;
|
|
|
|
default:
|
|
throw "Unknown token type " + token.type;
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|