nodebb-plugin-markdown 9.0.8 → 10.0.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/.eslintrc CHANGED
@@ -1,128 +1,3 @@
1
1
  {
2
- "extends": "airbnb-base",
3
- "parserOptions": {
4
- "sourceType": "script"
5
- },
6
-
7
- "rules": {
8
- // Customized
9
- "handle-callback-err": [ "error","^(e$|(e|(.*(_e|E)))rr)" ],
10
- "comma-dangle": ["error", {
11
- "arrays": "always-multiline",
12
- "objects": "always-multiline",
13
- "imports": "always-multiline",
14
- "exports": "always-multiline",
15
- "functions": "never"
16
- }],
17
- "no-empty": ["error", { "allowEmptyCatch": true }],
18
- "no-underscore-dangle": "off",
19
- "newline-per-chained-call": "off",
20
- "no-console": "off",
21
- "no-mixed-operators": ["error", { "allowSamePrecedence": true }],
22
- "strict": ["error", "global"],
23
- "consistent-return": "off",
24
- "func-names": "off",
25
- "no-tabs": "off",
26
- "indent": ["error", "tab"],
27
- "no-eq-null": "off",
28
- "camelcase": "off",
29
- "no-new": "off",
30
- "no-shadow": "off",
31
- "no-use-before-define": ["error", "nofunc"],
32
- "no-prototype-builtins": "off",
33
- "new-cap": "off",
34
- "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
35
- "import/no-unresolved": "error",
36
-
37
- // ES6
38
- "prefer-rest-params": "off",
39
- "prefer-spread": "off",
40
- "prefer-arrow-callback": "off",
41
- "prefer-template": "off",
42
- "no-var": "off",
43
- "object-shorthand": "off",
44
- "vars-on-top": "off",
45
-
46
- // TODO
47
- "import/no-extraneous-dependencies": "off",
48
- "import/no-dynamic-require": "off",
49
- "import/newline-after-import": "off",
50
- "no-bitwise": "off",
51
- "global-require": "off",
52
- "max-len": "off",
53
- "no-param-reassign": "off",
54
- "no-restricted-syntax": "off",
55
- "no-script-url": "off",
56
- "default-case": "off",
57
- "linebreak-style": "off",
58
-
59
- // "no-multi-assign": "off",
60
- // "one-var": "off",
61
- // "no-undef": "off",
62
- // "max-nested-callbacks": "off",
63
- // "no-mixed-requires": "off",
64
- // "brace-style": "off",
65
- // "max-statements-per-line": "off",
66
- // "no-unused-vars": "off",
67
- // "no-mixed-spaces-and-tabs": "off",
68
- // "no-useless-concat": "off",
69
- // "require-jsdoc": "off",
70
- // "eqeqeq": "off",
71
- // "no-negated-condition": "off",
72
- // "one-var-declaration-per-line": "off",
73
- // "no-lonely-if": "off",
74
- // "radix": "off",
75
- // "no-else-return": "off",
76
- // "no-useless-escape": "off",
77
- // "block-scoped-var": "off",
78
- // "operator-assignment": "off",
79
- // "yoda": "off",
80
- // "no-loop-func": "off",
81
- // "no-void": "off",
82
- // "valid-jsdoc": "off",
83
- // "no-cond-assign": "off",
84
- // "no-redeclare": "off",
85
- // "no-unreachable": "off",
86
- // "no-nested-ternary": "off",
87
- // "operator-linebreak": "off",
88
- // "guard-for-in": "off",
89
- // "no-unneeded-ternary": "off",
90
- // "no-sequences": "off",
91
- // "no-extend-native": "off",
92
- // "no-shadow-restricted-names": "off",
93
- // "no-extra-boolean-cast": "off",
94
- // "no-path-concat": "off",
95
- // "no-unused-expressions": "off",
96
- // "no-return-assign": "off",
97
- // "no-restricted-modules": "off",
98
- // "object-curly-spacing": "off",
99
- // "indent": "off",
100
- // "padded-blocks": "off",
101
- // "eol-last": "off",
102
- // "lines-around-directive": "off",
103
- // "strict": "off",
104
- // "comma-dangle": "off",
105
- // "no-multi-spaces": "off",
106
- // "quotes": "off",
107
- // "keyword-spacing": "off",
108
- // "no-mixed-operators": "off",
109
- // "comma-spacing": "off",
110
- // "no-trailing-spaces": "off",
111
- // "key-spacing": "off",
112
- // "no-multiple-empty-lines": "off",
113
- // "spaced-comment": "off",
114
- // "space-in-parens": "off",
115
- // "block-spacing": "off",
116
- // "quote-props": "off",
117
- // "space-unary-ops": "off",
118
- // "no-empty": "off",
119
- // "dot-notation": "off",
120
- // "func-call-spacing": "off",
121
- // "array-bracket-spacing": "off",
122
- // "object-property-newline": "off",
123
- // "no-continue": "off",
124
- // "no-extra-semi": "off",
125
- // "no-spaced-func": "off",
126
- // "no-useless-return": "off"
127
- }
2
+ "extends": "nodebb"
128
3
  }
