Merge pull request #3503 from MasterQ32/markdown-renderer
Starts to implement markdown parser.
This commit is contained in:
commit
0dbdb6329f
@ -1315,9 +1315,289 @@
|
|||||||
return markdown(firstLine);
|
return markdown(firstLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
function markdown(mdText) {
|
function markdown(input) {
|
||||||
// TODO implement more
|
const raw_lines = input.split('\n'); // zig allows no '\r', so we don't need to split on CR
|
||||||
return escapeHtml(mdText);
|
const lines = [];
|
||||||
|
|
||||||
|
// PHASE 1:
|
||||||
|
// Dissect lines and determine the type for each line.
|
||||||
|
// Also computes indentation level and removes unnecessary whitespace
|
||||||
|
|
||||||
|
var is_reading_code = false;
|
||||||
|
var code_indent = 0;
|
||||||
|
for (var line_no = 0; line_no < raw_lines.length; line_no++) {
|
||||||
|
const raw_line = raw_lines[line_no];
|
||||||
|
|
||||||
|
const line = {
|
||||||
|
indent: 0,
|
||||||
|
raw_text: raw_line,
|
||||||
|
text: raw_line.trim(),
|
||||||
|
type: "p", // p, h1 … h6, code, ul, ol, blockquote, skip, empty
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!is_reading_code) {
|
||||||
|
while ((line.indent < line.raw_text.length) && line.raw_text[line.indent] == ' ') {
|
||||||
|
line.indent += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.text.startsWith("######")) {
|
||||||
|
line.type = "h6";
|
||||||
|
line.text = line.text.substr(6);
|
||||||
|
}
|
||||||
|
else if (line.text.startsWith("#####")) {
|
||||||
|
line.type = "h5";
|
||||||
|
line.text = line.text.substr(5);
|
||||||
|
}
|
||||||
|
else if (line.text.startsWith("####")) {
|
||||||
|
line.type = "h4";
|
||||||
|
line.text = line.text.substr(4);
|
||||||
|
}
|
||||||
|
else if (line.text.startsWith("###")) {
|
||||||
|
line.type = "h3";
|
||||||
|
line.text = line.text.substr(3);
|
||||||
|
}
|
||||||
|
else if (line.text.startsWith("##")) {
|
||||||
|
line.type = "h2";
|
||||||
|
line.text = line.text.substr(2);
|
||||||
|
}
|
||||||
|
else if (line.text.startsWith("#")) {
|
||||||
|
line.type = "h1";
|
||||||
|
line.text = line.text.substr(1);
|
||||||
|
}
|
||||||
|
else if (line.text.startsWith("-")) {
|
||||||
|
line.type = "ul";
|
||||||
|
line.text = line.text.substr(1);
|
||||||
|
}
|
||||||
|
else if (line.text.match(/\d+\./)) {
|
||||||
|
const match = line.match(/(\d+)\./);
|
||||||
|
line.type = "ul";
|
||||||
|
line.text = line.text.substr(match[0].length);
|
||||||
|
line.ordered_number = Number(match[1].length);
|
||||||
|
}
|
||||||
|
else if (line.text == "```") {
|
||||||
|
line.type = "skip";
|
||||||
|
is_reading_code = true;
|
||||||
|
code_indent = line.indent;
|
||||||
|
}
|
||||||
|
else if (line.text == "") {
|
||||||
|
line.type = "empty";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (line.text == "```") {
|
||||||
|
is_reading_code = false;
|
||||||
|
line.type = "skip";
|
||||||
|
} else {
|
||||||
|
line.type = "code";
|
||||||
|
line.text = line.raw_text.substr(code_indent); // remove the indent of the ``` from all the code block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.type != "skip") {
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHASE 2:
|
||||||
|
// Render HTML from markdown lines.
|
||||||
|
// Look at each line and emit fitting HTML code
|
||||||
|
|
||||||
|
function markdownInlines(innerText, stopChar) {
|
||||||
|
|
||||||
|
// inline types:
|
||||||
|
// **{INLINE}** : <strong>
|
||||||
|
// __{INLINE}__ : <u>
|
||||||
|
// ~~{INLINE}~~ : <s>
|
||||||
|
// *{INLINE}* : <emph>
|
||||||
|
// _{INLINE}_ : <emph>
|
||||||
|
// `{TEXT}` : <code>
|
||||||
|
// [{INLINE}]({URL}) : <a>
|
||||||
|
//  : <img>
|
||||||
|
// [[std;format.fmt]] : <a> (inner link)
|
||||||
|
|
||||||
|
const formats = [
|
||||||
|
{
|
||||||
|
marker: "**",
|
||||||
|
tag: "strong",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
marker: "~~",
|
||||||
|
tag: "s",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
marker: "__",
|
||||||
|
tag: "u",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
marker: "*",
|
||||||
|
tag: "em",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const stack = [];
|
||||||
|
|
||||||
|
var innerHTML = "";
|
||||||
|
var currentRun = "";
|
||||||
|
|
||||||
|
function flushRun() {
|
||||||
|
if (currentRun != "") {
|
||||||
|
innerHTML += escapeHtml(currentRun);
|
||||||
|
}
|
||||||
|
currentRun = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsing_code = false;
|
||||||
|
var codetag = "";
|
||||||
|
var in_code = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < innerText.length; i++) {
|
||||||
|
|
||||||
|
if (parsing_code && in_code) {
|
||||||
|
if (innerText.substr(i, codetag.length) == codetag) {
|
||||||
|
// remove leading and trailing whitespace if string both starts and ends with one.
|
||||||
|
if (currentRun[0] == " " && currentRun[currentRun.length - 1] == " ") {
|
||||||
|
currentRun = currentRun.substr(1, currentRun.length - 2);
|
||||||
|
}
|
||||||
|
flushRun();
|
||||||
|
i += codetag.length - 1;
|
||||||
|
in_code = false;
|
||||||
|
parsing_code = false;
|
||||||
|
innerHTML += "</code>";
|
||||||
|
codetag = "";
|
||||||
|
} else {
|
||||||
|
currentRun += innerText[i];
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (innerText[i] == "`") {
|
||||||
|
flushRun();
|
||||||
|
if (!parsing_code) {
|
||||||
|
innerHTML += "<code>";
|
||||||
|
}
|
||||||
|
parsing_code = true;
|
||||||
|
codetag += "`";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsing_code) {
|
||||||
|
currentRun += innerText[i];
|
||||||
|
in_code = true;
|
||||||
|
} else {
|
||||||
|
var any = false;
|
||||||
|
for (var idx = (stack.length > 0 ? -1 : 0); idx < formats.length; idx++) {
|
||||||
|
const fmt = idx >= 0 ? formats[idx] : stack[stack.length - 1];
|
||||||
|
if (innerText.substr(i, fmt.marker.length) == fmt.marker) {
|
||||||
|
flushRun();
|
||||||
|
if (stack[stack.length - 1] == fmt) {
|
||||||
|
stack.pop();
|
||||||
|
innerHTML += "</" + fmt.tag + ">";
|
||||||
|
} else {
|
||||||
|
stack.push(fmt);
|
||||||
|
innerHTML += "<" + fmt.tag + ">";
|
||||||
|
}
|
||||||
|
i += fmt.marker.length - 1;
|
||||||
|
any = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!any) {
|
||||||
|
currentRun += innerText[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flushRun();
|
||||||
|
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const fmt = stack.pop();
|
||||||
|
innerHTML += "</" + fmt.tag + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
return innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
var html = "";
|
||||||
|
for (var line_no = 0; line_no < lines.length; line_no++) {
|
||||||
|
const line = lines[line_no];
|
||||||
|
|
||||||
|
function previousLineIs(type) {
|
||||||
|
if (line_no > 0) {
|
||||||
|
return (lines[line_no - 1].type == type);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextLineIs(type) {
|
||||||
|
if (line_no < (lines.length - 1)) {
|
||||||
|
return (lines[line_no + 1].type == type);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreviousLineIndent() {
|
||||||
|
if (line_no > 0) {
|
||||||
|
return lines[line_no - 1].indent;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextLineIndent() {
|
||||||
|
if (line_no < (lines.length - 1)) {
|
||||||
|
return lines[line_no + 1].indent;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (line.type) {
|
||||||
|
case "h1":
|
||||||
|
case "h2":
|
||||||
|
case "h3":
|
||||||
|
case "h4":
|
||||||
|
case "h5":
|
||||||
|
case "h6":
|
||||||
|
html += "<" + line.type + ">" + markdownInlines(line.text) + "</" + line.type + ">\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ul":
|
||||||
|
case "ol":
|
||||||
|
if (!previousLineIs("ul") || getPreviousLineIndent() < line.indent) {
|
||||||
|
html += "<" + line.type + ">\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
html += "<li>" + markdownInlines(line.text) + "</li>\n";
|
||||||
|
|
||||||
|
if (!nextLineIs("ul") || getNextLineIndent() < line.indent) {
|
||||||
|
html += "</" + line.type + ">\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "p":
|
||||||
|
if (!previousLineIs("p")) {
|
||||||
|
html += "<p>\n";
|
||||||
|
}
|
||||||
|
html += markdownInlines(line.text) + "\n";
|
||||||
|
if (!nextLineIs("p")) {
|
||||||
|
html += "</p>\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "code":
|
||||||
|
if (!previousLineIs("code")) {
|
||||||
|
html += "<pre><code>";
|
||||||
|
}
|
||||||
|
html += escapeHtml(line.text) + "\n";
|
||||||
|
if (!nextLineIs("code")) {
|
||||||
|
html += "</code></pre>\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function activateSelectedResult() {
|
function activateSelectedResult() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user