diff options
| author | rr- | 2016-09-10 11:09:24 +0200 |
|---|---|---|
| committer | rr- | 2016-09-10 11:36:02 +0200 |
| commit | e05e0e5fd22cc9ffaccc58a511651719937ef26f (patch) | |
| tree | 9a6da6806a9c763c2c08f2695a55121b85cd8a8c /client/js | |
| parent | 16d04adde0de461cd992f63440a84c9ea66ee106 (diff) | |
client/util: refactor Markdown formatter code
Diffstat (limited to 'client/js')
| -rw-r--r-- | client/js/util/markdown.js | 137 | ||||
| -rw-r--r-- | client/js/util/misc.js | 66 |
2 files changed, 139 insertions, 64 deletions
diff --git a/client/js/util/markdown.js b/client/js/util/markdown.js new file mode 100644 index 0000000..3b79c47 --- /dev/null +++ b/client/js/util/markdown.js @@ -0,0 +1,137 @@ +'use strict'; + +const marked = require('marked'); + +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\]/ig, + (match, capture) => { + var ret = '%%%SJIS' + this.buf.length; + this.buf.push(capture); + return ret; + }); + } + + postprocess(text) { + return text.replace( + /(?:<p>)?%%%SJIS(\d+)(?:<\/p>)?/, + (match, capture) => { + return '<div class="sjis">' + this.buf[capture] + '</div>'; + }); + } +} + +// 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\]/ig, + '<a href="/posts/query=$1"><code>$1</code></a>'); + } +} + +class SpoilersWrapper extends BaseMarkdownWrapper { + postprocess(text) { + return text.replace( + /\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/ig, + '<span class="spoiler">$1</span>'); + } +} + +class SmallWrapper extends BaseMarkdownWrapper { + postprocess(text) { + return text.replace( + /\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/ig, + '<small>$1</small>'); + } +} + +class StrikeThroughWrapper extends BaseMarkdownWrapper { + postprocess(text) { + text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>'); + return text.replace(/\\~/g, '~'); + } +} + +function formatMarkdown(text) { + const renderer = new marked.Renderer(); + const options = { + renderer: renderer, + breaks: true, + sanitize: 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 text; +} + +module.exports = { + formatMarkdown: formatMarkdown, +}; diff --git a/client/js/util/misc.js b/client/js/util/misc.js index 83e286d..256fae6 100644 --- a/client/js/util/misc.js +++ b/client/js/util/misc.js @@ -1,6 +1,6 @@ 'use strict'; -const marked = require('marked'); +const markdown = require('./markdown.js'); function decamelize(str, sep) { sep = sep === undefined ? '-' : sep; @@ -92,69 +92,7 @@ function formatRelativeTime(timeString) { } function formatMarkdown(text) { - const renderer = new marked.Renderer(); - - const options = { - renderer: renderer, - breaks: true, - sanitize: true, - smartypants: true, - }; - - const sjis = []; - - const preDecorator = text => { - text = text.replace( - /\[sjis\]((?:[^\[]|\[(?!\/?sjis\]))+)\[\/sjis\]/ig, - (match, capture) => { - var ret = '%%%SJIS' + sjis.length; - sjis.push(capture); - return ret; - }); - //prevent ^#... from being treated as headers, due to tag permalinks - text = text.replace(/^#/g, '%%%#'); - //fix \ before ~ being stripped away - text = text.replace(/\\~/g, '%%%T'); - //post, user and tags premalinks - 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; - }; - - const postDecorator = text => { - //restore fixes - text = text.replace(/%%%T/g, '\\~'); - text = text.replace(/%%%#/g, '#'); - - text = text.replace( - /(?:<p>)?%%%SJIS(\d+)(?:<\/p>)?/, - (match, capture) => { - return '<div class="sjis">' + sjis[capture] + '</div>'; - }); - - //search permalinks - text = text.replace( - /\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig, - '<a href="/posts/query=$1"><code>$1</code></a>'); - //spoilers - text = text.replace( - /\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/ig, - '<span class="spoiler">$1</span>'); - //[small] - text = text.replace( - /\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/ig, - '<small>$1</small>'); - //strike-through - text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>'); - text = text.replace(/\\~/g, '~'); - return text; - }; - - return postDecorator(marked(preDecorator(text), options)); + return markdown.formatMarkdown(text); } function formatUrlParameters(dict) { |