package/index.js CHANGED
@@ -23,10 +23,13 @@ let parser;
23
23
  const Markdown = {
24
24
  config: {},
25
25
  _externalImageCache: undefined,
26
+ _externalImageFailures: new Set(),
26
27
  onLoad: async function (params) {
27
28
  const controllers = require('./lib/controllers');
28
29
  const hostMiddleware = require.main.require('./src/middleware');
29
- const middlewares = [hostMiddleware.maintenanceMode, hostMiddleware.registrationComplete, hostMiddleware.pluginHooks];
30
+ const middlewares = [
31
+ hostMiddleware.maintenanceMode, hostMiddleware.registrationComplete, hostMiddleware.pluginHooks,
32
+ ];
30
33
 
31
34
  params.router.get('/admin/plugins/markdown', params.middleware.admin.buildHeader, controllers.renderAdmin);
32
35
  params.router.get('/api/admin/plugins/markdown', controllers.renderAdmin);
@@ -46,7 +49,7 @@ const Markdown = {
46
49
  config.markdown = {
47
50
  highlight: Markdown.highlight ? 1 : 0,
48
51
  highlightLinesLanguageList: Markdown.config.highlightLinesLanguageList,
49
- theme: highlightTheme || 'default.min.css',
52
+ theme: highlightTheme || 'default.css',
50
53
  defaultHighlightLanguage: defaultHighlightLanguage || '',
51
54
  };
52
55
 
@@ -59,13 +62,10 @@ const Markdown = {
59
62
  hookData.links.push({
60
63
  rel: 'prefetch stylesheet',
61
64
  type: '',
62
- href: `${nconf.get('relative_path')}/assets/plugins/nodebb-plugin-markdown/themes/${highlightTheme || 'default.min.css'}`,
65
+ href: `${nconf.get('relative_path')}/assets/plugins/nodebb-plugin-markdown/styles/${highlightTheme || 'default.css'}`,
63
66
  }, {
64
67
  rel: 'prefetch',
65
68
  href: `${nconf.get('relative_path')}/assets/language/${meta.config.defaultLang || 'en-GB'}/markdown.json?${meta.config['cache-buster']}`,
66
- }, {
67
- rel: 'prefetch',
68
- href: `${nconf.get('relative_path')}/assets/src/modules/highlight.js`,
69
69
  });
70
70
 
71
71
  return hookData;
@@ -80,7 +80,7 @@ const Markdown = {
80
80
  langPrefix: 'language-',
81
81
  highlight: true,
82
82
  highlightLinesLanguageList: [],
83
- highlightTheme: 'default.min.css',
83
+ highlightTheme: 'default.css',
84
84
 
85
85
  probe: true,
86
86
  probeCacheSize: 256,
@@ -97,12 +97,12 @@ const Markdown = {
97
97
  };
98
98
  const notCheckboxes = ['langPrefix', 'highlightTheme', 'highlightLinesLanguageList', 'probeCacheSize'];
99
99
 
100
- meta.settings.get('markdown', function (err, options) {
100
+ meta.settings.get('markdown', (err, options) => {
101
101
  if (err) {
102
102
  winston.warn(`[plugin/markdown] Unable to retrieve settings, assuming defaults: ${err.message}`);
103
103
  }
104
104
 
105
- for (const field in defaults) {
105
+ Object.keys(defaults).forEach((field) => {
106
106
  // If not set in config (nil)
107
107
  if (!options.hasOwnProperty(field)) {
108
108
  _self.config[field] = defaults[field];
@@ -111,7 +111,7 @@ const Markdown = {
111
111
  } else {
112
112
  _self.config[field] = options[field];
113
113
  }
114
- }
114
+ });
115
115
 
116
116
  _self.highlight = _self.config.highlight;
117
117
  delete _self.config.highlight;
@@ -137,7 +137,7 @@ const Markdown = {
137
137
  name: 'markdown.externalImageCache',
138
138
  max: parseInt(_self.config.probeCacheSize, 10) || 256,
139
139
  length: function () { return 1; },
140
- maxAge: 1000 * 60 * 60 * 24, // 1 day
140
+ maxAge: 1000 * 60 * 60 * 24, // 1 day
141
141
  });
142
142
  }
143
143
  });
@@ -145,13 +145,11 @@ const Markdown = {
145
145
 
146
146
  loadThemes: async () => {
147
147
  try {
148
- const files = await fs.promises.readdir(path.resolve(require.main.paths[0], '@highlightjs/cdn-assets/styles'));
148
+ const files = await fs.promises.readdir(path.join(require.resolve('highlight.js'), '../../styles'));
149
149
  const isStylesheet = /\.css$/;
150
- Markdown.themes = files.filter(function (file) {
151
- return isStylesheet.test(file);
152
- });
150
+ Markdown.themes = files.filter(file => isStylesheet.test(file));
153
151
  } catch (err) {
154
- winston.error('[plugin/markdown] Could not load Markdown themes: ' + err.message);
152
+ winston.error(`[plugin/markdown] Could not load Markdown themes: ${err.message}`);
155
153
  Markdown.themes = [];
156
154
  }
157
155
  },
@@ -205,17 +203,24 @@ const Markdown = {
205
203
  // eslint-disable-next-line no-cond-assign
206
204
  while ((current = matcher.exec(data.postData.content)) !== null) {
207
205
  const match = current[1];
208
- if (match && Markdown.isExternalLink(match)) { // for security only parse external images
206
+ if (match && Markdown.isExternalLink(match)) { // for security only parse external images
209
207
  const parsedUrl = url.parse(match);
210
208
  const filename = path.basename(parsedUrl.pathname);
211
209
  const size = Markdown._externalImageCache.get(match);
210
+
211
+ // Short-circuit to ignore previous failures
212
+ const hasFailed = Markdown._externalImageFailures.has(match);
213
+ if (hasFailed) {
214
+ return;
215
+ }
216
+
212
217
  if (size) {
213
218
  env.images.set(filename, size);
214
219
  } else {
215
- try {
216
- // eslint-disable-next-line no-await-in-loop
217
- const size = await probe(match);
218
-
220
+ // Size checked asynchronously, see: https://github.com/tomas/needle/issues/389
221
+ probe(match, {
222
+ follow_max: 2,
223
+ }).then((size) => {
219
224
  let { width, height } = size;
220
225
 
221
226
  // Swap width and height if orientation bit is set
@@ -223,11 +228,11 @@ const Markdown = {
223
228
  [width, height] = [height, width];
224
229
  }
225
230
 
226
- env.images.set(filename, { width, height });
227
231
  Markdown._externalImageCache.set(match, { width, height });
228
- } catch (e) {
229
- // No handling required
230
- }
232
+ }).catch(() => {
233
+ // Likely an issue getting the external image size, ignore in the future
234
+ Markdown._externalImageFailures.add(match);
235
+ });
231
236
  }
232
237
  }
233
238
  }
@@ -245,13 +250,9 @@ const Markdown = {
245
250
  const execute = function (html) {
246
251
  // Replace all italicised mentions back to regular mentions
247
252
  if (italicMention.test(html)) {
248
- html = html.replace(italicMention, function (match, slug) {
249
- return '@_' + slug + '_';
250
- });
253
+ html = html.replace(italicMention, (match, slug) => `@_${slug}_`);
251
254
  } else if (boldMention.test(html)) {
252
- html = html.replace(boldMention, function (match, slug) {
253
- return '@__' + slug + '__';
254
- });
255
+ html = html.replace(boldMention, (match, slug) => `@__${slug}__`);
255
256
  }
256
257
 
257
258
  return html;
@@ -330,12 +331,15 @@ const Markdown = {
330
331
 
331
332
  // Update renderer to add some classes to all images
332
333
  const renderImage = parser.renderer.rules.image || function (tokens, idx, options, env, self) {
334
+ // eslint-disable-next-line prefer-spread,prefer-rest-params
333
335
  return self.renderToken.apply(self, arguments);
334
336
  };
335
337
  const renderLink = parser.renderer.rules.link_open || function (tokens, idx, options, env, self) {
338
+ // eslint-disable-next-line prefer-spread,prefer-rest-params
336
339
  return self.renderToken.apply(self, arguments);
337
340
  };
338
341
  const renderTable = parser.renderer.rules.table_open || function (tokens, idx, options, env, self) {
342
+ // eslint-disable-next-line prefer-spread,prefer-rest-params
339
343
  return self.renderToken.apply(self, arguments);
340
344
  };
341
345
 
@@ -347,7 +351,7 @@ const Markdown = {
347
351
  // Validate the url
348
352
  if (!Markdown.isUrlValid(attributes.get('src'))) { return ''; }
349
353
 
350
- token.attrSet('class', (token.attrGet('class') || '') + ' img-responsive img-markdown');
354
+ token.attrSet('class', `${token.attrGet('class') || ''} img-responsive img-markdown`);
351
355
 
352
356
  // Append sizes to images
353
357
  if (parsedSrc.pathname) {
@@ -428,7 +432,7 @@ const Markdown = {
428
432
  * get updated.
429
433
  */
430
434
  const allowedRoots = [nconf.get('upload_url'), '/uploads'];
431
- const allowed = (pathname) => allowedRoots.some((root) => pathname.toString().startsWith(root) || pathname.toString().startsWith(nconf.get('relative_path') + root));
435
+ const allowed = pathname => allowedRoots.some(root => pathname.toString().startsWith(root) || pathname.toString().startsWith(nconf.get('relative_path') + root));
432
436
 
433
437
  try {
434
438
  const urlObj = url.parse(src, false, true);
@@ -449,9 +453,12 @@ const Markdown = {
449
453
  }
450
454
 
451
455
  if (
452
- urlObj.host === null // Relative paths are always internal links...
453
- || (urlObj.host === baseUrlObj.host && urlObj.protocol === baseUrlObj.protocol // Otherwise need to check that protocol and host match
454
- && (nconf.get('relative_path').length > 0 ? urlObj.pathname.indexOf(nconf.get('relative_path')) === 0 : true)) // Subfolder installs need this additional check
456
+ urlObj.host === null || // Relative paths are always internal links...
457
+ (
458
+ urlObj.host === baseUrlObj.host &&
459
+ urlObj.protocol === baseUrlObj.protocol && // Otherwise need to check that protocol and host match
460
+ (nconf.get('relative_path').length > 0 ? urlObj.pathname.indexOf(nconf.get('relative_path')) === 0 : true) // Subfolder installs need this additional check
461
+ )
455
462
  ) {
456
463
  return false;
457
464
  }
@@ -17,7 +17,7 @@ Controllers.retrieveRaw = function retrieveRaw(req, res, next) {
17
17
  return next();
18
18
  }
19
19
 
20
- posts.getPostField(pid, 'content', function (err, content) {
20
+ posts.getPostField(pid, 'content', (err, content) => {
21
21
  if (err) {
22
22
  return next(err);
23
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-markdown",
3
- "version": "9.0.8",
3
+ "version": "10.0.0",
4
4
  "description": "A Markdown parser for NodeBB",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -19,8 +19,8 @@
19
19
  },
20
20
  "husky": {
21
21
  "hooks": {
22
- "pre-commit": "lint-staged",
23
- "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
22
+ "pre-commit": "npx lint-staged",
23
+ "commit-msg": "npx commitlint -E HUSKY_GIT_PARAMS"
24
24
  }
25
25
  },
26
26
  "lint-staged": {
@@ -29,7 +29,7 @@
29
29
  ]
30
30
  },
31
31
  "dependencies": {
32
- "@highlightjs/cdn-assets": "^11.4.0",
32
+ "highlight.js": "11.4.0",
33
33
  "highlightjs-line-numbers.js": "^2.8.0",
34
34
  "markdown-it": "^12.0.6",
35
35
  "markdown-it-checkbox": "^1.1.0",
@@ -37,15 +37,15 @@
37
37
  "probe-image-size": "^7.2.1"
38
38
  },
39
39
  "nbbpm": {
40
- "compatibility": "^1.17.0"
40
+ "compatibility": "^2.0.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@commitlint/cli": "16.2.1",
44
44
  "@commitlint/config-angular": "16.2.1",
45
- "eslint": "8.9.0",
46
- "eslint-config-airbnb-base": "15.0.0",
47
- "eslint-plugin-import": "2.25.4",
45
+ "eslint": "8.x",
46
+ "eslint-config-nodebb": "0.1.1",
47
+ "eslint-plugin-import": "2.x",
48
48
  "husky": "7.0.4",
49
- "lint-staged": "12.3.4"
49
+ "lint-staged": "12.3.5"
50
50
  }
51
51
  }
package/plugin.json CHANGED
@@ -11,11 +11,9 @@
11
11
  "public/js/client.js"
12
12
  ],
13
13
  "staticDirs": {
14
- "themes": "../@highlightjs/cdn-assets/styles"
14
+ "styles": "node_modules/highlight.js/styles"
15
15
  },
16
16
  "modules": {
17
- "highlight.js": "../@highlightjs/cdn-assets/highlight.min.js",
18
- "highlightjs-line-numbers.js": "./public/js/highlightjs-line-numbers.js",
19
17
  "../admin/plugins/markdown.js": "./public/js/admin.js"
20
18
  },
21
19
  "languages": "public/languages",
@@ -36,6 +34,7 @@
36
34
  { "hook": "filter:sanitize.config", "method": "updateSanitizeConfig" }
37
35
  ],
38
36
  "upgrades": [
39
- "upgrades/reset_md_hljs_theme.js"
37
+ "upgrades/reset_md_hljs_theme.js",
38
+ "upgrades/use_unminified_hljs_theme.js"
40
39
  ]
41
40
  }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "nodebb/public"
3
+ }
@@ -17,7 +17,7 @@ define('admin/plugins/markdown', ['settings', 'alerts'], function (Settings, ale
17
17
  langPrefix: 'language-',
18
18
  highlight: true,
19
19
  highlightLinesLanguageList: [],
20
- highlightTheme: 'default.min.css',
20
+ highlightTheme: 'default.css',
21
21
 
22
22
  probe: true,
23
23
  probeCacheSize: 256,
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- /* global window, jQuery, $, config, socket */
4
-
5
3
  (function () {
6
4
  var Markdown = {};
7
5
 
@@ -33,13 +31,14 @@
33
31
  // Otherwise, edit the post to reflect state change
34
32
  var _this = this;
35
33
  var pid = $(this).parents('li[data-pid]').attr('data-pid');
36
- var index = $(this).parents('.content').find('input[type="checkbox"]').toArray().reduce(function (memo, cur, index) {
37
- if (cur === _this) {
38
- memo = index;
39
- }
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
+ }
40
39
 
41
- return memo;
42
- }, null);
40
+ return memo;
41
+ }, null);
43
42
 
44
43
  socket.emit('plugins.markdown.checkbox.edit', {
45
44
  pid: pid,
@@ -119,11 +118,15 @@
119
118
  controls.updateTextareaSelection(textarea, selectionStart + 2, selectionStart + 2);
120
119
  } else {
121
120
  controls.insertIntoTextarea(textarea, '**' + strings.bold + '**');
122
- controls.updateTextareaSelection(textarea, selectionStart + 2, selectionStart + strings.bold.length + 2);
121
+ controls.updateTextareaSelection(
122
+ textarea, selectionStart + 2, selectionStart + strings.bold.length + 2
123
+ );
123
124
  }
124
125
  } else {
125
126
  var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '**');
126
- controls.updateTextareaSelection(textarea, selectionStart + 2 + wrapDelta[0], selectionEnd + 2 - wrapDelta[1]);
127
+ controls.updateTextareaSelection(
128
+ textarea, selectionStart + 2 + wrapDelta[0], selectionEnd + 2 - wrapDelta[1]
129
+ );
127
130
  }
128
131
  });
129
132
 
@@ -136,11 +139,15 @@
136
139
  controls.updateTextareaSelection(textarea, selectionStart + 1, selectionStart + 1);
137
140
  } else {
138
141
  controls.insertIntoTextarea(textarea, '*' + strings.italic + '*');
139
- controls.updateTextareaSelection(textarea, selectionStart + 1, selectionStart + strings.italic.length + 1);
142
+ controls.updateTextareaSelection(
143
+ textarea, selectionStart + 1, selectionStart + strings.italic.length + 1
144
+ );
140
145
  }
141
146
  } else {
142
147
  var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '*');
143
- controls.updateTextareaSelection(textarea, selectionStart + 1 + wrapDelta[0], selectionEnd + 1 - wrapDelta[1]);
148
+ controls.updateTextareaSelection(
149
+ textarea, selectionStart + 1 + wrapDelta[0], selectionEnd + 1 - wrapDelta[1]
150
+ );
144
151
  }
145
152
  });
146
153
 
@@ -149,10 +156,14 @@
149
156
  controls.insertIntoTextarea(textarea, '\n* ' + strings.list_item);
150
157
 
151
158
  // Highlight "list item"
152
- controls.updateTextareaSelection(textarea, selectionStart + 3, selectionStart + strings.list_item.length + 3);
159
+ controls.updateTextareaSelection(
160
+ textarea, selectionStart + 3, selectionStart + strings.list_item.length + 3
161
+ );
153
162
  } else {
154
163
  var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '\n* ', '');
155
- controls.updateTextareaSelection(textarea, selectionStart + 3 + wrapDelta[0], selectionEnd + 3 - wrapDelta[1]);
164
+ controls.updateTextareaSelection(
165
+ textarea, selectionStart + 3 + wrapDelta[0], selectionEnd + 3 - wrapDelta[1]
166
+ );
156
167
  }
157
168
  });
158
169
 
@@ -165,41 +176,61 @@
165
176
  controls.updateTextareaSelection(textarea, selectionStart + 2, selectionStart + 2);
166
177
  } else {
167
178
  controls.insertIntoTextarea(textarea, '~~' + strings.strikethrough_text + '~~');
168
- controls.updateTextareaSelection(textarea, selectionStart + 2, selectionEnd + strings.strikethrough_text.length + 2);
179
+ controls.updateTextareaSelection(
180
+ textarea, selectionStart + 2, selectionEnd + strings.strikethrough_text.length + 2
181
+ );
169
182
  }
170
183
  } else {
171
184
  var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '~~', '~~');
172
- controls.updateTextareaSelection(textarea, selectionStart + 2 + wrapDelta[0], selectionEnd + 2 - wrapDelta[1]);
185
+ controls.updateTextareaSelection(
186
+ textarea, selectionStart + 2 + wrapDelta[0], selectionEnd + 2 - wrapDelta[1]
187
+ );
173
188
  }
