ya-bbcode 3.1.0 → 5.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/ya-bbcode.js DELETED
@@ -1,342 +0,0 @@
1
- 'use strict';
2
-
3
- class yabbcode {
4
- tags = {
5
- 'url': {
6
- type: 'replace',
7
- open: attr => `<a href="${attr || '#'}">`,
8
- close: '</a>',
9
- },
10
- 'quote': {
11
- type: 'replace',
12
- open: attr => `<blockquote author="${attr || ''}">`,
13
- close: '</blockquote>',
14
- },
15
- 'b': {
16
- type: 'replace',
17
- open: '<strong>',
18
- close: '</strong>',
19
- },
20
- 'u': {
21
- type: 'replace',
22
- open: '<u>',
23
- close: '</u>',
24
- },
25
- 'i': {
26
- type: 'replace',
27
- open: '<i>',
28
- close: '</i>',
29
- },
30
- 'h1': {
31
- type: 'replace',
32
- open: '<h1>',
33
- close: '</h1>',
34
- },
35
- 'h2': {
36
- type: 'replace',
37
- open: '<h2>',
38
- close: '</h2>',
39
- },
40
- 'h3': {
41
- type: 'replace',
42
- open: '<h3>',
43
- close: '</h3>',
44
- },
45
- 'h4': {
46
- type: 'replace',
47
- open: '<h4>',
48
- close: '</h4>',
49
- },
50
- 'h5': {
51
- type: 'replace',
52
- open: '<h5>',
53
- close: '</h5>',
54
- },
55
- 'h6': {
56
- type: 'replace',
57
- open: '<h6>',
58
- close: '</h6>',
59
- },
60
- 'code': {
61
- type: 'replace',
62
- open: '<code>',
63
- close: '</code>',
64
- },
65
- 'strike': {
66
- type: 'replace',
67
- open: '<span class="yabbcode-strike">',
68
- close: '</span>',
69
- },
70
- 'spoiler': {
71
- type: 'replace',
72
- open: '<span class="yabbcode-spoiler">',
73
- close: '</span>',
74
- },
75
- 'list': {
76
- type: 'replace',
77
- open: '<ul>',
78
- close: '</ul>',
79
- },
80
- 'olist': {
81
- type: 'replace',
82
- open: '<ol>',
83
- close: '</ol>',
84
- },
85
- '*': {
86
- type: 'replace',
87
- open: '<li>',
88
- close: null,
89
- },
90
- 'img': {
91
- type: 'content',
92
- replace: (attr, content) => {
93
- if(!content){
94
- return '';
95
- }
96
- return `<img src="${content}" alt="${attr || ''}"/>`;
97
- },
98
- },
99
- 'noparse': {
100
- type: 'ignore',
101
- },
102
- };
103
- regex = {
104
- tags: /(\[[^\]^]+])/g,
105
- newline: /\r\n|\r|\n/g,
106
- placeholders: /\[TAG-[1-9]+]/g,
107
- };
108
- contentModules = {
109
- replace: (tag, module, content) => {
110
- let open = module.open;
111
- let close = module.close;
112
- if(typeof(open) === 'function'){
113
- open = open(tag.attr);
114
- }
115
- if(typeof(close) === 'function'){
116
- close = close(tag.attr);
117
- }
118
- // do the replace
119
- if(open && !tag.isClosing){
120
- //content = content
121
- content = content.replace('[TAG-' + tag.index + ']', open);
122
- }
123
- if(close && tag.closing){
124
- content = content.replace('[TAG-' + tag.closing.index + ']', close);
125
- }
126
- return content;
127
- },
128
- content: (tag, module, content) => {
129
- if(!tag.closing){ return content; }
130
- const openTag = "[TAG-" + tag.index + "]";
131
- const closeTag = "[TAG-" + tag.closing.index + "]";
132
- const start = content.indexOf(openTag);
133
- const end = content.indexOf(closeTag);
134
- let replace = module.replace;
135
-
136
- const innerContent = content.slice(start + openTag.length, end);
137
- if(typeof(replace) === 'function'){
138
- replace = replace(tag.attr, innerContent);
139
- }
140
-
141
- const contentStart = content.slice(0, Math.max(0, start));
142
- const contentEnd = content.slice(end + closeTag.length);
143
-
144
- return contentStart + replace + contentEnd;
145
- },
146
- ignore: (tag, module, content) => {
147
- const openTag = "[TAG-" + tag.index + "]";
148
- const start = content.indexOf(openTag);
149
- let closeTag = "";
150
- let end = content.length;
151
- if(tag.closing){
152
- closeTag = "[TAG-" + tag.closing.index + "]";
153
- end = content.indexOf(closeTag);
154
- }
155
- let innerContent = content.slice(start + openTag.length, end);
156
- innerContent = this.#ignoreLoop(tag.children, innerContent);
157
- const contentStart = content.slice(0, Math.max(0, start));
158
- const contentEnd = content.slice(end + closeTag.length);
159
- return contentStart + innerContent + contentEnd;
160
- },
161
- };
162
- constructor(config = {}){
163
- this.config = {
164
- newline: true,
165
- paragraph: false,
166
- cleanUnmatchable: true,
167
- };
168
- if(config.newline !== undefined){
169
- this.config.newline = config.newline;
170
- }
171
- if(config.paragraph !== undefined){
172
- this.config.paragraph = config.paragraph;
173
- }
174
- if(config.cleanUnmatchable !== undefined){
175
- this.config.cleanUnmatchable = config.cleanUnmatchable;
176
- }
177
- }
178
-
179
- #ignoreLoop = function(tagsMap, content){
180
- for(const tag of tagsMap){
181
- content = content.replace('[TAG-' + tag.index + ']', tag.raw);
182
- if(tag.closing){
183
- content = content.replace('[TAG-' + tag.closing.index + ']', tag.closing.raw);
184
- }
185
- if(tag.children.length > 0){
186
- content = this.#ignoreLoop(tag.children, content);
187
- }
188
- }
189
- return content;
190
- };
191
-
192
- #contentLoop = function(tagsMap, content){
193
- for(const tag of tagsMap){
194
- let module = this.tags[tag.module];
195
- if(!module){
196
- // ignore invalid BBCode
197
- module = {
198
- type: 'replace',
199
- open: tag.raw,
200
- close: tag.closing && tag.closing.raw || '',
201
- };
202
- }
203
- if(!this.contentModules[module.type]){
204
- throw new Error("Cannot parse content block. Invalid block type [" + module.type + "] provided for tag [" + tag.module + "]");
205
- }
206
- content = this.contentModules[module.type](tag, module, content);
207
- if(tag.children.length > 0 && module.type !== 'ignore'){
208
- content = this.#contentLoop(tag.children, content);
209
- }
210
- }
211
-
212
- return content;
213
- };
214
-
215
- #tagLoop = function(tagsMap, parent){
216
- let currentTagIndex = 0;
217
- while(currentTagIndex < tagsMap.length){
218
- let found = false;
219
- if(tagsMap[currentTagIndex].matchTag !== null || tagsMap[currentTagIndex].isClosing){
220
- currentTagIndex++;
221
- continue; // already handled this tag / not closing
222
- }
223
- for(const [i, item] of tagsMap.entries()){
224
- if(
225
- found ||
226
- tagsMap[currentTagIndex].matchTag !== null ||
227
- item.index === tagsMap[currentTagIndex].index ||
228
- item.matchTag !== null ||
229
- !item.isClosing ||
230
- tagsMap[currentTagIndex].module !== item.module
231
- ){
232
- continue;
233
- }
234
- tagsMap[i].matchTag = tagsMap[currentTagIndex].index;
235
- tagsMap[currentTagIndex].matchTag = item.index;
236
- found = i; // next index
237
- }
238
-
239
- const childStart = currentTagIndex + 1;
240
-
241
- if(found !== false){
242
- tagsMap[currentTagIndex].closing = tagsMap[tagsMap[currentTagIndex].matchTag];
243
- }else{
244
- tagsMap[currentTagIndex].matchTag = false;
245
- found = tagsMap.length - 1;
246
- }
247
-
248
- // sweep children
249
- if(childStart < found){
250
- tagsMap[currentTagIndex].children = tagsMap.slice(childStart, found).map((child) => {
251
- child.parent = tagsMap[currentTagIndex].index;
252
- return child;
253
- });
254
- }
255
-
256
- let i = childStart;
257
- while(i < found){
258
- tagsMap[i].parent = tagsMap[currentTagIndex].index;
259
- i++;
260
- }
261
-
262
- currentTagIndex++; // move on
263
- }
264
-
265
- // sweep children & matched closing tags
266
- // TODO: make this more readable
267
- tagsMap = tagsMap.filter(item => !((parent === undefined && item.parent !== null) || (item.parent !== null && item.parent !== parent) || item.isClosing));
268
-
269
- return tagsMap.map((tag) => {
270
- if(tag.children.length > 0){
271
- tag.children = this.#tagLoop(tag.children, tag.index);
272
- }
273
- return tag;
274
- });
275
- };
276
-
277
- clearTags(){
278
- this.tags = {};
279
- return this;
280
- }
281
-
282
- registerTag(tag, options){
283
- this.tags[String(tag).toLowerCase()] = options;
284
- return this;
285
- }
286
-
287
- parse(bbcInput){
288
- if(typeof(bbcInput) === 'boolean' || typeof(bbcInput) !== 'string' && Number.isNaN(Number(bbcInput))){ return ''; }
289
- // eslint-disable-next-line unicorn/prefer-spread
290
- let input = String(bbcInput).slice(0); // cheap string clone
291
- // reset
292
- let tagsMap = [];
293
- // split input into tags by index
294
- const tags = String(input).match(this.regex.tags);
295
-
296
- if(this.config.newline){
297
- if(this.config.paragraph){
298
- input = input.replace(this.regex.newline, "</p><p>");
299
- }else{
300
- input = input.replace(this.regex.newline, "<br/>");
301
- }
302
- }
303
- if(this.config.paragraph){
304
- input = '<p>' + input + '</p>';
305
- }
306
-
307
- // handle when no tags are present
308
- if(!tags || tags.length === 0){
309
- return input;
310
- }
311
- for(const [i, tag] of tags.entries()){
312
- const parts = tag.slice(1, -1).split('=');
313
- const item = {
314
- index: i,
315
- module: parts[0].toLowerCase(),
316
- isClosing: tag.slice(1, 2) === '/',
317
- raw: tag,
318
- attr: parts.slice(1).join('='),
319
- closing: null,
320
- children: [],
321
- parent: null,
322
- matchTag: null,
323
- };
324
- if(item.isClosing){
325
- item.module = item.module.slice(1);
326
- }
327
-
328
- tagsMap.push(item);
329
- input = input.replace(tag, '[TAG-' + i + ']'); // placeholder for tag
330
- }
331
- // loop through each tag to create nested elements
332
- tagsMap = this.#tagLoop(tagsMap);
333
- // put back all non-found matches?
334
- input = this.#contentLoop(tagsMap, input);
335
- if(this.config.cleanUnmatchable){
336
- input = input.replace(this.regex.placeholders, '');
337
- }
338
- return input;
339
- }
340
- }
341
-
342
- module.exports = yabbcode;