ya-bbcode 3.1.0 → 4.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.
Files changed (3) hide show
  1. package/package.json +9 -10
  2. package/ya-bbcode.d.ts +38 -37
  3. package/ya-bbcode.js +62 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ya-bbcode",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "description": "Yet another BBCode Parser",
5
5
  "keywords": [
6
6
  "bbc",
@@ -34,23 +34,22 @@
34
34
  "lint-staged": {
35
35
  "*.js": "eslint --cache"
36
36
  },
37
- "dependencies": {},
38
37
  "devDependencies": {
39
- "@commitlint/cli": "^17.4.2",
40
- "@commitlint/config-conventional": "^17.4.2",
41
- "@nodecraft/eslint-config": "^22.0.0",
42
- "ava": "^5.1.1",
38
+ "@commitlint/cli": "^17.6.5",
39
+ "@commitlint/config-conventional": "^17.6.5",
40
+ "@nodecraft/eslint-config": "^25.0.1",
41
+ "ava": "^5.3.0",
43
42
  "coveralls": "^3.1.1",
44
- "eslint": "^8.32.0",
43
+ "eslint": "^8.41.0",
45
44
  "eslint-plugin-json": "^3.1.0",
46
45
  "eslint-plugin-node": "^11.1.0",
47
- "eslint-plugin-unicorn": "^45.0.2",
46
+ "eslint-plugin-unicorn": "^47.0.0",
48
47
  "husky": "^8.0.3",
49
- "lint-staged": "^13.1.0",
48
+ "lint-staged": "^13.2.2",
50
49
  "nyc": "^15.1.0"
51
50
  },
52
51
  "engines": {
53
- "node": ">=14",
52
+ "node": ">=16",
54
53
  "npm": ">=8"
55
54
  }
56
55
  }