174
189
  });
175
190
 
176
191
  formatting.addButtonDispatch('code', function (textarea, selectionStart, selectionEnd) {
177
192
  if (selectionStart === selectionEnd) {
178
193
  controls.insertIntoTextarea(textarea, '```\n' + strings.code_text + '\n```');
179
- controls.updateTextareaSelection(textarea, selectionStart + 4, selectionEnd + strings.code_text.length + 4);
194
+ controls.updateTextareaSelection(
195
+ textarea, selectionStart + 4, selectionEnd + strings.code_text.length + 4
196
+ );
180
197
  } else {
181
198
  var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '```\n', '\n```');
182
- controls.updateTextareaSelection(textarea, selectionStart + 4 + wrapDelta[0], selectionEnd + 4 - wrapDelta[1]);
199
+ controls.updateTextareaSelection(
200
+ textarea, selectionStart + 4 + wrapDelta[0], selectionEnd + 4 - wrapDelta[1]
201
+ );
183
202
  }
184
203
  });
185
204
 
186
205
  formatting.addButtonDispatch('link', function (textarea, selectionStart, selectionEnd) {
187
206
  if (selectionStart === selectionEnd) {
188
207
  controls.insertIntoTextarea(textarea, '[' + strings.link_text + '](' + strings.link_url + ')');
189
- controls.updateTextareaSelection(textarea, selectionStart + strings.link_text.length + 3, selectionEnd + strings.link_text.length + strings.link_url.length + 3);
208
+ controls.updateTextareaSelection(
209
+ textarea,
210
+ selectionStart + strings.link_text.length + 3,
211
+ selectionEnd + strings.link_text.length + strings.link_url.length + 3
212
+ );
190
213
  } else {
191
214
  var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '[', '](' + strings.link_url + ')');
192
- controls.updateTextareaSelection(textarea, selectionEnd + 3 - wrapDelta[1], selectionEnd + strings.link_url.length + 3 - wrapDelta[1]);
215
+ controls.updateTextareaSelection(
216
+ textarea, selectionEnd + 3 - wrapDelta[1], selectionEnd + strings.link_url.length + 3 - wrapDelta[1]
217
+ );
193
218
  }
194
219
  });
