2020-06-06 00:03:37 +02:00
|
|
|
"use strict";
|
2016-09-10 11:09:24 +02:00
|
|
|
|
2022-02-14 23:39:28 +01:00
|
|
|
import { marked } from "marked";
|
2020-06-23 19:24:59 +02:00
|
|
|
const DOMPurify = require("dompurify");
|
2016-09-10 11:09:24 +02:00
|
|
|
|
|
|
|
class BaseMarkdownWrapper {
|
|
|
|
preprocess(text) {
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
|
|
|
postprocess(text) {
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SjisWrapper extends BaseMarkdownWrapper {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.buf = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
preprocess(text) {
|
|
|
|
return text.replace(
|
2020-06-06 00:03:37 +02:00
|
|
|
/\[sjis\]((?:[^\[]|\[(?!\/?sjis\]))+)\[\/sjis\]/gi,
|
2016-09-10 11:09:24 +02:00
|
|
|
(match, capture) => {
|
2020-06-06 00:03:37 +02:00
|
|
|
var ret = "%%%SJIS" + this.buf.length;
|
2016-09-10 11:09:24 +02:00
|
|
|
this.buf.push(capture);
|
|
|
|
return ret;
|
2020-06-06 00:03:37 +02:00
|
|
|
}
|
|
|
|
);
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
postprocess(text) {
|
|
|
|
return text.replace(
|
|
|
|
/(?:<p>)?%%%SJIS(\d+)(?:<\/p>)?/,
|
|
|
|
(match, capture) => {
|
2020-06-06 00:03:37 +02:00
|
|
|
return '<div class="sjis">' + this.buf[capture] + "</div>";
|
|
|
|
}
|
|
|
|
);
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// fix \ before ~ being stripped away
|
|
|
|
class TildeWrapper extends BaseMarkdownWrapper {
|
|
|
|
preprocess(text) {
|
2020-06-06 00:03:37 +02:00
|
|
|
return text.replace(/\\~/g, "%%%T");
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
postprocess(text) {
|
2020-06-06 00:03:37 +02:00
|
|
|
return text.replace(/%%%T/g, "\\~");
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-04 20:09:35 +02:00
|
|
|
// prevent ^#... from being treated as headers, due to tag permalinks
|
2016-09-10 11:09:24 +02:00
|
|
|
class TagPermalinkFixWrapper extends BaseMarkdownWrapper {
|
|
|
|
preprocess(text) {
|
2020-06-06 00:03:37 +02:00
|
|
|
return text.replace(/^#/g, "%%%#");
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
postprocess(text) {
|
2020-06-06 00:03:37 +02:00
|
|
|
return text.replace(/%%%#/g, "#");
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-04 20:09:35 +02:00
|
|
|
// post, user and tags permalinks
|
2016-09-10 11:09:24 +02:00
|
|
|
class EntityPermalinkWrapper extends BaseMarkdownWrapper {
|
|
|
|
preprocess(text) {
|
|
|
|
text = text.replace(
|
|
|
|
/(^|^\(|(?:[^\]])\(|[\s<>\[\]\)])([+#@][a-zA-Z0-9_-]+)/g,
|
2020-06-06 00:03:37 +02:00
|
|
|
"$1[$2]($2)"
|
|
|
|
);
|
|
|
|
text = text.replace(/\]\(@(\d+)\)/g, "](/post/$1)");
|
|
|
|
text = text.replace(/\]\(\+([a-zA-Z0-9_-]+)\)/g, "](/user/$1)");
|
|
|
|
text = text.replace(/\]\(#([a-zA-Z0-9_-]+)\)/g, "](/posts/query=$1)");
|
2016-09-10 11:09:24 +02:00
|
|
|
return text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SearchPermalinkWrapper extends BaseMarkdownWrapper {
|
|
|
|
postprocess(text) {
|
|
|
|
return text.replace(
|
2020-06-06 00:03:37 +02:00
|
|
|
/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/gi,
|
|
|
|
'<a href="/posts/query=$1"><code>$1</code></a>'
|
|
|
|
);
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SpoilersWrapper extends BaseMarkdownWrapper {
|
|
|
|
postprocess(text) {
|
|
|
|
return text.replace(
|
2020-06-06 00:03:37 +02:00
|
|
|
/\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/gi,
|
|
|
|
'<span class="spoiler">$1</span>'
|
|
|
|
);
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SmallWrapper extends BaseMarkdownWrapper {
|
|
|
|
postprocess(text) {
|
|
|
|
return text.replace(
|
2020-06-06 00:03:37 +02:00
|
|
|
/\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/gi,
|
|
|
|
"<small>$1</small>"
|
|
|
|
);
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class StrikeThroughWrapper extends BaseMarkdownWrapper {
|
|
|
|
postprocess(text) {
|
2020-06-06 00:03:37 +02:00
|
|
|
text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, "$1<del>$3</del>");
|
|
|
|
return text.replace(/\\~/g, "~");
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-22 23:12:58 +01:00
|
|
|
function createRenderer() {
|
|
|
|
function sanitize(str) {
|
2020-06-06 00:03:37 +02:00
|
|
|
return str.replace(/&<"/g, (m) => {
|
|
|
|
if (m === "&") {
|
|
|
|
return "&";
|
2016-12-22 23:12:58 +01:00
|
|
|
}
|
2020-06-06 00:03:37 +02:00
|
|
|
if (m === "<") {
|
|
|
|
return "<";
|
2016-12-22 23:12:58 +01:00
|
|
|
}
|
2020-06-06 00:03:37 +02:00
|
|
|
return """;
|
2016-12-22 23:12:58 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-09-10 11:09:24 +02:00
|
|
|
const renderer = new marked.Renderer();
|
2016-12-22 23:12:58 +01:00
|
|
|
renderer.image = (href, title, alt) => {
|
2021-11-30 00:44:20 +01:00
|
|
|
let [_, url, width, height] =
|
|
|
|
/^(.+?)(?:\s=\s*(\d*)\s*x\s*(\d*)\s*)?$/.exec(href);
|
2016-12-22 23:12:58 +01:00
|
|
|
let res = '<img src="' + sanitize(url) + '" alt="' + sanitize(alt);
|
|
|
|
if (width) {
|
|
|
|
res += '" width="' + width;
|
|
|
|
}
|
|
|
|
if (height) {
|
|
|
|
res += '" height="' + height;
|
|
|
|
}
|
|
|
|
return res + '">';
|
2017-01-08 02:22:39 +01:00
|
|
|
};
|
2016-12-22 23:12:58 +01:00
|
|
|
return renderer;
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatMarkdown(text) {
|
|
|
|
const renderer = createRenderer();
|
2016-09-10 11:09:24 +02:00
|
|
|
const options = {
|
|
|
|
renderer: renderer,
|
|
|
|
breaks: true,
|
|
|
|
smartypants: true,
|
|
|
|
};
|
|
|
|
let wrappers = [
|
|
|
|
new SjisWrapper(),
|
|
|
|
new TildeWrapper(),
|
|
|
|
new TagPermalinkFixWrapper(),
|
|
|
|
new EntityPermalinkWrapper(),
|
|
|
|
new SearchPermalinkWrapper(),
|
|
|
|
new SpoilersWrapper(),
|
|
|
|
new SmallWrapper(),
|
|
|
|
new StrikeThroughWrapper(),
|
|
|
|
];
|
|
|
|
for (let wrapper of wrappers) {
|
|
|
|
text = wrapper.preprocess(text);
|
|
|
|
}
|
|
|
|
text = marked(text, options);
|
|
|
|
wrappers.reverse();
|
|
|
|
for (let wrapper of wrappers) {
|
|
|
|
text = wrapper.postprocess(text);
|
|
|
|
}
|
2020-06-23 19:24:59 +02:00
|
|
|
return DOMPurify.sanitize(text);
|
2016-09-10 11:09:24 +02:00
|
|
|
}
|
|
|
|
|
2016-09-10 11:11:07 +02:00
|
|
|
function formatInlineMarkdown(text) {
|
2016-12-22 23:12:58 +01:00
|
|
|
const renderer = createRenderer();
|
2016-09-10 11:11:07 +02:00
|
|
|
const options = {
|
|
|
|
renderer: renderer,
|
|
|
|
breaks: true,
|
|
|
|
smartypants: true,
|
|
|
|
};
|
|
|
|
let wrappers = [
|
|
|
|
new TildeWrapper(),
|
|
|
|
new EntityPermalinkWrapper(),
|
|
|
|
new SearchPermalinkWrapper(),
|
|
|
|
new SpoilersWrapper(),
|
|
|
|
new SmallWrapper(),
|
|
|
|
new StrikeThroughWrapper(),
|
|
|
|
];
|
|
|
|
for (let wrapper of wrappers) {
|
|
|
|
text = wrapper.preprocess(text);
|
|
|
|
}
|
|
|
|
text = marked.inlineLexer(text, [], options);
|
|
|
|
wrappers.reverse();
|
|
|
|
for (let wrapper of wrappers) {
|
|
|
|
text = wrapper.postprocess(text);
|
|
|
|
}
|
2020-06-23 19:24:59 +02:00
|
|
|
return DOMPurify.sanitize(text);
|
2016-09-10 11:11:07 +02:00
|
|
|
}
|
|
|
|
|
2016-09-10 11:09:24 +02:00
|
|
|
module.exports = {
|
|
|
|
formatMarkdown: formatMarkdown,
|
2016-09-10 11:11:07 +02:00
|
|
|
formatInlineMarkdown: formatInlineMarkdown,
|
2016-09-10 11:09:24 +02:00
|
|
|
};
|