"use strict"; const marked = require("marked"); const DOMPurify = require("dompurify"); class BaseMarkdownWrapper { preprocess(text) { return text; } postprocess(text) { return text; } } class SjisWrapper extends BaseMarkdownWrapper { constructor() { super(); this.buf = []; } preprocess(text) { return text.replace( /\[sjis\]((?:[^\[]|\[(?!\/?sjis\]))+)\[\/sjis\]/gi, (match, capture) => { var ret = "%%%SJIS" + this.buf.length; this.buf.push(capture); return ret; } ); } postprocess(text) { return text.replace( /(?:

)?%%%SJIS(\d+)(?:<\/p>)?/, (match, capture) => { return '

' + this.buf[capture] + "
"; } ); } } // fix \ before ~ being stripped away class TildeWrapper extends BaseMarkdownWrapper { preprocess(text) { return text.replace(/\\~/g, "%%%T"); } postprocess(text) { return text.replace(/%%%T/g, "\\~"); } } // prevent ^#... from being treated as headers, due to tag permalinks class TagPermalinkFixWrapper extends BaseMarkdownWrapper { preprocess(text) { return text.replace(/^#/g, "%%%#"); } postprocess(text) { return text.replace(/%%%#/g, "#"); } } // post, user and tags permalinks class EntityPermalinkWrapper extends BaseMarkdownWrapper { preprocess(text) { text = text.replace( /(^|^\(|(?:[^\]])\(|[\s<>\[\]\)])([+#@][a-zA-Z0-9_-]+)/g, "$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)"); return text; } } class SearchPermalinkWrapper extends BaseMarkdownWrapper { postprocess(text) { return text.replace( /\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/gi, '$1' ); } } class SpoilersWrapper extends BaseMarkdownWrapper { postprocess(text) { return text.replace( /\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/gi, '$1' ); } } class SmallWrapper extends BaseMarkdownWrapper { postprocess(text) { return text.replace( /\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/gi, "$1" ); } } class StrikeThroughWrapper extends BaseMarkdownWrapper { postprocess(text) { text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, "$1$3"); return text.replace(/\\~/g, "~"); } } function createRenderer() { function sanitize(str) { return str.replace(/&<"/g, (m) => { if (m === "&") { return "&"; } if (m === "<") { return "<"; } return """; }); } const renderer = new marked.Renderer(); renderer.image = (href, title, alt) => { let [_, url, width, height] = /^(.+?)(?:\s=\s*(\d*)\s*x\s*(\d*)\s*)?$/.exec(href); let res = '' + sanitize(alt);
        if (width) {
            res += ''; }; return renderer; } function formatMarkdown(text) { const renderer = createRenderer(); 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); } return DOMPurify.sanitize(text); } function formatInlineMarkdown(text) { const renderer = createRenderer(); 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); } return DOMPurify.sanitize(text); } module.exports = { formatMarkdown: formatMarkdown, formatInlineMarkdown: formatInlineMarkdown, };