195
220
 
196
221
  formatting.addButtonDispatch('picture-o', function (textarea, selectionStart, selectionEnd) {
197
222
  if (selectionStart === selectionEnd) {
198
223
  controls.insertIntoTextarea(textarea, '![' + strings.picture_text + '](' + strings.picture_url + ')');
199
- controls.updateTextareaSelection(textarea, selectionStart + strings.picture_text.length + 4, selectionEnd + strings.picture_text.length + strings.picture_url.length + 4);
224
+ controls.updateTextareaSelection(
225
+ textarea,
226
+ selectionStart + strings.picture_text.length + 4,
227
+ selectionEnd + strings.picture_text.length + strings.picture_url.length + 4
228
+ );
200
229
  } else {
201
230
  var wrapDelta = controls.wrapSelectionInTextareaWith(textarea, '![', '](' + strings.picture_url + ')');
202
- controls.updateTextareaSelection(textarea, selectionEnd + 4 - wrapDelta[1], selectionEnd + strings.picture_url.length + 4 - wrapDelta[1]);
231
+ controls.updateTextareaSelection(
232
+ textarea, selectionEnd + 4 - wrapDelta[1], selectionEnd + strings.picture_url.length + 4 - wrapDelta[1]
233
+ );
203
234
  }
204
235
  });