package/ya-bbcode.d.ts CHANGED
@@ -1,38 +1,39 @@
1
- export = yabbcode
2
-
3
- declare class yabbcode {
4
- /**
5
- * Create a new bbcode parser
6
- */
7
- constructor(config?: {
8
- newline?: boolean
9
- paragraph?: boolean
10
- cleanUnmatchable?: boolean
11
- })
12
- /**
13
- * Parse bbcode
14
- * @param bbc bbcode to parse
15
- */
16
- parse(bbc: any): string
17
- /**
18
- * Remove all default or registered tags
19
- */
20
- clearTags(): this
21
- /**
22
- * Add custom tags
23
- * @param tag tag name
24
- * @param options options to parse the tag
25
- */
26
- registerTag(tag: string, options: {
27
- type: 'replace'
28
- open: ((attr: string) => string) | string
29
- close: ((attr: string) => string) | string | null
30
- }): this
31
- registerTag(tag: string, options: {
32
- type: 'content',
33
- replace: (attr: string, content: string) => string
34
- }): this
35
- registerTag(tag: string, options: {
36
- type: 'ignore'
37
- }): this
1
+ export = yabbcode
2
+
3
+ declare class yabbcode {
4
+ /**
5
+ * Create a new bbcode parser
6
+ */
7
+ constructor(config?: {
8
+ newline?: boolean
9
+ paragraph?: boolean
10
+ cleanUnmatchable?: boolean
11
+ sanitizeHtml?: boolean
12
+ })
13
+ /**
14
+ * Parse bbcode
15
+ * @param bbc bbcode to parse
16
+ */
17
+ parse(bbc: any): string
18
+ /**
19
+ * Remove all default or registered tags
20
+ */
21
+ clearTags(): this
22
+ /**
23
+ * Add custom tags
24
+ * @param tag tag name
25
+ * @param options options to parse the tag
26
+ */
27
+ registerTag(tag: string, options: {
28
+ type: 'replace'
29
+ open: ((attr: string) => string) | string
30
+ close: ((attr: string) => string) | string | null
31
+ }): this
32
+ registerTag(tag: string, options: {
33
+ type: 'content',
34
+ replace: (attr: string, content: string) => string
35
+ }): this
36
+ registerTag(tag: string, options: {
37
+ type: 'ignore'
38
+ }): this
38
39
  }
package/ya-bbcode.js CHANGED
@@ -90,7 +90,7 @@ class yabbcode {
90
90
  'img': {
91
91
  type: 'content',
92
92
  replace: (attr, content) => {
93
- if(!content){
93
+ if(!content) {
94
94
  return '';
95
95
  }
96
96
  return `<img src="${content}" alt="${attr || ''}"/>`;
@@ -109,32 +109,32 @@ class yabbcode {
109
109
  replace: (tag, module, content) => {
110
110
  let open = module.open;
111
111
  let close = module.close;
112
- if(typeof(open) === 'function'){
112
+ if(typeof(open) === 'function') {
113
113
  open = open(tag.attr);
114
114
  }
115
- if(typeof(close) === 'function'){
115
+ if(typeof(close) === 'function') {
116
116
  close = close(tag.attr);
117
117
  }
118
118
  // do the replace
119
- if(open && !tag.isClosing){
119
+ if(open && !tag.isClosing) {
120
120
  //content = content
121
121
  content = content.replace('[TAG-' + tag.index + ']', open);
122
122
  }
123
- if(close && tag.closing){
123
+ if(close && tag.closing) {
124
124
  content = content.replace('[TAG-' + tag.closing.index + ']', close);
125
125
  }
126
126
  return content;
127
127
  },
128
128
  content: (tag, module, content) => {
129
- if(!tag.closing){ return content; }
130
- const openTag = "[TAG-" + tag.index + "]";
131
- const closeTag = "[TAG-" + tag.closing.index + "]";
129
+ if(!tag.closing) { return content; }
130
+ const openTag = '[TAG-' + tag.index + ']';
131
+ const closeTag = '[TAG-' + tag.closing.index + ']';
132
132
  const start = content.indexOf(openTag);
133
133
  const end = content.indexOf(closeTag);
134
134
  let replace = module.replace;
135
135
 
136
136
  const innerContent = content.slice(start + openTag.length, end);
137
- if(typeof(replace) === 'function'){
137
+ if(typeof(replace) === 'function') {
138
138
  replace = replace(tag.attr, innerContent);
139
139
  }
140
140
 
@@ -144,12 +144,12 @@ class yabbcode {
144
144
  return contentStart + replace + contentEnd;
145
145
  },
146
146
  ignore: (tag, module, content) => {
147
- const openTag = "[TAG-" + tag.index + "]";
147
+ const openTag = '[TAG-' + tag.index + ']';
148
148
  const start = content.indexOf(openTag);
149
- let closeTag = "";
149
+ let closeTag = '';
150
150
  let end = content.length;
151
- if(tag.closing){
152
- closeTag = "[TAG-" + tag.closing.index + "]";
151
+ if(tag.closing) {
152
+ closeTag = '[TAG-' + tag.closing.index + ']';
153
153
  end = content.indexOf(closeTag);
154
154
  }
155
155
  let innerContent = content.slice(start + openTag.length, end);
@@ -159,40 +159,44 @@ class yabbcode {
159
159
  return contentStart + innerContent + contentEnd;
160
160
  },
161
161
  };
162
- constructor(config = {}){
162
+ constructor(config = {}) {
163
163
  this.config = {
164
164
  newline: true,
165
165
  paragraph: false,
166
166
  cleanUnmatchable: true,
167
+ sanitizeHtml: true,
167
168
  };
168
- if(config.newline !== undefined){
169
+ if(config.newline !== undefined) {
169
170
  this.config.newline = config.newline;
170
171
  }
171
- if(config.paragraph !== undefined){
172
+ if(config.paragraph !== undefined) {
172
173
  this.config.paragraph = config.paragraph;
173
174
  }
174
- if(config.cleanUnmatchable !== undefined){
175
+ if(config.cleanUnmatchable !== undefined) {
175
176
  this.config.cleanUnmatchable = config.cleanUnmatchable;
176
177
  }
178
+ if(config.sanitizeHtml !== undefined) {
179
+ this.config.sanitizeHtml = config.sanitizeHtml;
180
+ }
177
181
  }
178
182
 
179
- #ignoreLoop = function(tagsMap, content){
180
- for(const tag of tagsMap){
183
+ #ignoreLoop = function(tagsMap, content) {
184
+ for(const tag of tagsMap) {
181
185
  content = content.replace('[TAG-' + tag.index + ']', tag.raw);
182
- if(tag.closing){
186
+ if(tag.closing) {
183
187
  content = content.replace('[TAG-' + tag.closing.index + ']', tag.closing.raw);
184
188
  }
185
- if(tag.children.length > 0){
189
+ if(tag.children.length > 0) {
186
190
  content = this.#ignoreLoop(tag.children, content);
187
191
  }
188
192
  }
189
193
  return content;
190
194
  };
191
195
 
192
- #contentLoop = function(tagsMap, content){
193
- for(const tag of tagsMap){
196
+ #contentLoop = function(tagsMap, content) {
197
+ for(const tag of tagsMap) {
194
198
  let module = this.tags[tag.module];
195
- if(!module){
199
+ if(!module) {
196
200
  // ignore invalid BBCode
197
201
  module = {
198
202
  type: 'replace',
@@ -200,11 +204,11 @@ class yabbcode {
200
204
  close: tag.closing && tag.closing.raw || '',
201
205
  };
202
206
  }
203
- if(!this.contentModules[module.type]){
204
- throw new Error("Cannot parse content block. Invalid block type [" + module.type + "] provided for tag [" + tag.module + "]");
207
+ if(!this.contentModules[module.type]) {
208
+ throw new Error('Cannot parse content block. Invalid block type [' + module.type + '] provided for tag [' + tag.module + ']');
205
209
  }
206
210
  content = this.contentModules[module.type](tag, module, content);
207
- if(tag.children.length > 0 && module.type !== 'ignore'){
211
+ if(tag.children.length > 0 && module.type !== 'ignore') {
208
212
  content = this.#contentLoop(tag.children, content);
209
213
  }
210
214
  }
@@ -212,15 +216,15 @@ class yabbcode {
212
216
  return content;
213
217
  };
214
218
 
215
- #tagLoop = function(tagsMap, parent){
219
+ #tagLoop = function(tagsMap, parent) {
216
220
  let currentTagIndex = 0;
217
- while(currentTagIndex < tagsMap.length){
221
+ while(currentTagIndex < tagsMap.length) {
218
222
  let found = false;
219
- if(tagsMap[currentTagIndex].matchTag !== null || tagsMap[currentTagIndex].isClosing){
223
+ if(tagsMap[currentTagIndex].matchTag !== null || tagsMap[currentTagIndex].isClosing) {
220
224
  currentTagIndex++;
221
225
  continue; // already handled this tag / not closing
222
226
  }
223
- for(const [i, item] of tagsMap.entries()){
227
+ for(const [i, item] of tagsMap.entries()) {
224
228
  if(
225
229
  found ||
226
230
  tagsMap[currentTagIndex].matchTag !== null ||
@@ -228,7 +232,7 @@ class yabbcode {
228
232
  item.matchTag !== null ||
229
233
  !item.isClosing ||
230
234
  tagsMap[currentTagIndex].module !== item.module
231
- ){
235
+ ) {
232
236
  continue;
233
237
  }
234
238
  tagsMap[i].matchTag = tagsMap[currentTagIndex].index;
@@ -238,7 +242,7 @@ class yabbcode {
238
242
 
239
243
  const childStart = currentTagIndex + 1;
240
244
 
241
- if(found !== false){
245
+ if(found !== false) {
242
246
  tagsMap[currentTagIndex].closing = tagsMap[tagsMap[currentTagIndex].matchTag];
243
247
  }else{
244
248
  tagsMap[currentTagIndex].matchTag = false;
@@ -246,7 +250,7 @@ class yabbcode {
246
250
  }
247
251
 
248
252
  // sweep children
249
- if(childStart < found){
253
+ if(childStart < found) {
250
254
  tagsMap[currentTagIndex].children = tagsMap.slice(childStart, found).map((child) => {
251
255
  child.parent = tagsMap[currentTagIndex].index;
252
256
  return child;
@@ -254,7 +258,7 @@ class yabbcode {
254
258
  }
255
259
 
256
260
  let i = childStart;
257
- while(i < found){
261
+ while(i < found) {
258
262
  tagsMap[i].parent = tagsMap[currentTagIndex].index;
259
263
  i++;
260
264
  }
@@ -267,48 +271,57 @@ class yabbcode {
267
271
  tagsMap = tagsMap.filter(item => !((parent === undefined && item.parent !== null) || (item.parent !== null && item.parent !== parent) || item.isClosing));
268
272
 
269
273
  return tagsMap.map((tag) => {
270
- if(tag.children.length > 0){
274
+ if(tag.children.length > 0) {
271
275
  tag.children = this.#tagLoop(tag.children, tag.index);
272
276
  }
273
277
  return tag;
274
278
  });
275
279
  };
276
280
 
277
- clearTags(){
281
+ clearTags() {
278
282
  this.tags = {};
279
283
  return this;
280
284
  }
281
285
 
282
- registerTag(tag, options){
286
+ registerTag(tag, options) {
283
287
  this.tags[String(tag).toLowerCase()] = options;
284
288
  return this;
285
289
  }
286
290
 
287
- parse(bbcInput){
288
- if(typeof(bbcInput) === 'boolean' || typeof(bbcInput) !== 'string' && Number.isNaN(Number(bbcInput))){ return ''; }
291
+ parse(bbcInput) {
292
+ if(typeof(bbcInput) === 'boolean' || typeof(bbcInput) !== 'string' && Number.isNaN(Number(bbcInput))) { return ''; }
289
293
  // eslint-disable-next-line unicorn/prefer-spread
290
294
  let input = String(bbcInput).slice(0); // cheap string clone
295
+ if(this.config.sanitizeHtml) {
296
+ input = input
297
+ .replaceAll('&', '&amp;')
298
+ .replaceAll('<', '&lt;')
299
+ .replaceAll('>', '&gt;')
300
+ .replaceAll('"', '&quot;')
301
+ .replaceAll('\'', '&#39;');
302
+ }
303
+
291
304
  // reset
292
305
  let tagsMap = [];
293
306
  // split input into tags by index
294
307
  const tags = String(input).match(this.regex.tags);
295
308
 
296
- if(this.config.newline){
297
- if(this.config.paragraph){
298
- input = input.replace(this.regex.newline, "</p><p>");
309
+ if(this.config.newline) {
310
+ if(this.config.paragraph) {
311
+ input = input.replace(this.regex.newline, '</p><p>');
299
312
  }else{
300
- input = input.replace(this.regex.newline, "<br/>");
313
+ input = input.replace(this.regex.newline, '<br/>');
301
314
  }
302
315
  }
303
- if(this.config.paragraph){
316
+ if(this.config.paragraph) {
304
317
  input = '<p>' + input + '</p>';
305
318
  }
306
319
 
307
320
  // handle when no tags are present
308
- if(!tags || tags.length === 0){
321
+ if(!tags || tags.length === 0) {
309
322
  return input;
310
323
  }
311
- for(const [i, tag] of tags.entries()){
324
+ for(const [i, tag] of tags.entries()) {
312
325
  const parts = tag.slice(1, -1).split('=');
313
326
  const item = {
314
327
  index: i,
@@ -321,7 +334,7 @@ class yabbcode {
321
334
  parent: null,
322
335
  matchTag: null,
323
336
  };
324
- if(item.isClosing){
337
+ if(item.isClosing) {
325
338
  item.module = item.module.slice(1);
326
339
  }
327
340
 
@@ -332,7 +345,7 @@ class yabbcode {
332
345
  tagsMap = this.#tagLoop(tagsMap);
333
346
  // put back all non-found matches?
334
347
  input = this.#contentLoop(tagsMap, input);
335
- if(this.config.cleanUnmatchable){
348
+ if(this.config.cleanUnmatchable) {
336
349
  input = input.replace(this.regex.placeholders, '');
337
350
  }
338
351
  return input;