nodebb-plugin-markdown
Version:
A Markdown parser for NodeBB
247 lines (210 loc) • 9.57 kB
JavaScript
;
/* global window, jQuery, $, require, config, socket */
(function () {
var Markdown = {};
$(window).on('action:composer.enhanced', function (evt, data) {
var textareaEl = data.postContainer.find('textarea');
Markdown.capturePaste(textareaEl);
Markdown.prepareFormattingTools();
});
Markdown.enhanceCheckbox = function (ev, data) {
if (!data.posts && !data.post) {
return;
} if (data.hasOwnProperty('post')) {
data.posts = [data.post];
}
var disable;
var checkboxEls;
data.posts.forEach(function (post) {
disable = !post.display_edit_tools;
checkboxEls = $('.posts li[data-pid="' + post.pid + '"] .content div.plugin-markdown input[type="checkbox"]');
checkboxEls.on('click', function (e) {
if (disable) {
// Find the post's checkboxes in DOM and make them readonly
e.preventDefault();
}
// Otherwise, edit the post to reflect state change
var _this = this;
var pid = $(this).parents('li[data-pid]').attr('data-pid');
var index = $(this).parents('.content').find('input[type="checkbox"]').toArray().reduce(function (memo, cur, index) {
if (cur === _this) {
memo = index;
}
return memo;
}, null);
socket.emit('plugins.markdown.checkbox.edit', {
pid: pid,
index: index,
state: $(_this).prop('checked'),
});
});
});
};
Markdown.capturePaste = function (targetEl) {
targetEl.on('paste', function (e) {
var triggers = [/^>\s*/, /^\s*\*\s+/, /^\s*\d+\.\s+/, /^\s{4,}/];
var start = e.target.selectionStart;
var line = getLine(targetEl.val(), start);
var trigger = triggers.reduce(function (regexp, cur) {
if (regexp) {
return regexp;
}
return cur.test(line) ? cur : false;
}, false);
var prefix = line.match(trigger);
if (prefix) {
prefix = prefix.shift();
var payload = e.originalEvent.clipboardData.getData('text');
var fixed = payload.replace(/^/gm, prefix).slice(prefix.length);
setTimeout(function () {
var replacement = targetEl.val().slice(0, start) + fixed + targetEl.val().slice(start + payload.length);
targetEl.val(replacement);
}, 0);
}
});
function getLine(text, selectionStart) {
// Break apart into lines, return the line the cursor is in
var lines = text.split('\n');
return lines.reduce(function (memo, cur) {
if (typeof memo !== 'number') {
return memo;
} if (selectionStart > (memo + cur.length)) {
return memo + cur.length + 1;
}
return cur;
}, 0);
}
};
Markdown.highlight = function (data) {
if (data instanceof jQuery.Event) {
highlight($(data.data.selector));
} else {
highlight(data);
}
};
Markdown.prepareFormattingTools = function () {
require([
'composer/formatting',
'composer/controls',
'translator',
], function (formatting, controls, translator) {
if (formatting && controls) {
translator.getTranslations(window.config.userLang || window.config.defaultLang, 'markdown', function (strings) {
formatting.addButtonDispatch('bold', function (textarea, selectionStart, selectionEnd) {
if (selectionStart === selectionEnd) {
var block = controls.getBlockData(textarea, '**', selectionStart);
if (block.in && block.atEnd) {
// At end of bolded string, move cursor past delimiters
controls.updateTextareaSelection(textarea, selectionStart + 2, selectionStart + 2);
} else {
controls.insertIntoTextarea(textarea, '**' + strings.bold + '**');
controls.updateTextareaSelection(textarea, selectionStart + 2, selectionStart + strings.bold.length + 2);
}
} else {
var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '**');
controls.updateTextareaSelection(textarea, selectionStart + 2 + wrapDelta[0], selectionEnd + 2 - wrapDelta[1]);
}
});
formatting.addButtonDispatch('italic', function (textarea, selectionStart, selectionEnd) {
if (selectionStart === selectionEnd) {
var block = controls.getBlockData(textarea, '*', selectionStart);
if (block.in && block.atEnd) {
// At end of italicised string, move cursor past delimiters
controls.updateTextareaSelection(textarea, selectionStart + 1, selectionStart + 1);
} else {
controls.insertIntoTextarea(textarea, '*' + strings.italic + '*');
controls.updateTextareaSelection(textarea, selectionStart + 1, selectionStart + strings.italic.length + 1);
}
} else {
var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '*');
controls.updateTextareaSelection(textarea, selectionStart + 1 + wrapDelta[0], selectionEnd + 1 - wrapDelta[1]);
}
});
formatting.addButtonDispatch('list', function (textarea, selectionStart, selectionEnd) {
if (selectionStart === selectionEnd) {
controls.insertIntoTextarea(textarea, '\n* ' + strings.list_item);
// Highlight "list item"
controls.updateTextareaSelection(textarea, selectionStart + 3, selectionStart + strings.list_item.length + 3);
} else {
var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '\n* ', '');
controls.updateTextareaSelection(textarea, selectionStart + 3 + wrapDelta[0], selectionEnd + 3 - wrapDelta[1]);
}
});
formatting.addButtonDispatch('strikethrough', function (textarea, selectionStart, selectionEnd) {
if (selectionStart === selectionEnd) {
var block = controls.getBlockData(textarea, '~~', selectionStart);
if (block.in && block.atEnd) {
// At end of bolded string, move cursor past delimiters
controls.updateTextareaSelection(textarea, selectionStart + 2, selectionStart + 2);
} else {
controls.insertIntoTextarea(textarea, '~~' + strings.strikethrough_text + '~~');
controls.updateTextareaSelection(textarea, selectionStart + 2, selectionEnd + strings.strikethrough_text.length + 2);
}
} else {
var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '~~', '~~');
controls.updateTextareaSelection(textarea, selectionStart + 2 + wrapDelta[0], selectionEnd + 2 - wrapDelta[1]);
}
});
formatting.addButtonDispatch('code', function (textarea, selectionStart, selectionEnd) {
if (selectionStart === selectionEnd) {
controls.insertIntoTextarea(textarea, '```\n' + strings.code_text + '\n```');
controls.updateTextareaSelection(textarea, selectionStart + 4, selectionEnd + strings.code_text.length + 4);
} else {
var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '```\n', '\n```');
controls.updateTextareaSelection(textarea, selectionStart + 4 + wrapDelta[0], selectionEnd + 4 - wrapDelta[1]);
}
});
formatting.addButtonDispatch('link', function (textarea, selectionStart, selectionEnd) {
if (selectionStart === selectionEnd) {
controls.insertIntoTextarea(textarea, '[' + strings.link_text + '](' + strings.link_url + ')');
controls.updateTextareaSelection(textarea, selectionStart + strings.link_text.length + 3, selectionEnd + strings.link_text.length + strings.link_url.length + 3);
} else {
var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '[', '](' + strings.link_url + ')');
controls.updateTextareaSelection(textarea, selectionEnd + 3 - wrapDelta[1], selectionEnd + strings.link_url.length + 3 - wrapDelta[1]);
}
});
formatting.addButtonDispatch('picture-o', function (textarea, selectionStart, selectionEnd) {
if (selectionStart === selectionEnd) {
controls.insertIntoTextarea(textarea, '');
controls.updateTextareaSelection(textarea, selectionStart + strings.picture_text.length + 4, selectionEnd + strings.picture_text.length + strings.picture_url.length + 4);
} else {
var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '');
controls.updateTextareaSelection(textarea, selectionEnd + 4 - wrapDelta[1], selectionEnd + strings.picture_url.length + 4 - wrapDelta[1]);
}
});
});
}
});
};
function highlight(elements) {
if (parseInt(config.markdown.highlight, 10)) {
require(['highlight', 'highlightjs-line-numbers'], function (hljs) {
elements.each(function (i, block) {
$(block.parentNode).addClass('markdown-highlight');
hljs.highlightBlock(block);
// Check detected language against whitelist and add lines if enabled
if (block.className.split(' ').map(function (className) {
if (className.indexOf('language-') === 0) {
className = className.slice(9);
}
return config.markdown.highlightLinesLanguageList.includes(className) || config.markdown.highlightLinesLanguageList.includes(className);
}).some(Boolean)) {
$(block).attr('data-lines', 1);
hljs.lineNumbersBlock(block);
}
});
});
}
}
$(window).on('action:composer.preview', {
selector: '.composer .preview pre code',
}, Markdown.highlight);
$(window).on('action:topic.loaded', Markdown.enhanceCheckbox);
$(window).on('action:posts.loaded', Markdown.enhanceCheckbox);
$(window).on('action:posts.edited', Markdown.enhanceCheckbox);
$(window).on('action:posts.loaded action:topic.loaded action:posts.edited', function () {
require(['components'], function (components) {
Markdown.highlight(components.get('post/content').find('pre code'));
});
});
}());