205
236
  });
@@ -207,30 +238,32 @@
207
238
  });
208
239
  };
209
240
 
210
- function highlight(elements) {
241
+ async function highlight(elements) {
211
242
  if (parseInt(config.markdown.highlight, 10)) {
212
- require(['highlight', 'highlightjs-line-numbers'], function () {
213
- elements.each(function (i, block) {
214
- $(block.parentNode).addClass('markdown-highlight');
243
+ const { default: hljs } = await import('highlight.js/lib/common');
244
+ window.hljs = hljs;
245
+ require('highlightjs-line-numbers.js');
215
246
 
216
- // Default language if set in ACP
217
- if (!Array.prototype.some.call(block.classList, (className) => className.startsWith('language-')) && config.markdown.defaultHighlightLanguage) {
218
- block.classList.add(`language-${config.markdown.defaultHighlightLanguage}`);
219
- }
247
+ elements.each(function (i, block) {
248
+ $(block.parentNode).addClass('markdown-highlight');
220
249
 
221
- window.hljs.highlightElement(block);
250
+ // Default language if set in ACP
251
+ if (!Array.prototype.some.call(block.classList, (className) => className.startsWith('language-')) && config.markdown.defaultHighlightLanguage) {
252
+ block.classList.add(`language-${config.markdown.defaultHighlightLanguage}`);
253
+ }
222
254
 
223
- // Check detected language against whitelist and add lines if enabled
224
- if (block.className.split(' ').map(function (className) {
225
- if (className.indexOf('language-') === 0) {
226
- className = className.slice(9);
227
- }
228
- return config.markdown.highlightLinesLanguageList.includes(className) || config.markdown.highlightLinesLanguageList.includes(className);
229
- }).some(Boolean)) {
230
- $(block).attr('data-lines', 1);
231
- window.hljs.lineNumbersBlock(block);
255
+ window.hljs.highlightElement(block);
256
+
257
+ // Check detected language against whitelist and add lines if enabled
258
+ if (block.className.split(' ').map(function (className) {
259
+ if (className.indexOf('language-') === 0) {
260
+ className = className.slice(9);
232
261
  }
233
- });
262
+ return config.markdown.highlightLinesLanguageList.includes(className) || config.markdown.highlightLinesLanguageList.includes(className);
263
+ }).some(Boolean)) {
264
+ $(block).attr('data-lines', 1);
265
+ window.hljs.lineNumbersBlock(block);
266
+ }
234
267
  });
235
268
  }
236
269
  }
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const meta = require.main.require('./src/meta');
4
+
5
+ module.exports = {
6
+ name: 'Update Markdown Theme to point to unminified file',
7
+ timestamp: Date.UTC(2022, 1, 17),
8
+ method: async () => {
9
+ const { highlightTheme } = await meta.settings.get('markdown');
10
+ if (highlightTheme) {
11
+ await meta.settings.setOne('markdown', 'highlightTheme', highlightTheme.replace('.min.css', '.css'));
12
+ }
13
+ },
14
+ };
package/.eslintignore DELETED
@@ -1,2 +0,0 @@
1
- public/js/highlight.js
2
- public/js/highlightjs-line-numbers.js
@@ -1,373 +0,0 @@
1
- /**
2
- * STOP! This file was edited from the version found in the repo
3
- * https://github.com/wcoder/highlightjs-line-numbers.js
4
- *
5
- * Changes:
6
- * - Made into a requirejs module
7
- * - Instead of passing window, document in as w and d, they
8
- * are defined at top of module
9
- */
10
- define('highlightjs-line-numbers', ['highlight'], function () {
11
- var w = window;
12
- var d = document;
13
-
14
- var TABLE_NAME = 'hljs-ln',
15
- LINE_NAME = 'hljs-ln-line',
16
- CODE_BLOCK_NAME = 'hljs-ln-code',
17
- NUMBERS_BLOCK_NAME = 'hljs-ln-numbers',
18
- NUMBER_LINE_NAME = 'hljs-ln-n',
19
- DATA_ATTR_NAME = 'data-line-number',
20
- BREAK_LINE_REGEXP = /\r\n|\r|\n/g;
21
-
22
- if (w.hljs) {
23
- w.hljs.initLineNumbersOnLoad = initLineNumbersOnLoad;
24
- w.hljs.lineNumbersBlock = lineNumbersBlock;
25
- w.hljs.lineNumbersValue = lineNumbersValue;
26
-
27
- addStyles();
28
- } else {
29
- w.console.error('highlight.js not detected!');
30
- }
31
-
32
- function isHljsLnCodeDescendant(domElt) {
33
- var curElt = domElt;
34
- while (curElt) {
35
- if (curElt.className && curElt.className.indexOf('hljs-ln-code') !== -1) {
36
- return true;
37
- }
38
- curElt = curElt.parentNode;
39
- }
40
- return false;
41
- }
42
-
43
- function getHljsLnTable(hljsLnDomElt) {
44
- var curElt = hljsLnDomElt;
45
- while (curElt.nodeName !== 'TABLE') {
46
- curElt = curElt.parentNode;
47
- }
48
- return curElt;
49
- }
50
-
51
- // Function to workaround a copy issue with Microsoft Edge.
52
- // Due to hljs-ln wrapping the lines of code inside a <table> element,
53
- // itself wrapped inside a <pre> element, window.getSelection().toString()
54
- // does not contain any line breaks. So we need to get them back using the
55
- // rendered code in the DOM as reference.
56
- function edgeGetSelectedCodeLines(selection) {
57
- // current selected text without line breaks
58
- var selectionText = selection.toString();
59
-
60
- // get the <td> element wrapping the first line of selected code
61
- var tdAnchor = selection.anchorNode;
62
- while (tdAnchor.nodeName !== 'TD') {
63
- tdAnchor = tdAnchor.parentNode;
64
- }
65
-
66
- // get the <td> element wrapping the last line of selected code
67
- var tdFocus = selection.focusNode;
68
- while (tdFocus.nodeName !== 'TD') {
69
- tdFocus = tdFocus.parentNode;
70
- }
71
-
72
- // extract line numbers
73
- var firstLineNumber = parseInt(tdAnchor.dataset.lineNumber);
74
- var lastLineNumber = parseInt(tdFocus.dataset.lineNumber);
75
-
76
- // multi-lines copied case
77
- if (firstLineNumber != lastLineNumber) {
78
-
79
- var firstLineText = tdAnchor.textContent;
80
- var lastLineText = tdFocus.textContent;
81
-
82
- // if the selection was made backward, swap values
83
- if (firstLineNumber > lastLineNumber) {
84
- var tmp = firstLineNumber;
85
- firstLineNumber = lastLineNumber;
86
- lastLineNumber = tmp;
87
- tmp = firstLineText;
88
- firstLineText = lastLineText;
89
- lastLineText = tmp;
90
- }
91
-
92
- // discard not copied characters in first line
93
- while (selectionText.indexOf(firstLineText) !== 0) {
94
- firstLineText = firstLineText.slice(1);
95
- }
96
-
97
- // discard not copied characters in last line
98
- while (selectionText.lastIndexOf(lastLineText) === -1) {
99
- lastLineText = lastLineText.slice(0, -1);
100
- }
101
-
102
- // reconstruct and return the real copied text
103
- var selectedText = firstLineText;
104
- var hljsLnTable = getHljsLnTable(tdAnchor);
105
- for (var i = firstLineNumber + 1 ; i < lastLineNumber ; ++i) {
106
- var codeLineSel = format('.{0}[{1}="{2}"]', [CODE_BLOCK_NAME, DATA_ATTR_NAME, i]);
107
- var codeLineElt = hljsLnTable.querySelector(codeLineSel);
108
- selectedText += '\n' + codeLineElt.textContent;
109
- }
110
- selectedText += '\n' + lastLineText;
111
- return selectedText;
112
- // single copied line case
113
- } else {
114
- return selectionText;
115
- }
116
- }
117
-
118
- // ensure consistent code copy/paste behavior across all browsers
119
- // (see https://github.com/wcoder/highlightjs-line-numbers.js/issues/51)
120
- document.addEventListener('copy', function(e) {
121
- // get current selection
122
- var selection = window.getSelection();
123
- // override behavior when one wants to copy line of codes
124
- if (isHljsLnCodeDescendant(selection.anchorNode)) {
125
- var selectionText;
126
- // workaround an issue with Microsoft Edge as copied line breaks
127
- // are removed otherwise from the selection string
128
- if (window.navigator.userAgent.indexOf('Edge') !== -1) {
129
- selectionText = edgeGetSelectedCodeLines(selection);
130
- } else {
131
- // other browsers can directly use the selection string
132
- selectionText = selection.toString();
133
- }
134
- e.clipboardData.setData('text/plain', selectionText);
135
- e.preventDefault();
136
- }
137
- });
138
-
139
- function addStyles () {
140
- var css = d.createElement('style');
141
- css.type = 'text/css';
142
- css.innerHTML = format(
143
- '.{0}{border-collapse:collapse}' +
144
- '.{0} td{padding:0}' +
145
- '.{1}:before{content:attr({2})}',
146
- [
147
- TABLE_NAME,
148
- NUMBER_LINE_NAME,
149
- DATA_ATTR_NAME
150
- ]);
151
- d.getElementsByTagName('head')[0].appendChild(css);
152
- }
153
-
154
- function initLineNumbersOnLoad (options) {
155
- if (d.readyState === 'interactive' || d.readyState === 'complete') {
156
- documentReady(options);
157
- } else {
158
- w.addEventListener('DOMContentLoaded', function () {
159
- documentReady(options);
160
- });
161
- }
162
- }
163
-
164
- function documentReady (options) {
165
- try {
166
- var blocks = d.querySelectorAll('code.hljs,code.nohighlight');
167
-
168
- for (var i in blocks) {
169
- if (blocks.hasOwnProperty(i)) {
170
- if (!isPluginDisabledForBlock(blocks[i])) {
171
- lineNumbersBlock(blocks[i], options);
172
- }
173
- }
174
- }
175
- } catch (e) {
176
- w.console.error('LineNumbers error: ', e);
177
- }
178
- }
179
-
180
- function isPluginDisabledForBlock(element) {
181
- return element.classList.contains('nohljsln');
182
- }
183
-
184
- function lineNumbersBlock (element, options) {
185
- if (typeof element !== 'object') return;
186
-
187
- async(function () {
188
- element.innerHTML = lineNumbersInternal(element, options);
189
- });
190
- }
191
-
192
- function lineNumbersValue (value, options) {
193
- if (typeof value !== 'string') return;
194
-
195
- var element = document.createElement('code')
196
- element.innerHTML = value
197
-
198
- return lineNumbersInternal(element, options);
199
- }
200
-
201
- function lineNumbersInternal (element, options) {
202
-
203
- var internalOptions = mapOptions(element, options);
204
-
205
- duplicateMultilineNodes(element);
206
-
207
- return addLineNumbersBlockFor(element.innerHTML, internalOptions);
208
- }
209
-
210
- function addLineNumbersBlockFor (inputHtml, options) {
211
- var lines = getLines(inputHtml);
212
-
213
- // if last line contains only carriage return remove it
214
- if (lines[lines.length-1].trim() === '') {
215
- lines.pop();
216
- }
217
-
218
- if (lines.length > 1 || options.singleLine) {
219
- var html = '';
220
-
221
- for (var i = 0, l = lines.length; i < l; i++) {
222
- html += format(
223
- '<tr>' +
224
- '<td class="{0} {1}" {3}="{5}">' +
225
- '<div class="{2}" {3}="{5}"></div>' +
226
- '</td>' +
227
- '<td class="{0} {4}" {3}="{5}">' +
228
- '{6}' +
229
- '</td>' +
230
- '</tr>',
231
- [
232
- LINE_NAME,
233
- NUMBERS_BLOCK_NAME,
234
- NUMBER_LINE_NAME,
235
- DATA_ATTR_NAME,
236
- CODE_BLOCK_NAME,
237
- i + options.startFrom,
238
- lines[i].length > 0 ? lines[i] : ' '
239
- ]);
240
- }
241
-
242
- return format('<table class="{0}">{1}</table>', [ TABLE_NAME, html ]);
243
- }
244
-
245
- return inputHtml;
246
- }
247
-
248
- /**
249
- * @param {HTMLElement} element Code block.
250
- * @param {Object} options External API options.
251
- * @returns {Object} Internal API options.
252
- */
253
- function mapOptions (element, options) {
254
- options = options || {};
255
- return {
256
- singleLine: getSingleLineOption(options),
257
- startFrom: getStartFromOption(element, options)
258
- };
259
- }
260
-
261
- function getSingleLineOption (options) {
262
- var defaultValue = false;
263
- if (!!options.singleLine) {
264
- return options.singleLine;
265
- }
266
- return defaultValue;
267
- }
268
-
269
- function getStartFromOption (element, options) {
270
- var defaultValue = 1;
271
- var startFrom = defaultValue;
272
-
273
- if (isFinite(options.startFrom)) {
274
- startFrom = options.startFrom;
275
- }
276
-
277
- // can be overridden because local option is priority
278
- var value = getAttribute(element, 'data-ln-start-from');
279
- if (value !== null) {
280
- startFrom = toNumber(value, defaultValue);
281
- }
282
-
283
- return startFrom;
284
- }
285
-
286
- /**
287
- * Recursive method for fix multi-line elements implementation in highlight.js
288
- * Doing deep passage on child nodes.
289
- * @param {HTMLElement} element
290
- */
291
- function duplicateMultilineNodes (element) {
292
- var nodes = element.childNodes;
293
- for (var node in nodes) {
294
- if (nodes.hasOwnProperty(node)) {
295
- var child = nodes[node];
296
- if (getLinesCount(child.textContent) > 0) {
297
- if (child.childNodes.length > 0) {
298
- duplicateMultilineNodes(child);
299
- } else {
300
- duplicateMultilineNode(child.parentNode);
301
- }
302
- }
303
- }
304
- }
305
- }
306
-
307
- /**
308
- * Method for fix multi-line elements implementation in highlight.js
309
- * @param {HTMLElement} element
310
- */
311
- function duplicateMultilineNode (element) {
312
- var className = element.className;
313
-
314
- if ( ! /hljs-/.test(className)) return;
315
-
316
- var lines = getLines(element.innerHTML);
317
-
318
- for (var i = 0, result = ''; i < lines.length; i++) {
319
- var lineText = lines[i].length > 0 ? lines[i] : ' ';
320
- result += format('<span class="{0}">{1}</span>\n', [ className, lineText ]);
321
- }
322
-
323
- element.innerHTML = result.trim();
324
- }
325
-
326
- function getLines (text) {
327
- if (text.length === 0) return [];
328
- return text.split(BREAK_LINE_REGEXP);
329
- }
330
-
331
- function getLinesCount (text) {
332
- return (text.trim().match(BREAK_LINE_REGEXP) || []).length;
333
- }
334
-
335
- ///
336
- /// HELPERS
337
- ///
338
-
339
- function async (func) {
340
- w.setTimeout(func, 0);
341
- }
342
-
343
- /**
344
- * {@link https://wcoder.github.io/notes/string-format-for-string-formating-in-javascript}
345
- * @param {string} format
346
- * @param {array} args
347
- */
348
- function format (format, args) {
349
- return format.replace(/\{(\d+)\}/g, function(m, n){
350
- return args[n] !== undefined ? args[n] : m;
351
- });
352
- }
353
-
354
- /**
355
- * @param {HTMLElement} element Code block.
356
- * @param {String} attrName Attribute name.
357
- * @returns {String} Attribute value or empty.
358
- */
359
- function getAttribute (element, attrName) {
360
- return element.hasAttribute(attrName) ? element.getAttribute(attrName) : null;
361
- }
362
-
363
- /**
364
- * @param {String} str Source string.
365
- * @param {Number} fallback Fallback value.
366
- * @returns Parsed number or fallback value.
367
- */
368
- function toNumber (str, fallback) {
369
- if (!str) return fallback;
370
- var number = Number(str);
371
- return isFinite(number) ? number : fallback;
372
- }
373
- });