nodebb-plugin-markdown 12.1.7 → 12.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-markdown",
3
- "version": "12.1.7",
3
+ "version": "12.2.0",
4
4
  "description": "A Markdown parser for NodeBB",
5
5
  "main": "index.js",
6
6
  "repository": {
package/plugin.json CHANGED
@@ -14,7 +14,8 @@
14
14
  "styles": "node_modules/highlight.js/styles"
15
15
  },
16
16
  "modules": {
17
- "../admin/plugins/markdown.js": "./public/js/admin.js"
17
+ "../admin/plugins/markdown.js": "./public/js/admin.js",
18
+ "markdown.js": "./public/js/markdown.js"
18
19
  },
19
20
  "languages": "public/languages",
20
21
  "defaultLang": "en_GB",
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- /* globals define, $, socket, bootbox */
4
-
5
3
  define('admin/plugins/markdown', ['settings', 'alerts'], function (Settings, alerts) {
6
4
  var Markdown = {};
7
5
 
@@ -45,30 +43,7 @@ define('admin/plugins/markdown', ['settings', 'alerts'], function (Settings, ale
45
43
  });
46
44
 
47
45
  $('#save').on('click', function () {
48
- Settings.save('markdown', $('.markdown-settings'), function () {
49
- alerts.alert({
50
- type: 'success',
51
- alert_id: 'markdown-saved',
52
- title: 'Reload Required',
53
- message: 'Please reload your NodeBB to have your changes take effect',
54
- clickfn: function () {
55
- socket.emit('admin.reload');
56
- },
57
- timeout: 2500,
58
- });
59
- });
60
- });
61
-
62
- // Warning for "html" option
63
- $('#html').on('change', function () {
64
- var inputEl = $(this);
65
- if (inputEl.prop('checked')) {
66
- bootbox.confirm('Are you sure you wish to disable sanitisation of HTML? <strong>Doing so compromises your forum&apos;s client-side security, and allows malicious users to execute arbitrary javascript on other users&apos; browsers.</strong>', function (result) {
67
- if (!result) {
68
- inputEl.prop('checked', false);
69
- }
70
- });
71
- }
46
+ Settings.save('markdown', $('.markdown-settings'));
72
47
  });
73
48
  };
74
49
 
@@ -1,339 +1,21 @@
1
1
  'use strict';
2
2
 
3
3
  (function () {
4
- var Markdown = {};
5
-
6
- $(window).on('action:composer.enhanced', function (evt, data) {
7
- var textareaEl = data.postContainer.find('textarea');
8
- Markdown.capturePaste(textareaEl);
9
- Markdown.prepareFormattingTools();
10
- });
11
-
12
- Markdown.enhanceCheckbox = function (ev, data) {
13
- if (!data.posts && !data.post) {
14
- return;
15
- } if (data.hasOwnProperty('post')) {
16
- data.posts = [data.post];
17
- }
18
-
19
- var disable;
20
- var checkboxEls;
21
- data.posts.forEach(function (post) {
22
- disable = !post.display_edit_tools;
23
- checkboxEls = $('.posts li[data-pid="' + post.pid + '"] .content div.plugin-markdown input[type="checkbox"]');
24
-
25
- checkboxEls.on('click', function (e) {
26
- if (disable) {
27
- // Find the post's checkboxes in DOM and make them readonly
28
- e.preventDefault();
29
- }
30
-
31
- // Otherwise, edit the post to reflect state change
32
- var _this = this;
33
- var pid = $(this).parents('li[data-pid]').attr('data-pid');
34
- var index = $(this).parents('.content').find('input[type="checkbox"]').toArray()
35
- .reduce(function (memo, cur, index) {
36
- if (cur === _this) {
37
- memo = index;
38
- }
39
-
40
- return memo;
41
- }, null);
42
-
43
- socket.emit('plugins.markdown.checkbox.edit', {
44
- pid: pid,
45
- index: index,
46
- state: $(_this).prop('checked'),
47
- });
48
- });
49
- });
50
- };
51
-
52
- Markdown.capturePaste = function (targetEl) {
53
- targetEl.on('paste', function (e) {
54
- var triggers = [/^>\s*/, /^\s*\*\s+/, /^\s*\d+\.\s+/, /^\s{4,}/];
55
- var start = e.target.selectionStart;
56
- var line = getLine(targetEl.val(), start);
57
-
58
- var trigger = triggers.reduce(function (regexp, cur) {
59
- if (regexp) {
60
- return regexp;
61
- }
62
-
63
- return cur.test(line) ? cur : false;
64
- }, false);
65
-
66
- var prefix = line.match(trigger);
67
- if (prefix) {
68
- prefix = prefix.shift();
69
-
70
- var payload = e.originalEvent.clipboardData.getData('text');
71
- var fixed = payload.replace(/^/gm, prefix).slice(prefix.length);
72
-
73
- setTimeout(function () {
74
- var replacement = targetEl.val().slice(0, start) + fixed + targetEl.val().slice(start + payload.length);
75
- targetEl.val(replacement);
76
- }, 0);
77
- }
78
- });
79
-
80
- function getLine(text, selectionStart) {
81
- // Break apart into lines, return the line the cursor is in
82
- var lines = text.split('\n');
83
-
84
- return lines.reduce(function (memo, cur) {
85
- if (typeof memo !== 'number') {
86
- return memo;
87
- } if (selectionStart > (memo + cur.length)) {
88
- return memo + cur.length + 1;
89
- }
90
-
91
- return cur;
92
- }, 0);
93
- }
94
- };
95
-
96
- Markdown.highlight = function (data) {
97
- if (data instanceof jQuery.Event) {
98
- highlight($(data.data.selector));
99
- } else {
100
- highlight(data);
101
- }
102
- };
103
-
104
- Markdown.prepareFormattingTools = function () {
105
- require([
106
- 'composer/formatting',
107
- 'composer/controls',
108
- 'translator',
109
- ], function (formatting, controls, translator) {
110
- if (formatting && controls) {
111
- translator.getTranslations(window.config.userLang || window.config.defaultLang, 'markdown', function (strings) {
112
- formatting.addButtonDispatch('bold', function (textarea, selectionStart, selectionEnd) {
113
- if (selectionStart === selectionEnd) {
114
- var block = controls.getBlockData(textarea, '**', selectionStart);
115
-
116
- if (block.in && block.atEnd) {
117
- // At end of bolded string, move cursor past delimiters
118
- controls.updateTextareaSelection(textarea, selectionStart + 2, selectionStart + 2);
119
- } else {
120
- controls.insertIntoTextarea(textarea, '**' + strings.bold + '**');
121
- controls.updateTextareaSelection(
122
- textarea, selectionStart + 2, selectionStart + strings.bold.length + 2
123
- );
124
- }
125
- } else {
126
- var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '**');
127
- controls.updateTextareaSelection(
128
- textarea, selectionStart + 2 + wrapDelta[0], selectionEnd + 2 - wrapDelta[1]
129
- );
130
- }
131
- });
132
-
133
- formatting.addButtonDispatch('italic', function (textarea, selectionStart, selectionEnd) {
134
- if (selectionStart === selectionEnd) {
135
- var block = controls.getBlockData(textarea, '*', selectionStart);
136
-
137
- if (block.in && block.atEnd) {
138
- // At end of italicised string, move cursor past delimiters
139
- controls.updateTextareaSelection(textarea, selectionStart + 1, selectionStart + 1);
140
- } else {
141
- controls.insertIntoTextarea(textarea, '*' + strings.italic + '*');
142
- controls.updateTextareaSelection(
143
- textarea, selectionStart + 1, selectionStart + strings.italic.length + 1
144
- );
145
- }
146
- } else {
147
- var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '*');
148
- controls.updateTextareaSelection(
149
- textarea, selectionStart + 1 + wrapDelta[0], selectionEnd + 1 - wrapDelta[1]
150
- );
151
- }
152
- });
153
-
154
- formatting.addButtonDispatch('list', function (textarea, selectionStart, selectionEnd) {
155
- if (selectionStart === selectionEnd) {
156
- controls.insertIntoTextarea(textarea, '\n* ' + strings.list_item);
157
-
158
- // Highlight "list item"
159
- controls.updateTextareaSelection(
160
- textarea, selectionStart + 3, selectionStart + strings.list_item.length + 3
161
- );
162
- } else {
163
- const selectedText = $(textarea).val().substring(selectionStart, selectionEnd);
164
- const newText = '* ' + selectedText.split('\n').join('\n* ');
165
- controls.replaceSelectionInTextareaWith(textarea, newText);
166
- controls.updateTextareaSelection(textarea, selectionStart, selectionEnd + (newText.length - selectedText.length))
167
- }
168
- });
169
-
170
- formatting.addButtonDispatch('strikethrough', function (textarea, selectionStart, selectionEnd) {
171
- if (selectionStart === selectionEnd) {
172
- var block = controls.getBlockData(textarea, '~~', selectionStart);
173
-
174
- if (block.in && block.atEnd) {
175
- // At end of bolded string, move cursor past delimiters
176
- controls.updateTextareaSelection(textarea, selectionStart + 2, selectionStart + 2);
177
- } else {
178
- controls.insertIntoTextarea(textarea, '~~' + strings.strikethrough_text + '~~');
179
- controls.updateTextareaSelection(
180
- textarea, selectionStart + 2, selectionEnd + strings.strikethrough_text.length + 2
181
- );
182
- }
183
- } else {
184
- var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '~~', '~~');
185
- controls.updateTextareaSelection(
186
- textarea, selectionStart + 2 + wrapDelta[0], selectionEnd + 2 - wrapDelta[1]
187
- );
188
- }
189
- });
190
-
191
- formatting.addButtonDispatch('code', function (textarea, selectionStart, selectionEnd) {
192
- if (selectionStart === selectionEnd) {
193
- controls.insertIntoTextarea(textarea, '```\n' + strings.code_text + '\n```');
194
- controls.updateTextareaSelection(
195
- textarea, selectionStart + 4, selectionEnd + strings.code_text.length + 4
196
- );
197
- } else {
198
- var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '```\n', '\n```');
199
- controls.updateTextareaSelection(
200
- textarea, selectionStart + 4 + wrapDelta[0], selectionEnd + 4 - wrapDelta[1]
201
- );
202
- }
203
- });
204
-
205
- formatting.addButtonDispatch('link', function (textarea, selectionStart, selectionEnd) {
206
- if (selectionStart === selectionEnd) {
207
- controls.insertIntoTextarea(textarea, '[' + strings.link_text + '](' + strings.link_url + ')');
208
- controls.updateTextareaSelection(
209
- textarea,
210
- selectionStart + strings.link_text.length + 3,
211
- selectionEnd + strings.link_text.length + strings.link_url.length + 3
212
- );
213
- } else {
214
- var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '[', '](' + strings.link_url + ')');
215
- controls.updateTextareaSelection(
216
- textarea, selectionEnd + 3 - wrapDelta[1], selectionEnd + strings.link_url.length + 3 - wrapDelta[1]
217
- );
218
- }
219
- });
220
-
221
- formatting.addButtonDispatch('picture-o', function (textarea, selectionStart, selectionEnd) {
222
- if (selectionStart === selectionEnd) {
223
- controls.insertIntoTextarea(textarea, '![' + strings.picture_text + '](' + strings.picture_url + ')');
224
- controls.updateTextareaSelection(
225
- textarea,
226
- selectionStart + strings.picture_text.length + 4,
227
- selectionEnd + strings.picture_text.length + strings.picture_url.length + 4
228
- );
229
- } else {
230
- var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '![', '](' + strings.picture_url + ')');
231
- controls.updateTextareaSelection(
232
- textarea, selectionEnd + 4 - wrapDelta[1], selectionEnd + strings.picture_url.length + 4 - wrapDelta[1]
233
- );
234
- }
235
- });
236
- });
237
- }
4
+ require(['markdown', 'components'], (markdown, components) => {
5
+ $(window).on('action:composer.enhanced', function (evt, data) {
6
+ var textareaEl = data.postContainer.find('textarea');
7
+ markdown.capturePaste(textareaEl);
8
+ markdown.prepareFormattingTools();
238
9
  });
239
- };
240
-
241
- async function highlight(elements) {
242
- if (parseInt(config.markdown.highlight, 10)) {
243
- console.debug('[plugin/markdown] Initializing highlight.js');
244
- let hljs;
245
- let list;
246
- let aliasMap = new Map();
247
- switch(true) {
248
- case config.markdown.hljsLanguages.includes('common'): {
249
- ({ default: hljs} = await import(`highlight.js/lib/common`));
250
- list = 'common';
251
- break;
252
- }
253
-
254
- case config.markdown.hljsLanguages.includes('all'): {
255
- ({ default: hljs} = await import(`highlight.js`));
256
- list = 'all';
257
- break;
258
- }
259
-
260
- default: {
261
- ({ default: hljs} = await import(`highlight.js/lib/core`));
262
- list = 'core';
263
- }
264
- }
265
- console.debug(`[plugins/markdown] Loaded ${list} hljs library`);
266
-
267
- if (list !== 'all') {
268
- await Promise.all(config.markdown.hljsLanguages.map(async (language) => {
269
- if (['common', 'all'].includes(language)) {
270
- return;
271
- }
272
-
273
- console.debug(`[plugins/markdown] Loading ${language} support`);
274
- const { default: lang } = await import('../../node_modules/highlight.js/lib/languages/' + language + '.js');
275
- hljs.registerLanguage(language, lang);
276
- }));
277
- }
278
-
279
- // Build alias set
280
- hljs.listLanguages().forEach((language) => {
281
- const { aliases } = hljs.getLanguage(language);
282
- if (aliases && Array.isArray(aliases)) {
283
- aliases.forEach((alias) => {
284
- aliasMap.set(alias, language);
285
- });
286
- }
287
-
288
- aliasMap.set(language, language);
289
- });
290
-
291
- console.debug(`[plugins/markdown] Loading support for line numbers`);
292
- window.hljs = hljs;
293
- require('highlightjs-line-numbers.js');
294
-
295
- elements.each(function (i, block) {
296
- const parentNode = $(block.parentNode);
297
- if (parentNode.hasClass('markdown-highlight')) {
298
- return;
299
- }
300
- parentNode.addClass('markdown-highlight');
301
-
302
- // Default language if set in ACP
303
- if (!Array.prototype.some.call(block.classList, (className) => className.startsWith('language-')) && config.markdown.defaultHighlightLanguage) {
304
- block.classList.add(`language-${config.markdown.defaultHighlightLanguage}`);
305
- }
306
-
307
- window.hljs.highlightElement(block);
308
-
309
- // Check detected language against whitelist and add lines if enabled
310
- const classIterator = block.classList.values();
311
- for(className of classIterator) {
312
- if (className.startsWith('language-')) {
313
- const language = className.split('-')[1];
314
- const list = config.markdown.highlightLinesLanguageList;
315
- if (aliasMap.has(language) && list && list.includes(aliasMap.get(language))) {
316
- $(block).attr('data-lines', 1);
317
- window.hljs.lineNumbersBlock(block);
318
- }
319
- break;
320
- }
321
- }
322
- });
323
- }
324
- }
325
-
326
- $(window).on('action:composer.preview', {
327
- selector: '.composer .preview pre code',
328
- }, Markdown.highlight);
329
10
 
330
- $(window).on('action:topic.loaded', Markdown.enhanceCheckbox);
331
- $(window).on('action:posts.loaded', Markdown.enhanceCheckbox);
332
- $(window).on('action:posts.edited', Markdown.enhanceCheckbox);
11
+ $(window).on('action:composer.preview', {
12
+ selector: '.composer .preview pre code',
13
+ }, markdown.highlight);
333
14
 
334
- $(window).on('action:posts.loaded action:topic.loaded action:posts.edited', function () {
335
- require(['components'], function (components) {
336
- Markdown.highlight(components.get('post/content').find('pre code'));
15
+ $(window).on('action:posts.loaded action:topic.loaded action:posts.edited', function (ev, data) {
16
+ markdown.highlight(components.get('post/content').find('pre code'));
17
+ markdown.enhanceCheckbox(ev, data);
18
+ markdown.markExternalLinks();
337
19
  });
338
20
  });
339
21
  }());