|
|
@@ -1,26 +1,13 @@
|
|
1
|
1
|
// TODO: Linked image not parsed correctly. [](link.html)
|
|
2
|
|
-
|
|
3
|
|
-// Blocks
|
|
4
|
|
-// - Paragraph
|
|
5
|
|
-// - Header 1-6 # ## ### #### ##### ###### or === ---
|
|
6
|
|
-// - Blockquote (nestable) >
|
|
7
|
|
-// - Unordered list (nestable) *_
|
|
8
|
|
-// - Ordered list (nestable) 1._
|
|
9
|
|
-// - Code block ```\ncode\n``` or 4 spaces/tab indent
|
|
10
|
|
-// - Horizontal rule --- - - - * * * etc
|
|
11
|
|
-// - Table -|-
|
|
12
|
|
-// - Definition list term\n: definition\n: alternate definition
|
|
13
|
|
-// - Footnote (bottom) citation[^1]
|
|
14
|
|
-// - Abbreviation (definition) *[ABC]: Abbrev Blah Cat
|
|
15
|
|
-// Inline
|
|
16
|
|
-// - Link [text](https://url)
|
|
17
|
|
-// - Emphasis *emphasized*
|
|
18
|
|
-// - Strong **bold**
|
|
19
|
|
-// - Inline code `code`
|
|
20
|
|
-// - Strikethrough ~strike~
|
|
21
|
|
-// - Image {.cssclass}
|
|
22
|
|
-// - Footnote (inline) [^1]: footnote text
|
|
23
|
|
-// - Abbreviation (inline)
|
|
|
2
|
+// TODO: {.class #cssid lang=fr}
|
|
|
3
|
+// # Header {.class}
|
|
|
4
|
+// Header {.class}
|
|
|
5
|
+// ---
|
|
|
6
|
+// [link](url){.class}
|
|
|
7
|
+// ``` {.class}
|
|
|
8
|
+// FIXME: Nested lists not working right
|
|
|
9
|
+// FIXME: Nested blockquotes require blank line
|
|
|
10
|
+// FIXME: Ordered list should start with first number
|
|
24
|
11
|
|
|
25
|
12
|
class _MDHAlign {
|
|
26
|
13
|
static Left = new _MDHAlign('Left');
|
|
|
@@ -111,16 +98,20 @@ class _MDToken {
|
|
111
|
98
|
|
|
112
|
99
|
|
|
113
|
100
|
class _MDSpan {
|
|
114
|
|
- toHTML(config) {
|
|
|
101
|
+ /**
|
|
|
102
|
+ * @param {_MDState} state
|
|
|
103
|
+ * @returns {String} HTML
|
|
|
104
|
+ */
|
|
|
105
|
+ toHTML(state) {
|
|
115
|
106
|
throw new Error(self.constructor.name + ".toHTML not implemented");
|
|
116
|
107
|
}
|
|
117
|
108
|
|
|
118
|
109
|
/**
|
|
119
|
110
|
* @param {_MDSpan[]} spans
|
|
120
|
|
- * @param {_MDConfig} config
|
|
|
111
|
+ * @param {_MDState} state
|
|
121
|
112
|
*/
|
|
122
|
|
- static toHTML(spans, config) {
|
|
123
|
|
- return spans.map((span) => span.toHTML(config)).join("");
|
|
|
113
|
+ static toHTML(spans, state) {
|
|
|
114
|
+ return spans.map((span) => span.toHTML(state)).join("");
|
|
124
|
115
|
}
|
|
125
|
116
|
}
|
|
126
|
117
|
class _MDMultiSpan extends _MDSpan {
|
|
|
@@ -133,8 +124,8 @@ class _MDMultiSpan extends _MDSpan {
|
|
133
|
124
|
super();
|
|
134
|
125
|
this.content = content;
|
|
135
|
126
|
}
|
|
136
|
|
- toHTML() {
|
|
137
|
|
- return _MDSpan.toHTML(this.content);
|
|
|
127
|
+ toHTML(state) {
|
|
|
128
|
+ return _MDSpan.toHTML(this.content, state);
|
|
138
|
129
|
}
|
|
139
|
130
|
}
|
|
140
|
131
|
class _MDTextSpan extends _MDSpan {
|
|
|
@@ -147,8 +138,17 @@ class _MDTextSpan extends _MDSpan {
|
|
147
|
138
|
super();
|
|
148
|
139
|
this.text = text;
|
|
149
|
140
|
}
|
|
150
|
|
- toHTML(config) {
|
|
151
|
|
- return this.text.replace('<', '<');
|
|
|
141
|
+ toHTML(state) {
|
|
|
142
|
+ let html = this.text.replace('<', '<');
|
|
|
143
|
+ let abbrevs = state.abbreviations;
|
|
|
144
|
+ let regexes = state.abbreviationRegexes;
|
|
|
145
|
+ for (const abbrev in abbrevs) {
|
|
|
146
|
+ let def = abbrevs[abbrev];
|
|
|
147
|
+ let regex = regexes[abbrev];
|
|
|
148
|
+ let escapedDef = def.replace('"', '"');
|
|
|
149
|
+ html = html.replace(regex, `<abbr title="${escapedDef}">$1</abbr>`);
|
|
|
150
|
+ }
|
|
|
151
|
+ return html;
|
|
152
|
152
|
}
|
|
153
|
153
|
}
|
|
154
|
154
|
class _MDHTMLSpan extends _MDSpan {
|
|
|
@@ -161,7 +161,7 @@ class _MDHTMLSpan extends _MDSpan {
|
|
161
|
161
|
super();
|
|
162
|
162
|
this.html = html;
|
|
163
|
163
|
}
|
|
164
|
|
- toHTML(config) {
|
|
|
164
|
+ toHTML(state) {
|
|
165
|
165
|
return this.html;
|
|
166
|
166
|
}
|
|
167
|
167
|
}
|
|
|
@@ -183,14 +183,14 @@ class _MDLinkSpan extends _MDSpan {
|
|
183
|
183
|
this.content = content;
|
|
184
|
184
|
}
|
|
185
|
185
|
|
|
186
|
|
- toHTML(config) {
|
|
|
186
|
+ toHTML(state) {
|
|
187
|
187
|
let escapedLink = this.link.replace('"', '"');
|
|
188
|
188
|
var html = `<a href="${escapedLink}"`;
|
|
189
|
189
|
if (target) {
|
|
190
|
190
|
let escapedTarget = this.target.replace('"', '"');
|
|
191
|
191
|
html += ` target="${escapedTarget}"`;
|
|
192
|
192
|
}
|
|
193
|
|
- html += '>' + this.content.toHTML(config) + '</a>';
|
|
|
193
|
+ html += '>' + this.content.toHTML(state) + '</a>';
|
|
194
|
194
|
return html;
|
|
195
|
195
|
}
|
|
196
|
196
|
}
|
|
|
@@ -201,11 +201,11 @@ class _MDReferencedLinkSpan extends _MDLinkSpan {
|
|
201
|
201
|
super(null, content);
|
|
202
|
202
|
this.id = id;
|
|
203
|
203
|
}
|
|
204
|
|
- toHTML(config) {
|
|
|
204
|
+ toHTML(state) {
|
|
205
|
205
|
if (this.link) {
|
|
206
|
|
- return super.toHTML(config);
|
|
|
206
|
+ return super.toHTML(state);
|
|
207
|
207
|
} else {
|
|
208
|
|
- let contentHTML = this.content.toHTML(config);
|
|
|
208
|
+ let contentHTML = this.content.toHTML(state);
|
|
209
|
209
|
return `[${contentHTML}][${this.id}]`;
|
|
210
|
210
|
}
|
|
211
|
211
|
}
|
|
|
@@ -220,8 +220,8 @@ class _MDEmphasisSpan extends _MDSpan {
|
|
220
|
220
|
super();
|
|
221
|
221
|
this.#content = content;
|
|
222
|
222
|
}
|
|
223
|
|
- toHTML(config) {
|
|
224
|
|
- let contentHTML = this.#content.toHTML(config);
|
|
|
223
|
+ toHTML(state) {
|
|
|
224
|
+ let contentHTML = this.#content.toHTML(state);
|
|
225
|
225
|
return `<em>${contentHTML}</em>`;
|
|
226
|
226
|
}
|
|
227
|
227
|
}
|
|
|
@@ -235,8 +235,8 @@ class _MDStrongSpan extends _MDSpan {
|
|
235
|
235
|
super();
|
|
236
|
236
|
this.#content = content;
|
|
237
|
237
|
}
|
|
238
|
|
- toHTML(config) {
|
|
239
|
|
- let contentHTML = this.#content.toHTML(config);
|
|
|
238
|
+ toHTML(state) {
|
|
|
239
|
+ let contentHTML = this.#content.toHTML(state);
|
|
240
|
240
|
return `<strong>${contentHTML}</strong>`;
|
|
241
|
241
|
}
|
|
242
|
242
|
}
|
|
|
@@ -250,8 +250,8 @@ class _MDStrikethroughSpan extends _MDSpan {
|
|
250
|
250
|
super();
|
|
251
|
251
|
this.#content = content;
|
|
252
|
252
|
}
|
|
253
|
|
- toHTML(config) {
|
|
254
|
|
- let contentHTML = this.#content.toHTML(config);
|
|
|
253
|
+ toHTML(state) {
|
|
|
254
|
+ let contentHTML = this.#content.toHTML(state);
|
|
255
|
255
|
return `<strike>${contentHTML}</strike>`;
|
|
256
|
256
|
}
|
|
257
|
257
|
}
|
|
|
@@ -265,8 +265,8 @@ class _MDCodeSpan extends _MDSpan {
|
|
265
|
265
|
super();
|
|
266
|
266
|
this.#content = content;
|
|
267
|
267
|
}
|
|
268
|
|
- toHTML(config) {
|
|
269
|
|
- let contentHTML = this.#content.toHTML(config);
|
|
|
268
|
+ toHTML(state) {
|
|
|
269
|
+ let contentHTML = this.#content.toHTML(state);
|
|
270
|
270
|
return `<code>${contentHTML}</code>`;
|
|
271
|
271
|
}
|
|
272
|
272
|
}
|
|
|
@@ -275,21 +275,28 @@ class _MDImageSpan extends _MDSpan {
|
|
275
|
275
|
source;
|
|
276
|
276
|
/** @var {String|null} */
|
|
277
|
277
|
alt;
|
|
|
278
|
+ /** @var {String|null} */
|
|
|
279
|
+ title;
|
|
278
|
280
|
/**
|
|
279
|
281
|
* @param {String} source
|
|
280
|
282
|
*/
|
|
281
|
|
- constructor(source, alt) {
|
|
|
283
|
+ constructor(source, alt, title=null) {
|
|
282
|
284
|
super();
|
|
283
|
285
|
this.source = source;
|
|
284
|
286
|
this.alt = alt;
|
|
|
287
|
+ this.title = title;
|
|
285
|
288
|
}
|
|
286
|
|
- toHTML(config) {
|
|
|
289
|
+ toHTML(state) {
|
|
287
|
290
|
let escapedSource = this.source.replace('"', '"');
|
|
288
|
291
|
let html = `<img src="${escapedSource}"`;
|
|
289
|
292
|
if (this.alt) {
|
|
290
|
293
|
let altEscaped = this.alt.replace('"', '"');
|
|
291
|
294
|
html += ` alt="${altEscaped}"`;
|
|
292
|
295
|
}
|
|
|
296
|
+ if (this.title) {
|
|
|
297
|
+ let titleEscaped = this.title.replace('"', '"');
|
|
|
298
|
+ html += ` title="${titleEscaped}"`;
|
|
|
299
|
+ }
|
|
293
|
300
|
html += '>';
|
|
294
|
301
|
return html;
|
|
295
|
302
|
}
|
|
|
@@ -304,9 +311,9 @@ class _MDReferencedImageSpan extends _MDImageSpan {
|
|
304
|
311
|
super(null, alt);
|
|
305
|
312
|
this.id = id;
|
|
306
|
313
|
}
|
|
307
|
|
- toHTML(config) {
|
|
|
314
|
+ toHTML(state) {
|
|
308
|
315
|
if (this.source) {
|
|
309
|
|
- return super.toHTML(config);
|
|
|
316
|
+ return super.toHTML(state);
|
|
310
|
317
|
} else {
|
|
311
|
318
|
let altEscaped = this.alt.replace('"', '"');
|
|
312
|
319
|
let idEscaped = this.id.replace('"', '"');
|
|
|
@@ -317,8 +324,6 @@ class _MDReferencedImageSpan extends _MDImageSpan {
|
|
317
|
324
|
class _MDFootnoteReferenceSpan extends _MDSpan {
|
|
318
|
325
|
/** @var {String} */
|
|
319
|
326
|
symbol;
|
|
320
|
|
- /** @var {Number} */
|
|
321
|
|
- differentiator = 0;
|
|
322
|
327
|
/**
|
|
323
|
328
|
* @param {String} symbol
|
|
324
|
329
|
*/
|
|
|
@@ -326,8 +331,8 @@ class _MDFootnoteReferenceSpan extends _MDSpan {
|
|
326
|
331
|
super();
|
|
327
|
332
|
this.symbol = symbol;
|
|
328
|
333
|
}
|
|
329
|
|
- toHTML(config) {
|
|
330
|
|
- return `<sup id="fnref-${this.symbol}-${this.differentiator}"><a href="#fndef-${this.symbol}">${this.symbol}</a></sup>`;
|
|
|
334
|
+ toHTML(state) {
|
|
|
335
|
+ return `<!--FNREF:{${this.symbol}}-->`
|
|
331
|
336
|
}
|
|
332
|
337
|
}
|
|
333
|
338
|
class _MDAbbreviationSpan extends _MDSpan {
|
|
|
@@ -344,7 +349,7 @@ class _MDAbbreviationSpan extends _MDSpan {
|
|
344
|
349
|
this.abbreviation = abbreviation;
|
|
345
|
350
|
this.definition = definition;
|
|
346
|
351
|
}
|
|
347
|
|
- toHTML(config) {
|
|
|
352
|
+ toHTML(state) {
|
|
348
|
353
|
let definitionEscaped = this.definition.replace('"', '"');
|
|
349
|
354
|
return `<abbr title="${definitionEscaped}">${this.abbreviation}</em>`;
|
|
350
|
355
|
}
|
|
|
@@ -355,16 +360,20 @@ class _MDAbbreviationSpan extends _MDSpan {
|
|
355
|
360
|
|
|
356
|
361
|
|
|
357
|
362
|
class _MDBlock {
|
|
358
|
|
- toHTML(config) {
|
|
|
363
|
+ /**
|
|
|
364
|
+ * @param {_MDState} state
|
|
|
365
|
+ */
|
|
|
366
|
+ toHTML(state) {
|
|
359
|
367
|
throw new Error(self.constructor.name + ".toHTML not implemented");
|
|
360
|
368
|
}
|
|
361
|
369
|
|
|
362
|
370
|
/**
|
|
363
|
371
|
* @param {_MDBlock[]} blocks
|
|
|
372
|
+ * @param {_MDState} state
|
|
364
|
373
|
* @returns {String}
|
|
365
|
374
|
*/
|
|
366
|
|
- static toHTML(blocks, config) {
|
|
367
|
|
- return blocks.map((block) => block.toHTML(config)).join("\n");
|
|
|
375
|
+ static toHTML(blocks, state) {
|
|
|
376
|
+ return blocks.map((block) => block.toHTML(state)).join("\n");
|
|
368
|
377
|
}
|
|
369
|
378
|
}
|
|
370
|
379
|
|
|
|
@@ -378,8 +387,8 @@ class _MDMultiBlock extends _MDBlock {
|
|
378
|
387
|
super();
|
|
379
|
388
|
this.#blocks = blocks;
|
|
380
|
389
|
}
|
|
381
|
|
- toHTML(config) {
|
|
382
|
|
- return _MDBlock.toHTML(this.#blocks, config);
|
|
|
390
|
+ toHTML(state) {
|
|
|
391
|
+ return _MDBlock.toHTML(this.#blocks, state);
|
|
383
|
392
|
}
|
|
384
|
393
|
}
|
|
385
|
394
|
|
|
|
@@ -395,8 +404,8 @@ class _MDParagraphBlock extends _MDBlock {
|
|
395
|
404
|
this.content = content;
|
|
396
|
405
|
}
|
|
397
|
406
|
|
|
398
|
|
- toHTML(config) {
|
|
399
|
|
- let contentHTML = this.content.toHTML(config);
|
|
|
407
|
+ toHTML(state) {
|
|
|
408
|
+ let contentHTML = this.content.toHTML(state);
|
|
400
|
409
|
return `<p>${contentHTML}</p>\n`;
|
|
401
|
410
|
}
|
|
402
|
411
|
}
|
|
|
@@ -417,8 +426,8 @@ class _MDHeaderBlock extends _MDBlock {
|
|
417
|
426
|
this.content = content;
|
|
418
|
427
|
}
|
|
419
|
428
|
|
|
420
|
|
- toHTML(config) {
|
|
421
|
|
- let contentHTML = this.content.toHTML(config);
|
|
|
429
|
+ toHTML(state) {
|
|
|
430
|
+ let contentHTML = this.content.toHTML(state);
|
|
422
|
431
|
return `<h${this.level}>${contentHTML}</h${this.level}>\n`;
|
|
423
|
432
|
}
|
|
424
|
433
|
}
|
|
|
@@ -433,8 +442,8 @@ class _MDBlockquoteBlock extends _MDBlock {
|
|
433
|
442
|
super();
|
|
434
|
443
|
this.content = content;
|
|
435
|
444
|
}
|
|
436
|
|
- toHTML(config) {
|
|
437
|
|
- let contentHTML = _MDBlock.toHTML(this.content, config);
|
|
|
445
|
+ toHTML(state) {
|
|
|
446
|
+ let contentHTML = _MDBlock.toHTML(this.content, state);
|
|
438
|
447
|
return `<blockquote>\n${contentHTML}\n</blockquote>`;
|
|
439
|
448
|
}
|
|
440
|
449
|
}
|
|
|
@@ -449,8 +458,8 @@ class _MDUnorderedListBlock extends _MDBlock {
|
|
449
|
458
|
super();
|
|
450
|
459
|
this.items = items;
|
|
451
|
460
|
}
|
|
452
|
|
- toHTML(config) {
|
|
453
|
|
- let contentHTML = _MDBlock.toHTML(this.items);
|
|
|
461
|
+ toHTML(state) {
|
|
|
462
|
+ let contentHTML = _MDBlock.toHTML(this.items, state);
|
|
454
|
463
|
return `<ul>\n${contentHTML}\n</ul>`;
|
|
455
|
464
|
}
|
|
456
|
465
|
}
|
|
|
@@ -465,8 +474,8 @@ class _MDOrderedListBlock extends _MDBlock {
|
|
465
|
474
|
super();
|
|
466
|
475
|
this.items = items;
|
|
467
|
476
|
}
|
|
468
|
|
- toHTML(config) {
|
|
469
|
|
- let contentHTML = _MDBlock.toHTML(this.items);
|
|
|
477
|
+ toHTML(state) {
|
|
|
478
|
+ let contentHTML = _MDBlock.toHTML(this.items, state);
|
|
470
|
479
|
return `<ol>\n${contentHTML}\n</ol>`;
|
|
471
|
480
|
}
|
|
472
|
481
|
}
|
|
|
@@ -483,8 +492,8 @@ class _MDListItemBlock extends _MDBlock {
|
|
483
|
492
|
this.content = content;
|
|
484
|
493
|
}
|
|
485
|
494
|
|
|
486
|
|
- toHTML(config) {
|
|
487
|
|
- let contentHTML = this.content.toHTML(config);
|
|
|
495
|
+ toHTML(state) {
|
|
|
496
|
+ let contentHTML = this.content.toHTML(state);
|
|
488
|
497
|
return `<li>${contentHTML}</li>`;
|
|
489
|
498
|
}
|
|
490
|
499
|
}
|
|
|
@@ -499,13 +508,13 @@ class _MDCodeBlock extends _MDBlock {
|
|
499
|
508
|
super();
|
|
500
|
509
|
this.#code = code;
|
|
501
|
510
|
}
|
|
502
|
|
- toHTML(config) {
|
|
|
511
|
+ toHTML(state) {
|
|
503
|
512
|
return `<pre><code>${this.#code}</code></pre>`;
|
|
504
|
513
|
}
|
|
505
|
514
|
}
|
|
506
|
515
|
|
|
507
|
516
|
class _MDHorizontalRuleBlock extends _MDBlock {
|
|
508
|
|
- toHTML(config) {
|
|
|
517
|
+ toHTML(state) {
|
|
509
|
518
|
return "<hr>\n";
|
|
510
|
519
|
}
|
|
511
|
520
|
}
|
|
|
@@ -522,16 +531,16 @@ class _MDTableCellBlock extends _MDBlock {
|
|
522
|
531
|
super();
|
|
523
|
532
|
this.#content = content;
|
|
524
|
533
|
}
|
|
525
|
|
- toHTML(config) {
|
|
526
|
|
- let contentHTML = this.#content.toHTML(config);
|
|
|
534
|
+ toHTML(state) {
|
|
|
535
|
+ let contentHTML = this.#content.toHTML(state);
|
|
527
|
536
|
let alignAttribute = _MDHAlign.toHTMLAttribute(this.align);
|
|
528
|
537
|
return `<td${alignAttribute}>${contentHTML}</td>`;
|
|
529
|
538
|
}
|
|
530
|
539
|
}
|
|
531
|
540
|
|
|
532
|
541
|
class _MDTableHeaderCellBlock extends _MDTableCellBlock {
|
|
533
|
|
- toHTML(config) {
|
|
534
|
|
- let html = super.toHTML(config);
|
|
|
542
|
+ toHTML(state) {
|
|
|
543
|
+ let html = super.toHTML(state);
|
|
535
|
544
|
let groups = /^<td(.*)td>$/.exec(html);
|
|
536
|
545
|
return `<th${groups[1]}th>`;
|
|
537
|
546
|
}
|
|
|
@@ -557,8 +566,8 @@ class _MDTableRowBlock extends _MDBlock {
|
|
557
|
566
|
cell.align = align;
|
|
558
|
567
|
}
|
|
559
|
568
|
}
|
|
560
|
|
- toHTML(config) {
|
|
561
|
|
- let cellsHTML = _MDBlock.toHTML(this.#cells, config);
|
|
|
569
|
+ toHTML(state) {
|
|
|
570
|
+ let cellsHTML = _MDBlock.toHTML(this.#cells, state);
|
|
562
|
571
|
return `<tr>\n${cellsHTML}\n</tr>`;
|
|
563
|
572
|
}
|
|
564
|
573
|
}
|
|
|
@@ -577,9 +586,9 @@ class _MDTableBlock extends _MDBlock {
|
|
577
|
586
|
this.#headerRow = headerRow;
|
|
578
|
587
|
this.#bodyRows = bodyRows;
|
|
579
|
588
|
}
|
|
580
|
|
- toHTML(config) {
|
|
581
|
|
- let headerRowHTML = this.#headerRow.toHTML(config);
|
|
582
|
|
- let bodyRowsHTML = _MDBlock.toHTML(this.#bodyRows);
|
|
|
589
|
+ toHTML(state) {
|
|
|
590
|
+ let headerRowHTML = this.#headerRow.toHTML(state);
|
|
|
591
|
+ let bodyRowsHTML = _MDBlock.toHTML(this.#bodyRows, state);
|
|
583
|
592
|
return `<table>\n<thead>\n${headerRowHTML}\n</thead>\n<tbody>\n${bodyRowsHTML}\n</tbody>\n</table>`;
|
|
584
|
593
|
}
|
|
585
|
594
|
}
|
|
|
@@ -594,8 +603,8 @@ class _MDDefinitionListBlock extends _MDBlock {
|
|
594
|
603
|
super();
|
|
595
|
604
|
this.#content = content;
|
|
596
|
605
|
}
|
|
597
|
|
- toHTML(config) {
|
|
598
|
|
- let contentHTML = _MDBlock.toHTML(this.#content);
|
|
|
606
|
+ toHTML(state) {
|
|
|
607
|
+ let contentHTML = _MDBlock.toHTML(this.#content, state);
|
|
599
|
608
|
return `<dl>\n${contentHTML}\n</dl>`;
|
|
600
|
609
|
}
|
|
601
|
610
|
}
|
|
|
@@ -610,8 +619,8 @@ class _MDDefinitionTermBlock extends _MDBlock {
|
|
610
|
619
|
super();
|
|
611
|
620
|
this.#content = content;
|
|
612
|
621
|
}
|
|
613
|
|
- toHTML(config) {
|
|
614
|
|
- let contentHTML = this.#content.toHTML(config);
|
|
|
622
|
+ toHTML(state) {
|
|
|
623
|
+ let contentHTML = this.#content.toHTML(state);
|
|
615
|
624
|
return `<dt>${contentHTML}</dt>`;
|
|
616
|
625
|
}
|
|
617
|
626
|
}
|
|
|
@@ -626,38 +635,12 @@ class _MDDefinitionDefinitionBlock extends _MDBlock {
|
|
626
|
635
|
super();
|
|
627
|
636
|
this.#content = content;
|
|
628
|
637
|
}
|
|
629
|
|
- toHTML(config) {
|
|
630
|
|
- let contentHTML = this.#content.toHTML(config);
|
|
|
638
|
+ toHTML(state) {
|
|
|
639
|
+ let contentHTML = this.#content.toHTML(state);
|
|
631
|
640
|
return `<dd>${contentHTML}</dd>`;
|
|
632
|
641
|
}
|
|
633
|
642
|
}
|
|
634
|
643
|
|
|
635
|
|
-class _MDFootnoteBlock extends _MDBlock {
|
|
636
|
|
- /** @var {String} */
|
|
637
|
|
- #id;
|
|
638
|
|
- /** @var {_MDBlock} */
|
|
639
|
|
- #content;
|
|
640
|
|
- /**
|
|
641
|
|
- * @param {String} id
|
|
642
|
|
- * @param {_MDBlock} content
|
|
643
|
|
- */
|
|
644
|
|
- constructor(id, content) {
|
|
645
|
|
- super();
|
|
646
|
|
- this.#id = id;
|
|
647
|
|
- this.#content = content;
|
|
648
|
|
- }
|
|
649
|
|
- toHTML(config) {
|
|
650
|
|
- // TODO: Forward and back links
|
|
651
|
|
- // TODO: Deferring footnotes to end of document
|
|
652
|
|
- //<ol>
|
|
653
|
|
- //<li id="fn:1" role="doc-endnote">
|
|
654
|
|
- //<p>Footnote <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
|
|
655
|
|
- //</li>
|
|
656
|
|
- //</ol>
|
|
657
|
|
- return '';
|
|
658
|
|
- }
|
|
659
|
|
-}
|
|
660
|
|
-
|
|
661
|
644
|
class _MDInlineBlock extends _MDBlock {
|
|
662
|
645
|
/** @var {_MDSpan[]} */
|
|
663
|
646
|
#content;
|
|
|
@@ -669,8 +652,8 @@ class _MDInlineBlock extends _MDBlock {
|
|
669
|
652
|
this.#content = content;
|
|
670
|
653
|
}
|
|
671
|
654
|
|
|
672
|
|
- toHTML(config) {
|
|
673
|
|
- return _MDSpan.toHTML(this.#content);
|
|
|
655
|
+ toHTML(state) {
|
|
|
656
|
+ return _MDSpan.toHTML(this.#content, state);
|
|
674
|
657
|
}
|
|
675
|
658
|
}
|
|
676
|
659
|
|
|
|
@@ -706,6 +689,9 @@ class _MDState {
|
|
706
|
689
|
#abbreviations = {};
|
|
707
|
690
|
|
|
708
|
691
|
/** @var {Object} */
|
|
|
692
|
+ #abbreviationRegexes = {};
|
|
|
693
|
+
|
|
|
694
|
+ /** @var {Object} */
|
|
709
|
695
|
#footnotes = {};
|
|
710
|
696
|
|
|
711
|
697
|
/** @var {number} */
|
|
|
@@ -715,10 +701,19 @@ class _MDState {
|
|
715
|
701
|
#parent = null;
|
|
716
|
702
|
|
|
717
|
703
|
/** @var {Object} */
|
|
718
|
|
- get abbreviations() { (this.#parent) ? this.#parent.abbreviations : this.#abbreviations; }
|
|
|
704
|
+ get abbreviations() {
|
|
|
705
|
+ return (this.#parent) ? this.#parent.abbreviations : this.#abbreviations;
|
|
|
706
|
+ }
|
|
719
|
707
|
|
|
720
|
708
|
/** @var {Object} */
|
|
721
|
|
- get footnotes() { (this.#parent) ? this.#parent.footnotes : this.#footnotes; }
|
|
|
709
|
+ get abbreviationRegexes() {
|
|
|
710
|
+ return (this.#parent) ? this.#parent.abbreviationRegexes : this.#abbreviationRegexes;
|
|
|
711
|
+ }
|
|
|
712
|
+
|
|
|
713
|
+ /** @var {Object} */
|
|
|
714
|
+ get footnotes() {
|
|
|
715
|
+ return (this.#parent) ? this.#parent.footnotes : this.#footnotes;
|
|
|
716
|
+ }
|
|
722
|
717
|
|
|
723
|
718
|
/**
|
|
724
|
719
|
* @param {String[]} lines
|
|
|
@@ -738,9 +733,11 @@ class _MDState {
|
|
738
|
733
|
defineAbbreviation(abbreviation, definition) {
|
|
739
|
734
|
if (this.#parent) {
|
|
740
|
735
|
this.#parent.defineAbbreviation(abbreviation, definition);
|
|
741
|
|
- } else {
|
|
742
|
|
- this.#abbreviations[abbreviation] = definition;
|
|
|
736
|
+ return;
|
|
743
|
737
|
}
|
|
|
738
|
+ this.#abbreviations[abbreviation] = definition;
|
|
|
739
|
+ let regex = new RegExp("\\b(" + abbreviation + ")\\b", "ig");
|
|
|
740
|
+ this.#abbreviationRegexes[abbreviation] = regex;
|
|
744
|
741
|
}
|
|
745
|
742
|
|
|
746
|
743
|
/**
|
|
|
@@ -819,7 +816,6 @@ class Markdown {
|
|
819
|
816
|
*/
|
|
820
|
817
|
static #readNextBlock(state) {
|
|
821
|
818
|
while (state.hasLines(1) && state.lines[state.p].trim().length == 0) {
|
|
822
|
|
- console.info("Skipping blank line " + state.p);
|
|
823
|
819
|
state.p++;
|
|
824
|
820
|
}
|
|
825
|
821
|
var block;
|
|
|
@@ -832,9 +828,9 @@ class Markdown {
|
|
832
|
828
|
block = this.#readIndentedCodeBlock(state); if (block) return block;
|
|
833
|
829
|
block = this.#readHorizontalRule(state); if (block) return block;
|
|
834
|
830
|
block = this.#readTable(state); if (block) return block;
|
|
835
|
|
- block = this.#readDefinitionList(state); if (block) return block;
|
|
836
|
831
|
block = this.#readFootnoteDef(state); if (block) return block;
|
|
837
|
832
|
block = this.#readAbbreviationDef(state); if (block) return block;
|
|
|
833
|
+ block = this.#readDefinitionList(state); if (block) return block;
|
|
838
|
834
|
block = this.#readParagraph(state); if (block) return block;
|
|
839
|
835
|
return null;
|
|
840
|
836
|
}
|
|
|
@@ -1011,10 +1007,10 @@ class Markdown {
|
|
1011
|
1007
|
// Modified from https://emailregex.com/ to remove capture groups.
|
|
1012
|
1008
|
static #baseEmailRegex = /(?:(?:[^<>()\[\]\\.,;:\s@"]+(?:\.[^<>()\[\]\\.,;:\s@"]+)*)|(?:".+"))@(?:(?:\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(?:(?:[a-z\-0-9]+\.)+[a-z]{2,}))/i;
|
|
1013
|
1009
|
|
|
1014
|
|
- static #footnoteWithTitleRegex = /^\[\^\s*([^\]"]+?)\s+"(.*?)"\s*\]/; // 1=symbol, 2=title
|
|
1015
|
|
- static #footnoteRegex = /^\[\^\s*([^\]]+?)\s*\]/; // 1=symbol
|
|
|
1010
|
+ static #footnoteWithTitleRegex = /^\[\^(\d+?)\s+"(.*?)"\]/; // 1=symbol, 2=title
|
|
|
1011
|
+ static #footnoteRegex = /^\[\^(\d+?)\]/; // 1=symbol
|
|
1016
|
1012
|
static #labelRegex = /^\[(.*?)\]/; // 1=content
|
|
1017
|
|
- static #urlWithTitleRegex = /^\((\S+?)\s+"(.*?)"\)/i;
|
|
|
1013
|
+ static #urlWithTitleRegex = /^\((\S+?)\s+"(.*?)"\)/i; // 1=URL, 2=title
|
|
1018
|
1014
|
static #urlRegex = /^\((\S+?)\)/i; // 1=URL
|
|
1019
|
1015
|
static #emailWithTitleRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s+\"(.*?)\"\\s*\\)", "i"); // 1=email, 2=title
|
|
1020
|
1016
|
static #emailRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s*\\)", "i"); // 1=email
|
|
|
@@ -1098,7 +1094,7 @@ class Markdown {
|
|
1098
|
1094
|
} else if (groups = this.#emailWithTitleRegex.exec(remainder)) {
|
|
1099
|
1095
|
// Email address with title (user@example.com "Foo")
|
|
1100
|
1096
|
endText();
|
|
1101
|
|
- tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1]));
|
|
|
1097
|
+ tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1], groups[2]));
|
|
1102
|
1098
|
p += groups[0].length - 1;
|
|
1103
|
1099
|
} else if (groups = this.#urlRegex.exec(remainder)) {
|
|
1104
|
1100
|
// URL (https://example.com)
|
|
|
@@ -1188,7 +1184,7 @@ class Markdown {
|
|
1188
|
1184
|
])) !== null) {
|
|
1189
|
1185
|
let alt = spans[index + 1];
|
|
1190
|
1186
|
let url = spans[index + 2];
|
|
1191
|
|
- spans.splice(index, 3, new _MDImageSpan(url.content, alt.content));
|
|
|
1187
|
+ spans.splice(index, 3, new _MDImageSpan(url.content, alt.content, url.extra));
|
|
1192
|
1188
|
anyChanges = true;
|
|
1193
|
1189
|
}
|
|
1194
|
1190
|
|
|
|
@@ -1341,8 +1337,6 @@ class Markdown {
|
|
1341
|
1337
|
* @returns {_MDBlock}
|
|
1342
|
1338
|
*/
|
|
1343
|
1339
|
static #readInteriorContent(state, firstLineStartPos, stopRegex, inList=false) {
|
|
1344
|
|
- // FIXME: When reading <li> content need to detect nested list without
|
|
1345
|
|
- // a blank line
|
|
1346
|
1340
|
var p = state.p;
|
|
1347
|
1341
|
var seenBlankLine = false;
|
|
1348
|
1342
|
var needsBlocks = false;
|
|
|
@@ -1408,7 +1402,7 @@ class Markdown {
|
|
1408
|
1402
|
return null;
|
|
1409
|
1403
|
}
|
|
1410
|
1404
|
|
|
1411
|
|
- static #hashHeaderRegex = /^(#{1,6})\s*([^#].*)\s*$/; // 1=hashes, 2=content
|
|
|
1405
|
+ static #hashHeaderRegex = /^(#{1,6})\s*([^#].*?)\s*\#*\s*$/; // 1=hashes, 2=content
|
|
1412
|
1406
|
|
|
1413
|
1407
|
/**
|
|
1414
|
1408
|
* @param {_MDState} state
|
|
|
@@ -1514,6 +1508,7 @@ class Markdown {
|
|
1514
|
1508
|
* @returns {_MDBlock|null}
|
|
1515
|
1509
|
*/
|
|
1516
|
1510
|
static #readFencedCodeBlock(state) {
|
|
|
1511
|
+ if (!state.hasLines(2)) return null;
|
|
1517
|
1512
|
var p = state.p;
|
|
1518
|
1513
|
if (state.lines[p++].trim() != '```') return null;
|
|
1519
|
1514
|
var codeLines = [];
|
|
|
@@ -1644,8 +1639,38 @@ class Markdown {
|
|
1644
|
1639
|
* @returns {_MDBlock|null}
|
|
1645
|
1640
|
*/
|
|
1646
|
1641
|
static #readDefinitionList(state) {
|
|
1647
|
|
- // TODO: Definition list
|
|
1648
|
|
- return null;
|
|
|
1642
|
+ var p = state.p;
|
|
|
1643
|
+ var groups;
|
|
|
1644
|
+ var termCount = 0;
|
|
|
1645
|
+ var definitionCount = 0;
|
|
|
1646
|
+ var defLines = [];
|
|
|
1647
|
+ while (state.hasLines(1, p)) {
|
|
|
1648
|
+ let line = state.lines[p++];
|
|
|
1649
|
+ if (line.trim().length == 0) {
|
|
|
1650
|
+ p--;
|
|
|
1651
|
+ break;
|
|
|
1652
|
+ }
|
|
|
1653
|
+ if (/^\s+/.exec(line)) {
|
|
|
1654
|
+ if (defLines.length == 0) return null;
|
|
|
1655
|
+ defLines[defLines.length - 1] += "\n" + line;
|
|
|
1656
|
+ } else if (/^:\s+/.exec(line)) {
|
|
|
1657
|
+ defLines.push(line);
|
|
|
1658
|
+ definitionCount++;
|
|
|
1659
|
+ } else {
|
|
|
1660
|
+ defLines.push(line);
|
|
|
1661
|
+ termCount++;
|
|
|
1662
|
+ }
|
|
|
1663
|
+ }
|
|
|
1664
|
+ if (termCount == 0 || definitionCount == 0) return null;
|
|
|
1665
|
+ let blocks = defLines.map(function(line) {
|
|
|
1666
|
+ if (groups = /^:\s+(.*)$/.exec(line)) {
|
|
|
1667
|
+ return new _MDDefinitionDefinitionBlock(Markdown.#readInline(state, groups[1]));
|
|
|
1668
|
+ } else {
|
|
|
1669
|
+ return new _MDDefinitionTermBlock(Markdown.#readInline(state, line));
|
|
|
1670
|
+ }
|
|
|
1671
|
+ });
|
|
|
1672
|
+ state.p = p;
|
|
|
1673
|
+ return new _MDDefinitionListBlock(blocks);
|
|
1649
|
1674
|
}
|
|
1650
|
1675
|
|
|
1651
|
1676
|
/**
|
|
|
@@ -1653,8 +1678,25 @@ class Markdown {
|
|
1653
|
1678
|
* @returns {_MDBlock|null}
|
|
1654
|
1679
|
*/
|
|
1655
|
1680
|
static #readFootnoteDef(state) {
|
|
1656
|
|
- // TODO: Footnote definition
|
|
1657
|
|
- return null;
|
|
|
1681
|
+ var p = state.p;
|
|
|
1682
|
+ let groups = /^\s*\[\^\s*([^\]]+)\s*\]:\s+(.*)\s*$/.exec(state.lines[p++]);
|
|
|
1683
|
+ if (groups === null) return null;
|
|
|
1684
|
+ let symbol = groups[1];
|
|
|
1685
|
+ let def = groups[2];
|
|
|
1686
|
+ while (state.hasLines(1, p)) {
|
|
|
1687
|
+ let line = state.lines[p++];
|
|
|
1688
|
+ if (/^\s+/.exec(line)) {
|
|
|
1689
|
+ def += "\n" + line;
|
|
|
1690
|
+ } else {
|
|
|
1691
|
+ p--;
|
|
|
1692
|
+ break;
|
|
|
1693
|
+ }
|
|
|
1694
|
+ }
|
|
|
1695
|
+ state.p = p;
|
|
|
1696
|
+ let content = this.#readInline(state, def);
|
|
|
1697
|
+ state.defineFootnote(symbol, content);
|
|
|
1698
|
+ state.p = p;
|
|
|
1699
|
+ return new _MDMultiBlock([]);
|
|
1658
|
1700
|
}
|
|
1659
|
1701
|
|
|
1660
|
1702
|
/**
|
|
|
@@ -1662,8 +1704,15 @@ class Markdown {
|
|
1662
|
1704
|
* @returns {_MDBlock|null}
|
|
1663
|
1705
|
*/
|
|
1664
|
1706
|
static #readAbbreviationDef(state) {
|
|
1665
|
|
- // TODO: Abbreviation definition
|
|
1666
|
|
- return null;
|
|
|
1707
|
+ var p = state.p;
|
|
|
1708
|
+ let line = state.lines[p++];
|
|
|
1709
|
+ let groups = /^\s*\*\[([^\]]+?)\]:\s+(.*?)\s*$/.exec(line);
|
|
|
1710
|
+ if (groups === null) return null;
|
|
|
1711
|
+ let abbrev = groups[1];
|
|
|
1712
|
+ let def = groups[2];
|
|
|
1713
|
+ state.defineAbbreviation(abbrev, def);
|
|
|
1714
|
+ state.p = p;
|
|
|
1715
|
+ return new _MDMultiBlock([]);
|
|
1667
|
1716
|
}
|
|
1668
|
1717
|
|
|
1669
|
1718
|
/**
|
|
|
@@ -1689,6 +1738,46 @@ class Markdown {
|
|
1689
|
1738
|
}
|
|
1690
|
1739
|
|
|
1691
|
1740
|
/**
|
|
|
1741
|
+ * @param {String} html
|
|
|
1742
|
+ * @param {_MDState} state
|
|
|
1743
|
+ * @returns {String}
|
|
|
1744
|
+ */
|
|
|
1745
|
+ static #postProcessFootnotes(html, state) {
|
|
|
1746
|
+ let footnotes = state.footnotes;
|
|
|
1747
|
+ if (Object.keys(footnotes).length == 0) return html;
|
|
|
1748
|
+ var symbolOrder = [];
|
|
|
1749
|
+ var footnoteOccurrences = {};
|
|
|
1750
|
+ var footnoteIndex = 0;
|
|
|
1751
|
+ html = html.replace(/<!--FNREF:{(\d+)}-->/g, function(match, symbol) {
|
|
|
1752
|
+ footnoteIndex++;
|
|
|
1753
|
+ symbol = symbol.toLowerCase();
|
|
|
1754
|
+ if (!symbolOrder.includes(symbol)) {
|
|
|
1755
|
+ symbolOrder.push(symbol);
|
|
|
1756
|
+ }
|
|
|
1757
|
+ var occurrences = footnoteOccurrences[symbol] || [];
|
|
|
1758
|
+ occurrences.push(footnoteIndex);
|
|
|
1759
|
+ footnoteOccurrences[symbol] = occurrences;
|
|
|
1760
|
+ return `<sup id="footnoteref_${footnoteIndex}"><a href="#footnote_${symbol}">${symbol}</a></sup>`;
|
|
|
1761
|
+ });
|
|
|
1762
|
+ if (footnoteIndex == 0) return html;
|
|
|
1763
|
+ html += '<div class="footnotes"><hr/>';
|
|
|
1764
|
+ html += '<ol>';
|
|
|
1765
|
+ for (const symbol of symbolOrder) {
|
|
|
1766
|
+ let content = state.footnotes[symbol];
|
|
|
1767
|
+ if (!content) continue;
|
|
|
1768
|
+ html += `<li value="${symbol}" id="footnote_${symbol}">${content.toHTML(state)}`;
|
|
|
1769
|
+ for (const ref of footnoteOccurrences[symbol]) {
|
|
|
1770
|
+ html += ` <a href="#footnoteref_${ref}" class="footnote-backref" role="doc-backlink">↩︎</a>`;
|
|
|
1771
|
+ }
|
|
|
1772
|
+ html += `</li>\n`;
|
|
|
1773
|
+ }
|
|
|
1774
|
+ html += '</ol>';
|
|
|
1775
|
+ html += '</div>';
|
|
|
1776
|
+ // <!--FNREF:{symbol}-->
|
|
|
1777
|
+ return html;
|
|
|
1778
|
+ }
|
|
|
1779
|
+
|
|
|
1780
|
+ /**
|
|
1692
|
1781
|
* @param {String} markdown
|
|
1693
|
1782
|
* @returns {String} HTML
|
|
1694
|
1783
|
*/
|
|
|
@@ -1697,7 +1786,8 @@ class Markdown {
|
|
1697
|
1786
|
let lines = markdown.split(/(?:\n|\r|\r\n)/);
|
|
1698
|
1787
|
state.lines = lines;
|
|
1699
|
1788
|
let blocks = this.#readBlocks(state);
|
|
1700
|
|
- let html = _MDBlock.toHTML(blocks);
|
|
|
1789
|
+ let html = _MDBlock.toHTML(blocks, state);
|
|
|
1790
|
+ html = this.#postProcessFootnotes(html, state);
|
|
1701
|
1791
|
return html;
|
|
1702
|
1792
|
}
|
|
1703
|
1793
|
}
|