Просмотр исходного кода

Added tables, all span class defs

main
Rocketsoup 1 год назад
Родитель
Сommit
dd0b817e9f
2 измененных файлов: 460 добавлений и 53 удалений
  1. 431
    38
      js/markdown.js
  2. 29
    15
      markdownjs.html

+ 431
- 38
js/markdown.js Просмотреть файл

@@ -20,6 +20,265 @@
20 20
 // - Footnote (inline)   [^1]: footnote text
21 21
 // - Abbreviation (inline)
22 22
 
23
+class _MDHAlign {
24
+	static Left = new _MDHAlign('Left');
25
+	static Center = new _MDHAlign('Center');
26
+	static Right = new _MDHAlign('Right');
27
+
28
+	constructor(name) {
29
+		this.name = name;
30
+	}
31
+
32
+	toString() {
33
+		return `_MDHAlign.${this.name}`;
34
+	}
35
+
36
+	static toHTMLAttribute(align) {
37
+		switch (align) {
38
+			case _MDHAlign.Left: return ' align="left"';
39
+			case _MDHAlign.Center: return ' align="center"';
40
+			case _MDHAlign.Right: return ' align="right"';
41
+		}
42
+		return '';
43
+	}
44
+}
45
+
46
+class _MDSpan {
47
+	toHTML(config) {
48
+		throw new Error(self.constructor.name + ".toHTML not implemented");
49
+	}
50
+
51
+	static toHTML(spans, config) {
52
+		return spans.map((span) => span.toHTML(config)).join("");
53
+	}
54
+}
55
+class _MDMultiSpan extends _MDSpan {
56
+	/** @var {_MDSpan[]} */
57
+	content;
58
+	/**
59
+	 * @param {_MDSpan[]} content
60
+	 */
61
+	constructor(content) {
62
+		super();
63
+		this.content = content;
64
+	}
65
+	toHTML() {
66
+		return _MDSpan.toHTML(this.content);
67
+	}
68
+}
69
+class _MDTextSpan extends _MDSpan {
70
+	/** @param {String} text */
71
+	text;
72
+	/**
73
+	 * @param {String} text
74
+	 */
75
+	constructor(text) {
76
+		super();
77
+		this.text = text;
78
+	}
79
+	toHTML(config) {
80
+		return this.text.replace('<', '&lt;');
81
+	}
82
+}
83
+class _MDHTMLSpan extends _MDSpan {
84
+	/** @param {String} html */
85
+	html;
86
+	/**
87
+	 * @param {String} html
88
+	 */
89
+	constructor(html) {
90
+		super();
91
+		this.html = html;
92
+	}
93
+	toHTML(config) {
94
+		return this.html;
95
+	}
96
+}
97
+class _MDLink extends _MDSpan {
98
+	/** @var {String} */
99
+	link;
100
+	/** @var {String|null} */
101
+	target = null;
102
+	/** @var {_MDSpan} */
103
+	content;
104
+
105
+	/**
106
+	 * @param {String} link
107
+	 * @param {_MDSpan} content
108
+	 */
109
+	constructor(link, content) {
110
+		super();
111
+		this.link = link;
112
+		this.content = content;
113
+	}
114
+
115
+	toHTML(config) {
116
+		let escapedLink = this.link.replace('"', '&quot;');
117
+		var html = `<a href="${escapedLink}"`;
118
+		if (target) {
119
+			let escapedTarget = this.target.replace('"', '&quot;');
120
+			html += ` target="${escapedTarget}"`;
121
+		}
122
+		html += '>' + this.content.toHTML(config) + '</a>';
123
+		return html;
124
+	}
125
+}
126
+class _MDReferencedLink extends _MDLink {
127
+	/** @var {String} id */
128
+	id;
129
+	constructor(id, content) {
130
+		super(null, content);
131
+		this.id = id;
132
+	}
133
+	toHTML(config) {
134
+		if (this.link) {
135
+			return super.toHTML(config);
136
+		} else {
137
+			let contentHTML = this.content.toHTML(config);
138
+			return `[${contentHTML}][${this.id}]`;
139
+		}
140
+	}
141
+}
142
+class _MDEmphasis extends _MDSpan {
143
+	/** @var {_MDSpan} content */
144
+	#content;
145
+	/**
146
+	 * @param {_MDSpan} content
147
+	 */
148
+	constructor(content) {
149
+		super();
150
+		this.#content = content;
151
+	}
152
+	toHTML(config) {
153
+		let contentHTML = this.#content.toHTML(config);
154
+		return `<em>${contentHTML}</em>`;
155
+	}
156
+}
157
+class _MDStrong extends _MDSpan {
158
+	/** @var {_MDSpan} content */
159
+	#content;
160
+	/**
161
+	 * @param {_MDSpan} content
162
+	 */
163
+	constructor(content) {
164
+		super();
165
+		this.#content = content;
166
+	}
167
+	toHTML(config) {
168
+		let contentHTML = this.#content.toHTML(config);
169
+		return `<strong>${contentHTML}</strong>`;
170
+	}
171
+}
172
+class _MDStrikethrough extends _MDSpan {
173
+	/** @var {_MDSpan} content */
174
+	#content;
175
+	/**
176
+	 * @param {_MDSpan} content
177
+	 */
178
+	constructor(content) {
179
+		super();
180
+		this.#content = content;
181
+	}
182
+	toHTML(config) {
183
+		let contentHTML = this.#content.toHTML(config);
184
+		return `<strike>${contentHTML}</strike>`;
185
+	}
186
+}
187
+class _MDInlineCode extends _MDSpan {
188
+	/** @var {_MDSpan} content */
189
+	#content;
190
+	/**
191
+	 * @param {_MDSpan} content
192
+	 */
193
+	constructor(content) {
194
+		super();
195
+		this.#content = content;
196
+	}
197
+	toHTML(config) {
198
+		let contentHTML = this.#content.toHTML(config);
199
+		return `<code>${contentHTML}</code>`;
200
+	}
201
+}
202
+class _MDImage extends _MDSpan {
203
+	/** @var {String} */
204
+	source;
205
+	/** @var {String|null} */
206
+	alt;
207
+	/**
208
+	 * @param {String} source
209
+	 */
210
+	constructor(source, alt) {
211
+		super();
212
+		this.source = source;
213
+		this.alt = alt;
214
+	}
215
+	toHTML(config) {
216
+		let escapedSource = this.source.replace('"', '&quot;');
217
+		let html = `<img src="${escapedSource}"`;
218
+		if (this.alt) {
219
+			let altEscaped = this.alt.replace('"', '&quot');
220
+			html += ` alt="${altEscaped}"`;
221
+		}
222
+		html += '>';
223
+		return html;
224
+	}
225
+}
226
+class _MDReferencedImage extends _MDImage {
227
+	/** @var {String} */
228
+	id;
229
+	/**
230
+	 * @param {String} id
231
+	 */
232
+	constructor(id, alt) {
233
+		super(null, alt);
234
+		this.id = id;
235
+	}
236
+	toHTML(config) {
237
+		if (this.source) {
238
+			return super.toHTML(config);
239
+		} else {
240
+			let altEscaped = this.alt.replace('"', '&quot;');
241
+			let idEscaped = this.id.replace('"', '&quot;');
242
+			return `![${altEscaped}][${idEscaped}]`;
243
+		}
244
+	}
245
+}
246
+class _MDFootnoteReference extends _MDSpan {
247
+	/** @var {String} */
248
+	symbol;
249
+	/** @var {Number} */
250
+	differentiator = 0;
251
+	/**
252
+	 * @param {String} symbol
253
+	 */
254
+	constructor(symbol) {
255
+		super();
256
+		this.symbol = symbol;
257
+	}
258
+	toHTML(config) {
259
+		return `<sup id="fnref-${this.symbol}-${this.differentiator}"><a href="#fndef-${this.symbol}">${this.symbol}</a></sup>`;
260
+	}
261
+}
262
+class _MDAbbreviationReference extends _MDSpan {
263
+	/** @var {_MDSpan} content */
264
+	#content;
265
+	/** @var {String} definition */
266
+	#definition;
267
+	/**
268
+	 * @param {_MDSpan} content
269
+	 */
270
+	constructor(content, definition) {
271
+		super();
272
+		this.#content = content;
273
+		this.#definition = definition;
274
+	}
275
+	toHTML(config) {
276
+		let contentHTML = this.#content.toHTML(config);
277
+		let definitionEscaped = this.#definition.replace('"', '&quot;');
278
+		return `<abbr title="${definitionEscaped}">${contentHTML}</em>`;
279
+	}
280
+}
281
+
23 282
 class _MDBlock {
24 283
 	toHTML(config) {
25 284
 		throw new Error(self.constructor.name + ".toHTML not implemented");
@@ -176,9 +435,11 @@ class _MDHorizontalRule extends _MDBlock {
176 435
 	}
177 436
 }
178 437
 
179
-class _MDTableHeaderCell extends _MDBlock {
438
+class _MDTableCell extends _MDBlock {
180 439
 	/** @var {_MDBlock} */
181 440
 	#content;
441
+	/** @var {_MDHAlign|null} */
442
+	align = null;
182 443
 	/**
183 444
 	 * @param {_MDBlock} content
184 445
 	 */
@@ -188,23 +449,16 @@ class _MDTableHeaderCell extends _MDBlock {
188 449
 	}
189 450
 	toHTML(config) {
190 451
 		let contentHTML = this.#content.toHTML(config);
191
-		return `<th>${contentHTML}</th>`;
452
+		let alignAttribute = _MDHAlign.toHTMLAttribute(this.align);
453
+		return `<td${alignAttribute}>${contentHTML}</td>`;
192 454
 	}
193 455
 }
194 456
 
195
-class _MDTableCell extends _MDBlock {
196
-	/** @var {_MDBlock} */
197
-	#content;
198
-	/**
199
-	 * @param {_MDBlock} content
200
-	 */
201
-	constructor(content) {
202
-		super();
203
-		this.#content = content;
204
-	}
457
+class _MDTableHeaderCell extends _MDTableCell {
205 458
 	toHTML(config) {
206
-		let contentHTML = this.#content.toHTML(config);
207
-		return `<th>${contentHTML}</th>`;
459
+		let html = super.toHTML(config);
460
+		let groups = /^<td(.*)td>$/.exec(html);
461
+		return `<th${groups[1]}th>`;
208 462
 	}
209 463
 }
210 464
 
@@ -218,8 +472,18 @@ class _MDTableRow extends _MDBlock {
218 472
 		super();
219 473
 		this.#cells = cells;
220 474
 	}
475
+	/**
476
+	 * @param {_MDHAlign[]} alignments
477
+	 */
478
+	applyAlignments(alignments) {
479
+		for (var i = 0; i < this.#cells.length; i++) {
480
+			let cell = this.#cells[i];
481
+			let align = i < alignments.length ? alignments[i] : null;
482
+			cell.align = align;
483
+		}
484
+	}
221 485
 	toHTML(config) {
222
-		cellsHTML = _MDBlock.toHTML(this.#cells, config);
486
+		let cellsHTML = _MDBlock.toHTML(this.#cells, config);
223 487
 		return `<tr>\n${cellsHTML}\n</tr>`;
224 488
 	}
225 489
 }
@@ -229,6 +493,15 @@ class _MDTable extends _MDBlock {
229 493
 	#headerRow;
230 494
 	/** @var {_MDTableRow[]} */
231 495
 	#bodyRows;
496
+	/**
497
+	 * @param {_MDTableRow} headerRow
498
+	 * @param {_MDTableRow[]} bodyRows
499
+	 */
500
+	constructor(headerRow, bodyRows) {
501
+		super();
502
+		this.#headerRow = headerRow;
503
+		this.#bodyRows = bodyRows;
504
+	}
232 505
 	toHTML(config) {
233 506
 		let headerRowHTML = this.#headerRow.toHTML(config);
234 507
 		let bodyRowsHTML = _MDBlock.toHTML(this.#bodyRows);
@@ -284,21 +557,6 @@ class _MDDefinitionDefinition extends _MDBlock {
284 557
 	}
285 558
 }
286 559
 
287
-class _MDFootnoteReference extends _MDBlock {
288
-	/** @var {String} */
289
-	#id;
290
-	/**
291
-	 * @param {String} id
292
-	 */
293
-	constructor(id) {
294
-		super();
295
-		this.#id = id;
296
-	}
297
-	toHTML(config) {
298
-		return `<sup><a href="#footnote${this.#id}">${this.#id}</a></sup>`;
299
-	}
300
-}
301
-
302 560
 class _MDFootnoteContent extends _MDBlock {
303 561
 	/** @var {String} */
304 562
 	#id;
@@ -411,8 +669,9 @@ class _MDState {
411 669
 		this.p = other.p;
412 670
 	}
413 671
 
414
-	hasLines(minCount) {
415
-		return this.p + minCount <= this.lines.length;
672
+	hasLines(minCount, p=-1) {
673
+		let relativeTo = (p < 0) ? this.p : p;
674
+		return relativeTo + minCount <= this.lines.length;
416 675
 	}
417 676
 }
418 677
 
@@ -424,8 +683,31 @@ class Markdown {
424 683
 	/**
425 684
 	 * @param {String} line
426 685
 	 */
427
-	static #stripIndent(line) {
428
-		return line.replace(/^(?: {1,4}|\t)/, '');
686
+	static #stripIndent(line, count=1) {
687
+		let regex = new RegExp(`^(: {1,4}|\\t){${count}}`);
688
+		return line.replace(regex, '');
689
+	}
690
+
691
+	/**
692
+	 * @param {String} line
693
+	 * @param {Boolean} fullIndentsOnly
694
+	 * @returns {Number} indent count
695
+	 */
696
+	static #countIndents(line, fullIndentsOnly=false) {
697
+		var count = 0;
698
+		var lastLine = line;
699
+		while (line.length > 0) {
700
+			line = (fullIndentsOnly)
701
+				? line.replace(/^(?: {4}|\t)/, '')
702
+				: line.replace(/^(?: {1,4}|\t)/, '');
703
+			if (line != lastLine) {
704
+				count++;
705
+			} else {
706
+				break;
707
+			}
708
+			lastLine = line;
709
+		}
710
+		return count;
429 711
 	}
430 712
 
431 713
 	/**
@@ -487,6 +769,8 @@ class Markdown {
487 769
 	 * @returns {_MDBlock}
488 770
 	 */
489 771
 	static #readInteriorContent(state, firstLineStartPos, stopRegex) {
772
+		// FIXME: When reading <li> content need to detect nested list without
773
+		// a blank line
490 774
 		var p = state.p;
491 775
 		var seenBlankLine = false;
492 776
 		var needsBlocks = false;
@@ -614,7 +898,6 @@ class Markdown {
614 898
 	 * @returns {_MDBlock|null}
615 899
 	 */
616 900
 	static #readUnorderedList(state) {
617
-		var p = state.p;
618 901
 		var items = [];
619 902
 		var item = null;
620 903
 		do {
@@ -627,10 +910,29 @@ class Markdown {
627 910
 
628 911
 	/**
629 912
 	 * @param {_MDState} state
913
+	 * @returns {_MDListItem|null}
914
+	 */
915
+	static #readOrderedListItem(state) {
916
+		var p = state.p;
917
+		let line = state.lines[p];
918
+		let groups = /^(\d+\.\s+)(.*)$/.exec(line);
919
+		if (groups === null) return null;
920
+		return new _MDListItem(this.#readInteriorContent(state, groups[1].length, /^\d+\.\s+/));
921
+	}
922
+
923
+	/**
924
+	 * @param {_MDState} state
630 925
 	 * @returns {_MDBlock|null}
631 926
 	 */
632 927
 	static #readOrderedList(state) {
633
-		return null;
928
+		var items = [];
929
+		var item = null;
930
+		do {
931
+			item = this.#readOrderedListItem(state);
932
+			if (item) items.push(item);
933
+		} while (item);
934
+		if (items.length == 0) return null;
935
+		return new _MDOrderedList(items);
634 936
 	}
635 937
 
636 938
 	/**
@@ -638,6 +940,17 @@ class Markdown {
638 940
 	 * @returns {_MDBlock|null}
639 941
 	 */
640 942
 	static #readFencedCodeBlock(state) {
943
+		var p = state.p;
944
+		if (state.lines[p++].trim() != '```') return null;
945
+		var codeLines = [];
946
+		while (state.hasLines(1, p)) {
947
+			let line = state.lines[p++];
948
+			if (line.trim() == '```') {
949
+				state.p = p;
950
+				return new _MDCodeBlock(codeLines.join("\n"));
951
+			}
952
+			codeLines.push(line);
953
+		}
641 954
 		return null;
642 955
 	}
643 956
 
@@ -646,7 +959,19 @@ class Markdown {
646 959
 	 * @returns {_MDBlock|null}
647 960
 	 */
648 961
 	static #readIndentedCodeBlock(state) {
649
-		return null;
962
+		var p = state.p;
963
+		var codeLines = [];
964
+		while (state.hasLines(1, p)) {
965
+			let line = state.lines[p++];
966
+			if (this.#countIndents(line, true) < 1) {
967
+				p--;
968
+				break;
969
+			}
970
+			codeLines.push(this.#stripIndent(line));
971
+		}
972
+		if (codeLines.length == 0) return null;
973
+		state.p = p;
974
+		return new _MDCodeBlock(codeLines.join("\n"));
650 975
 	}
651 976
 
652 977
 	/**
@@ -665,10 +990,75 @@ class Markdown {
665 990
 
666 991
 	/**
667 992
 	 * @param {_MDState} state
993
+	 * @param {Boolean} isHeader
994
+	 * @return {_MDTableRow|null}
995
+	 */
996
+	static #readTableRow(state, isHeader) {
997
+		if (!state.hasLines(1)) return null;
998
+		var p = state.p;
999
+		let line = state.lines[p++].trim();
1000
+		if (/.*\|.*/.exec(line) === null) return null;
1001
+		if (line.startsWith('|')) line = line.substring(1);
1002
+		if (line.endsWith('|')) line = line.substring(0, line.length - 1);
1003
+		let cellTokens = line.split('|');
1004
+		let cells = cellTokens.map(function(token) {
1005
+			let content = Markdown.#readInline(state, token);
1006
+			return isHeader ? new _MDTableHeaderCell(content) : new _MDTableCell(content);
1007
+		});
1008
+		state.p = p;
1009
+		return new _MDTableRow(cells);
1010
+	}
1011
+
1012
+	/**
1013
+	 * @param {String} line
1014
+	 * @returns {_MDHAlign[]}
1015
+	 */
1016
+	static #parseColumnAlignments(line) {
1017
+		line = line.trim();
1018
+		if (line.startsWith('|')) line = line.substring(1);
1019
+		if (line.endsWith('|')) line = line.substring(0, line.length - 1);
1020
+		return line.split('|').map(function(token) {
1021
+			token = token.trim();
1022
+			if (token.startsWith(':')) {
1023
+				if (token.endsWith(':')) {
1024
+					return _MDHAlign.Center;
1025
+				}
1026
+				return _MDHAlign.Left;
1027
+			} else if (token.endsWith(':')) {
1028
+				return _MDHAlign.Right;
1029
+			}
1030
+			return null;
1031
+		});
1032
+	}
1033
+
1034
+	/**
1035
+	 * @param {_MDState} state
668 1036
 	 * @returns {_MDBlock|null}
669 1037
 	 */
670 1038
 	static #readTable(state) {
671
-		return null;
1039
+		if (!state.hasLines(2)) return null;
1040
+		let startP = state.p;
1041
+		let headerRow = this.#readTableRow(state, true);
1042
+		if (headerRow === null) {
1043
+			state.p = startP;
1044
+			return null;
1045
+		}
1046
+		let dividerLine = state.lines[state.p++];
1047
+		let dividerGroups = /^\s*[|]?(?:\s*[:]?-+[:]?\s*\|)(?:\s*[:]?-+[:]?\s*)[|]?\s*$/.exec(dividerLine);
1048
+		if (dividerGroups === null) {
1049
+			state.p = startP;
1050
+			return null;
1051
+		}
1052
+		let columnAlignments = this.#parseColumnAlignments(dividerLine);
1053
+		headerRow.applyAlignments(columnAlignments);
1054
+		var bodyRows = [];
1055
+		while (state.hasLines(1)) {
1056
+			let row = this.#readTableRow(state, false);
1057
+			if (row === null) break;
1058
+			row.applyAlignments(columnAlignments);
1059
+			bodyRows.push(row);
1060
+		}
1061
+		return new _MDTable(headerRow, bodyRows);
672 1062
 	}
673 1063
 
674 1064
 	/**
@@ -676,6 +1066,7 @@ class Markdown {
676 1066
 	 * @returns {_MDBlock|null}
677 1067
 	 */
678 1068
 	static #readDefinitionList(state) {
1069
+		// TODO
679 1070
 		return null;
680 1071
 	}
681 1072
 
@@ -684,6 +1075,7 @@ class Markdown {
684 1075
 	 * @returns {_MDBlock|null}
685 1076
 	 */
686 1077
 	static #readFootnoteDef(state) {
1078
+		// TODO
687 1079
 		return null;
688 1080
 	}
689 1081
 
@@ -692,6 +1084,7 @@ class Markdown {
692 1084
 	 * @returns {_MDBlock|null}
693 1085
 	 */
694 1086
 	static #readAbbreviationDef(state) {
1087
+		// TODO
695 1088
 		return null;
696 1089
 	}
697 1090
 

+ 29
- 15
markdownjs.html Просмотреть файл

@@ -24,20 +24,24 @@
24 24
 				padding: 0;
25 25
 				margin: 0;
26 26
 			}
27
-			.previewhalf {
28
-				position: relative;
29
-				width: 100%;
30
-				height: 50%;
31
-				background-color: white;
32
-				overflow-y: auto;
33
-			}
34 27
 			.inputhalf {
35
-				position: relative;
36
-				width: 100%;
37
-				height: 50%;
28
+				position: absolute;
29
+				width: 50%;
30
+				height: 100%;
31
+				left: 0;
32
+				top: 0;
38 33
 				background-color: #0000aa;
39 34
 				color: white;
40 35
 			}
36
+			.previewhalf {
37
+				position: absolute;
38
+				width: 50%;
39
+				height: 100%;
40
+				left: 50%;
41
+				top: 0;
42
+				background-color: white;
43
+				overflow-y: auto;
44
+			}
41 45
 			#preview {
42 46
 				padding: 1em;
43 47
 				margin: 0 auto;
@@ -53,15 +57,20 @@
53 57
 				background-color: #0000aa;
54 58
 				color: white;
55 59
 			}
60
+			table {
61
+				border-collapse: collapse;
62
+			}
63
+			table th {
64
+				background-color: #ddd;
65
+			}
66
+			table td, table th {
67
+				border: 1px solid black;
68
+				padding: 0.2em 0.5em;
69
+			}
56 70
 		</style>
57 71
 	</head>
58 72
 
59 73
 	<body>
60
-		<div class="previewhalf half">
61
-			<div id="preview">
62
-				<p>Preview here</p>
63
-			</div>
64
-		</div>
65 74
 		<div class="inputhalf half">
66 75
 			<textarea id="markdowninput">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut mollis vel metus ut tempor. Morbi dictum, velit quis venenatis consequat, ante leo dignissim magna, quis efficitur ex magna nec ex. In egestas aliquet tortor, vitae posuere dui lobortis ac. Pellentesque quis hendrerit lorem. In maximus ut sapien vel tristique. Pellentesque ut tincidunt orci. Phasellus dignissim massa sit amet tellus placerat finibus. Ut elementum tortor ex, non aliquam libero semper eu.
67 76
 
@@ -74,5 +83,10 @@ Curabitur fermentum nunc sed dapibus feugiat. Etiam volutpat viverra eros sit am
74 83
 Quisque posuere, libero ac elementum maximus, lacus nulla lobortis nibh, id aliquam tortor lacus vel quam. Nunc eget dolor non nunc sagittis varius. Proin viverra vestibulum placerat. Fusce placerat interdum diam quis sollicitudin. Praesent sed tincidunt orci. Suspendisse lectus lorem, porta vel quam sit amet, pharetra interdum purus. Suspendisse ac lacus consequat, dapibus nunc in, porttitor purus. Nam dictum elit eget massa tincidunt tempus. Vestibulum blandit, lorem et accumsan tristique, orci dolor vulputate magna, et posuere lorem sapien non sapien. Vivamus nulla justo, eleifend et metus sit amet, gravida iaculis erat. Fusce lacinia cursus accumsan. 
75 84
 			</textarea>
76 85
 		</div>
86
+		<div class="previewhalf half">
87
+			<div id="preview">
88
+				<p>Preview here</p>
89
+			</div>
90
+		</div>
77 91
 	</body>
78 92
 </html>

Загрузка…
Отмена
Сохранить