aboutsummaryrefslogtreecommitdiff
path: root/client/js/util
diff options
context:
space:
mode:
authorrr-2016-09-10 11:09:24 +0200
committerrr-2016-09-10 11:36:02 +0200
commite05e0e5fd22cc9ffaccc58a511651719937ef26f (patch)
tree9a6da6806a9c763c2c08f2695a55121b85cd8a8c /client/js/util
parent16d04adde0de461cd992f63440a84c9ea66ee106 (diff)
client/util: refactor Markdown formatter code
Diffstat (limited to 'client/js/util')
-rw-r--r--client/js/util/markdown.js137
-rw-r--r--client/js/util/misc.js66
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) {

© 2015 - 2026 Jakob L. Kreuze