mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 22:13:26 +00:00
We also take fenced_code out of the global namespace, since it only requires katex and underscore. And we fix the exports to be ES6 style.
241 lines
7.0 KiB
JavaScript
241 lines
7.0 KiB
JavaScript
import _ from "underscore";
|
|
import katex from "katex/dist/katex.min.js";
|
|
|
|
// Parsing routine that can be dropped in to message parsing
|
|
// and formats code blocks
|
|
//
|
|
// This supports arbitrarily nested code blocks as well as
|
|
// auto-completing code blocks missing a trailing close.
|
|
|
|
// See backend fenced_code.py:71 for associated regexp
|
|
const fencestr =
|
|
"^(~{3,}|`{3,})" + // Opening Fence
|
|
"[ ]*" + // Spaces
|
|
"(" +
|
|
"\\{?\\.?" +
|
|
"([a-zA-Z0-9_+-./#]*)" + // Language
|
|
"\\}?" +
|
|
")" +
|
|
"[ ]*" + // Spaces
|
|
"(" +
|
|
"\\{?\\.?" +
|
|
"([^~`]*)" + // Header (see fenced_code.py)
|
|
"\\}?" +
|
|
")" +
|
|
"$";
|
|
const fence_re = new RegExp(fencestr);
|
|
|
|
// Default stashing function does nothing
|
|
let stash_func = function (text) {
|
|
return text;
|
|
};
|
|
|
|
export function wrap_code(code) {
|
|
// Trim trailing \n until there's just one left
|
|
// This mirrors how pygments handles code input
|
|
return (
|
|
'<div class="codehilite"><pre><span></span><code>' +
|
|
_.escape(code.replace(/^\n+|\n+$/g, "")) +
|
|
"\n</code></pre></div>\n"
|
|
);
|
|
}
|
|
|
|
function wrap_quote(text) {
|
|
const paragraphs = text.split("\n\n");
|
|
const quoted_paragraphs = [];
|
|
|
|
// Prefix each quoted paragraph with > at the
|
|
// beginning of each line
|
|
for (const paragraph of paragraphs) {
|
|
const lines = paragraph.split("\n");
|
|
quoted_paragraphs.push(
|
|
lines
|
|
.filter((line) => line !== "")
|
|
.map((line) => "> " + line)
|
|
.join("\n"),
|
|
);
|
|
}
|
|
|
|
return quoted_paragraphs.join("\n\n");
|
|
}
|
|
|
|
function wrap_tex(tex) {
|
|
try {
|
|
return katex.renderToString(tex, {
|
|
displayMode: true,
|
|
});
|
|
} catch (ex) {
|
|
return '<span class="tex-error">' + _.escape(tex) + "</span>";
|
|
}
|
|
}
|
|
|
|
function wrap_spoiler(header, text, stash_func) {
|
|
const output = [];
|
|
const header_div_open_html = '<div class="spoiler-block"><div class="spoiler-header">';
|
|
const end_header_start_content_html = '</div><div class="spoiler-content" aria-hidden="true">';
|
|
const footer_html = "</div></div>";
|
|
|
|
output.push(stash_func(header_div_open_html));
|
|
output.push(header);
|
|
output.push(stash_func(end_header_start_content_html));
|
|
output.push(text);
|
|
output.push(stash_func(footer_html));
|
|
return output.join("\n\n");
|
|
}
|
|
|
|
export function set_stash_func(stash_handler) {
|
|
stash_func = stash_handler;
|
|
}
|
|
|
|
export function process_fenced_code(content) {
|
|
const input = content.split("\n");
|
|
const output = [];
|
|
const handler_stack = [];
|
|
let consume_line;
|
|
|
|
function handler_for_fence(output_lines, fence, lang, header) {
|
|
// lang is ignored except for 'quote', as we
|
|
// don't do syntax highlighting yet
|
|
return (function () {
|
|
const lines = [];
|
|
if (lang === "quote") {
|
|
return {
|
|
handle_line(line) {
|
|
if (line === fence) {
|
|
this.done();
|
|
} else {
|
|
consume_line(lines, line);
|
|
}
|
|
},
|
|
|
|
done() {
|
|
const text = wrap_quote(lines.join("\n"));
|
|
output_lines.push("");
|
|
output_lines.push(text);
|
|
output_lines.push("");
|
|
handler_stack.pop();
|
|
},
|
|
};
|
|
}
|
|
|
|
if (lang === "math") {
|
|
return {
|
|
handle_line(line) {
|
|
if (line === fence) {
|
|
this.done();
|
|
} else {
|
|
lines.push(line);
|
|
}
|
|
},
|
|
|
|
done() {
|
|
const text = wrap_tex(lines.join("\n"));
|
|
const placeholder = stash_func(text, true);
|
|
output_lines.push("");
|
|
output_lines.push(placeholder);
|
|
output_lines.push("");
|
|
handler_stack.pop();
|
|
},
|
|
};
|
|
}
|
|
|
|
if (lang === "spoiler") {
|
|
return {
|
|
handle_line(line) {
|
|
if (line === fence) {
|
|
this.done();
|
|
} else {
|
|
lines.push(line);
|
|
}
|
|
},
|
|
|
|
done() {
|
|
const text = wrap_spoiler(header, lines.join("\n"), stash_func);
|
|
output_lines.push("");
|
|
output_lines.push(text);
|
|
output_lines.push("");
|
|
handler_stack.pop();
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
handle_line(line) {
|
|
if (line === fence) {
|
|
this.done();
|
|
} else {
|
|
lines.push(line.trimRight());
|
|
}
|
|
},
|
|
|
|
done() {
|
|
const text = wrap_code(lines.join("\n"));
|
|
// insert safe HTML that is passed through the parsing
|
|
const placeholder = stash_func(text, true);
|
|
output_lines.push("");
|
|
output_lines.push(placeholder);
|
|
output_lines.push("");
|
|
handler_stack.pop();
|
|
},
|
|
};
|
|
})();
|
|
}
|
|
|
|
function default_hander() {
|
|
return {
|
|
handle_line(line) {
|
|
consume_line(output, line);
|
|
},
|
|
done() {
|
|
handler_stack.pop();
|
|
},
|
|
};
|
|
}
|
|
|
|
consume_line = function consume_line(output_lines, line) {
|
|
const match = fence_re.exec(line);
|
|
if (match) {
|
|
const fence = match[1];
|
|
const lang = match[3];
|
|
const header = match[5];
|
|
const handler = handler_for_fence(output_lines, fence, lang, header);
|
|
handler_stack.push(handler);
|
|
} else {
|
|
output_lines.push(line);
|
|
}
|
|
};
|
|
|
|
const current_handler = default_hander();
|
|
handler_stack.push(current_handler);
|
|
|
|
for (const line of input) {
|
|
const handler = handler_stack[handler_stack.length - 1];
|
|
handler.handle_line(line);
|
|
}
|
|
|
|
// Clean up all trailing blocks by letting them
|
|
// insert closing fences
|
|
while (handler_stack.length !== 0) {
|
|
const handler = handler_stack[handler_stack.length - 1];
|
|
handler.done();
|
|
}
|
|
|
|
if (output.length > 2 && output[output.length - 2] !== "") {
|
|
output.push("");
|
|
}
|
|
|
|
return output.join("\n");
|
|
}
|
|
|
|
const fence_length_re = /^ {0,3}(`{3,})/gm;
|
|
export function get_unused_fence(content) {
|
|
// we only return ``` fences, not ~~~.
|
|
let length = 3;
|
|
let match;
|
|
fence_length_re.lastIndex = 0;
|
|
while ((match = fence_length_re.exec(content)) !== null) {
|
|
length = Math.max(length, match[1].length + 1);
|
|
}
|
|
return "`".repeat(length);
|
|
}
|