Browse Source

Added tables, all span class defs

main
Rocketsoup 1 year ago
parent
commit
dd0b817e9f
2 changed files with 460 additions and 53 deletions
  1. 431
    38
      js/markdown.js
  2. 29
    15
      markdownjs.html

+ 431
- 38
js/markdown.js View File

20
 // - Footnote (inline)   [^1]: footnote text
20
 // - Footnote (inline)   [^1]: footnote text
21
 // - Abbreviation (inline)
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
 class _MDBlock {
282
 class _MDBlock {
24
 	toHTML(config) {
283
 	toHTML(config) {
25
 		throw new Error(self.constructor.name + ".toHTML not implemented");
284
 		throw new Error(self.constructor.name + ".toHTML not implemented");
176
 	}
435
 	}
177
 }
436
 }
178
 
437
 
179
-class _MDTableHeaderCell extends _MDBlock {
438
+class _MDTableCell extends _MDBlock {
180
 	/** @var {_MDBlock} */
439
 	/** @var {_MDBlock} */
181
 	#content;
440
 	#content;
441
+	/** @var {_MDHAlign|null} */
442
+	align = null;
182
 	/**
443
 	/**
183
 	 * @param {_MDBlock} content
444
 	 * @param {_MDBlock} content
184
 	 */
445
 	 */
188
 	}
449
 	}
189
 	toHTML(config) {
450
 	toHTML(config) {
190
 		let contentHTML = this.#content.toHTML(config);
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
 	toHTML(config) {
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
 		super();
472
 		super();
219
 		this.#cells = cells;
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
 	toHTML(config) {
485
 	toHTML(config) {
222
-		cellsHTML = _MDBlock.toHTML(this.#cells, config);
486
+		let cellsHTML = _MDBlock.toHTML(this.#cells, config);
223
 		return `<tr>\n${cellsHTML}\n</tr>`;
487
 		return `<tr>\n${cellsHTML}\n</tr>`;
224
 	}
488
 	}
225
 }
489
 }
229
 	#headerRow;
493
 	#headerRow;
230
 	/** @var {_MDTableRow[]} */
494
 	/** @var {_MDTableRow[]} */
231
 	#bodyRows;
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
 	toHTML(config) {
505
 	toHTML(config) {
233
 		let headerRowHTML = this.#headerRow.toHTML(config);
506
 		let headerRowHTML = this.#headerRow.toHTML(config);
234
 		let bodyRowsHTML = _MDBlock.toHTML(this.#bodyRows);
507
 		let bodyRowsHTML = _MDBlock.toHTML(this.#bodyRows);
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
 class _MDFootnoteContent extends _MDBlock {
560
 class _MDFootnoteContent extends _MDBlock {
303
 	/** @var {String} */
561
 	/** @var {String} */
304
 	#id;
562
 	#id;
411
 		this.p = other.p;
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
 	/**
683
 	/**
425
 	 * @param {String} line
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
 	 * @returns {_MDBlock}
769
 	 * @returns {_MDBlock}
488
 	 */
770
 	 */
489
 	static #readInteriorContent(state, firstLineStartPos, stopRegex) {
771
 	static #readInteriorContent(state, firstLineStartPos, stopRegex) {
772
+		// FIXME: When reading <li> content need to detect nested list without
773
+		// a blank line
490
 		var p = state.p;
774
 		var p = state.p;
491
 		var seenBlankLine = false;
775
 		var seenBlankLine = false;
492
 		var needsBlocks = false;
776
 		var needsBlocks = false;
614
 	 * @returns {_MDBlock|null}
898
 	 * @returns {_MDBlock|null}
615
 	 */
899
 	 */
616
 	static #readUnorderedList(state) {
900
 	static #readUnorderedList(state) {
617
-		var p = state.p;
618
 		var items = [];
901
 		var items = [];
619
 		var item = null;
902
 		var item = null;
620
 		do {
903
 		do {
627
 
910
 
628
 	/**
911
 	/**
629
 	 * @param {_MDState} state
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
 	 * @returns {_MDBlock|null}
925
 	 * @returns {_MDBlock|null}
631
 	 */
926
 	 */
632
 	static #readOrderedList(state) {
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
 	 * @returns {_MDBlock|null}
940
 	 * @returns {_MDBlock|null}
639
 	 */
941
 	 */
640
 	static #readFencedCodeBlock(state) {
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
 		return null;
954
 		return null;
642
 	}
955
 	}
643
 
956
 
646
 	 * @returns {_MDBlock|null}
959
 	 * @returns {_MDBlock|null}
647
 	 */
960
 	 */
648
 	static #readIndentedCodeBlock(state) {
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
 
990
 
666
 	/**
991
 	/**
667
 	 * @param {_MDState} state
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
 	 * @returns {_MDBlock|null}
1036
 	 * @returns {_MDBlock|null}
669
 	 */
1037
 	 */
670
 	static #readTable(state) {
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
 	 * @returns {_MDBlock|null}
1066
 	 * @returns {_MDBlock|null}
677
 	 */
1067
 	 */
678
 	static #readDefinitionList(state) {
1068
 	static #readDefinitionList(state) {
1069
+		// TODO
679
 		return null;
1070
 		return null;
680
 	}
1071
 	}
681
 
1072
 
684
 	 * @returns {_MDBlock|null}
1075
 	 * @returns {_MDBlock|null}
685
 	 */
1076
 	 */
686
 	static #readFootnoteDef(state) {
1077
 	static #readFootnoteDef(state) {
1078
+		// TODO
687
 		return null;
1079
 		return null;
688
 	}
1080
 	}
689
 
1081
 
692
 	 * @returns {_MDBlock|null}
1084
 	 * @returns {_MDBlock|null}
693
 	 */
1085
 	 */
694
 	static #readAbbreviationDef(state) {
1086
 	static #readAbbreviationDef(state) {
1087
+		// TODO
695
 		return null;
1088
 		return null;
696
 	}
1089
 	}
697
 
1090
 

+ 29
- 15
markdownjs.html View File

24
 				padding: 0;
24
 				padding: 0;
25
 				margin: 0;
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
 			.inputhalf {
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
 				background-color: #0000aa;
33
 				background-color: #0000aa;
39
 				color: white;
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
 			#preview {
45
 			#preview {
42
 				padding: 1em;
46
 				padding: 1em;
43
 				margin: 0 auto;
47
 				margin: 0 auto;
53
 				background-color: #0000aa;
57
 				background-color: #0000aa;
54
 				color: white;
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
 		</style>
70
 		</style>
57
 	</head>
71
 	</head>
58
 
72
 
59
 	<body>
73
 	<body>
60
-		<div class="previewhalf half">
61
-			<div id="preview">
62
-				<p>Preview here</p>
63
-			</div>
64
-		</div>
65
 		<div class="inputhalf half">
74
 		<div class="inputhalf half">
66
 			<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.
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
 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. 
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
 			</textarea>
84
 			</textarea>
76
 		</div>
85
 		</div>
86
+		<div class="previewhalf half">
87
+			<div id="preview">
88
+				<p>Preview here</p>
89
+			</div>
90
+		</div>
77
 	</body>
91
 	</body>
78
 </html>
92
 </html>

Loading…
Cancel
Save