Procházet zdrojové kódy

CSS modifiers for many block elements supported. Dark mode on test page.

main
Rocketsoup před 1 rokem
rodič
revize
105b4a2087
2 změnil soubory, kde provedl 140 přidání a 21 odebrání
  1. 116
    14
      js/markdown.js
  2. 24
    7
      markdownjs.html

+ 116
- 14
js/markdown.js Zobrazit soubor

1
-// FIXME: Nested lists not working right
2
 // FIXME: Nested blockquotes require blank line
1
 // FIXME: Nested blockquotes require blank line
3
 // TODO: HTML tags probably need better handling. Consider whether interior of matched tags should be interpreted as markdown.
2
 // TODO: HTML tags probably need better handling. Consider whether interior of matched tags should be interpreted as markdown.
4
 // TODO: {.class #cssid lang=fr}
3
 // TODO: {.class #cssid lang=fr}
5
-//     # Header {.class}
6
-//     Header {.class}
7
-//     ---
8
 //     [link](url){.class}
4
 //     [link](url){.class}
9
-//     ``` {.class}
10
 // TODO: Test broken/incomplete syntax thoroughly
5
 // TODO: Test broken/incomplete syntax thoroughly
11
 // TODO: Sanity checks on loops/recursion?
6
 // TODO: Sanity checks on loops/recursion?
7
+// TODO: Tolerate whitespace between tokens (e.g. [click here] [urlref])
8
+// TODO: Spreadsheet functions in tables
12
 
9
 
13
 class _MDHAlign {
10
 class _MDHAlign {
14
 	static Left = new _MDHAlign('Left');
11
 	static Left = new _MDHAlign('Left');
53
 	static SimpleLink = new _MDTokenType('SimpleLink'); // content=URL
50
 	static SimpleLink = new _MDTokenType('SimpleLink'); // content=URL
54
 	static SimpleEmail = new _MDTokenType('SimpleEmail'); // content=email address
51
 	static SimpleEmail = new _MDTokenType('SimpleEmail'); // content=email address
55
 	static Footnote = new _MDTokenType('Footnote'); // content=symbol
52
 	static Footnote = new _MDTokenType('Footnote'); // content=symbol
53
+	static Class = new _MDTokenType('Class'); // content
56
 
54
 
57
 	static HTMLTag = new _MDTokenType('HTMLTag'); // content=tag string, tag=_MDHTMLTag
55
 	static HTMLTag = new _MDTokenType('HTMLTag'); // content=tag string, tag=_MDHTMLTag
58
 
56
 
917
 	}
915
 	}
918
 }
916
 }
919
 
917
 
918
+class _MDTagModifier {
919
+	/** @var {String} */
920
+	original;
921
+	/** @var {String[]} */
922
+	cssClasses = [];
923
+	/** @var {String|null} */
924
+	id = null;
925
+	/** @var {Object} */
926
+	attributes = {};
927
+
928
+	static #baseClassRegex = /\.([a-z_\-][a-z0-9_\-]*)/i;
929
+	static #baseIdRegex = /#([a-z_\-][a-z0-9_\-]*)/i;
930
+	static #baseAttributeRegex = /([a-z0-9]+)=([^\s\}]+)/i;
931
+	static #modifierRegex = new RegExp('(?:' + this.#baseClassRegex.source + '|' + this.#baseIdRegex.source + '|' + this.#baseAttributeRegex.source + ')', 'i');
932
+	// static #baseRegex = new RegExp('\\{(' + this.#modifierRegex.source + '(?:\\s+' + this.#modifierRegex.source + '))\\}', 'i'); //  /\{((?:||)(?:\s+(?:\.[a-z_\-][a-z0-9_\-]*|#[a-z_\-][a-z0-9_\-]*|[a-z0-9]+=\S+)))\}/i; // 1=content
933
+	static #baseRegex = /\{([^}]+)}/i;
934
+	static #leadingClassRegex = new RegExp('^' + this.#baseRegex.source, 'i');
935
+	static #trailingClassRegex = new RegExp('^(.*?)\\s*' + this.#baseRegex.source + '\\s*$', 'i');
936
+	static #classRegex = new RegExp('^' + this.#baseClassRegex.source + '$', 'i');  // 1=classname
937
+	static #idRegex = new RegExp('^' + this.#baseIdRegex.source + '$', 'i');  // 1=id
938
+	static #attributeRegex = new RegExp('^' + this.#baseAttributeRegex.source + '$', 'i');  // 1=attribute name, 2=attribute value
939
+
940
+	/**
941
+	 * @param {_MDBlock|_MDSpan} elem
942
+	 */
943
+	applyTo(elem) {
944
+		if (elem instanceof _MDBlock || elem instanceof _MDSpan) {
945
+			elem.cssClasses = elem.cssClasses.concat(this.cssClasses);
946
+			if (this.id) elem.id = this.id;
947
+			for (const name in this.attributes) {
948
+				elem.attributes[name] = this.attributes[name];
949
+			}
950
+		}
951
+	}
952
+
953
+	static #fromContents(contents) {
954
+		let modifierTokens = contents.split(/\s+/);
955
+		let mod = new _MDTagModifier();
956
+		mod.original = `{${contents}}`;
957
+		var groups;
958
+		for (const token of modifierTokens) {
959
+			if (token.trim() == '') continue;
960
+			if (groups = this.#classRegex.exec(token)) {
961
+				mod.cssClasses.push(groups[1]);
962
+			} else if (groups = this.#idRegex.exec(token)) {
963
+				mod.id = groups[1];
964
+			} else if (groups = this.#attributeRegex.exec(token)) {
965
+				mod.attributes[groups[1]] = groups[2];
966
+			} else {
967
+				return null;
968
+			}
969
+		}
970
+		return mod;
971
+	}
972
+
973
+	/**
974
+	 * Extracts modifier from line.
975
+	 * @param {String} line
976
+	 * @returns {Array} Tuple with remaining line and _MDTagModifier.
977
+	 */
978
+	static fromLine(line) {
979
+		let groups = this.#trailingClassRegex.exec(line);
980
+		if (groups === null) return [ line, null ];
981
+		let bareLine = groups[1];
982
+		let mod = this.#fromContents(groups[2]);
983
+		return [ bareLine, mod ];
984
+	}
985
+
986
+	/**
987
+	 * @param {String} line
988
+	 * @returns {String}
989
+	 */
990
+	static strip(line) {
991
+		let groups = this.#trailingClassRegex.exec(line);
992
+		if (groups === null) return line;
993
+		return groups[1];
994
+	}
995
+}
996
+
920
 class Markdown {
997
 class Markdown {
921
 	/**
998
 	/**
922
 	 * @param {String} line
999
 	 * @param {String} line
1563
 	static #readUnderlineHeader(state) {
1640
 	static #readUnderlineHeader(state) {
1564
 		var p = state.p;
1641
 		var p = state.p;
1565
 		if (!state.hasLines(2)) return null;
1642
 		if (!state.hasLines(2)) return null;
1643
+		var modifier;
1566
 		let contentLine = state.lines[p++].trim();
1644
 		let contentLine = state.lines[p++].trim();
1645
+		[contentLine, modifier] = _MDTagModifier.fromLine(contentLine);
1567
 		let underLine = state.lines[p++].trim();
1646
 		let underLine = state.lines[p++].trim();
1568
 		if (contentLine == '') return null;
1647
 		if (contentLine == '') return null;
1569
 		if (/^=+$/.exec(underLine)) {
1648
 		if (/^=+$/.exec(underLine)) {
1570
 			state.p = p;
1649
 			state.p = p;
1571
-			return new _MDHeaderBlock(1, this.#readInline(state, contentLine));
1650
+			let block = new _MDHeaderBlock(1, this.#readInline(state, contentLine));
1651
+			if (modifier) modifier.applyTo(block);
1652
+			return block;
1572
 		}
1653
 		}
1573
 		if (/^\-+$/.exec(underLine)) {
1654
 		if (/^\-+$/.exec(underLine)) {
1574
 			state.p = p;
1655
 			state.p = p;
1575
-			return new _MDHeaderBlock(2, this.#readInline(state, contentLine));
1656
+			let block = new _MDHeaderBlock(2, this.#readInline(state, contentLine));
1657
+			if (modifier) modifier.applyTo(block);
1658
+			return block;
1576
 		}
1659
 		}
1577
 		return null;
1660
 		return null;
1578
 	}
1661
 	}
1585
 	 */
1668
 	 */
1586
 	static #readHashHeader(state) {
1669
 	static #readHashHeader(state) {
1587
 		var p = state.p;
1670
 		var p = state.p;
1588
-		var groups = this.#hashHeaderRegex.exec(state.lines[p++]);
1671
+		let line = state.lines[p++];
1672
+		var modifier;
1673
+		[line, modifier] = _MDTagModifier.fromLine(line);
1674
+		var groups = this.#hashHeaderRegex.exec(line);
1589
 		if (groups === null) return null;
1675
 		if (groups === null) return null;
1590
 		state.p = p;
1676
 		state.p = p;
1591
-		return new _MDHeaderBlock(groups[1].length, this.#readInline(state, groups[2]));
1677
+		let block = new _MDHeaderBlock(groups[1].length, this.#readInline(state, groups[2]));
1678
+		if (modifier) modifier.applyTo(block);
1679
+		return block;
1592
 	}
1680
 	}
1593
 
1681
 
1594
 	/**
1682
 	/**
1687
 	static #readFencedCodeBlock(state) {
1775
 	static #readFencedCodeBlock(state) {
1688
 		if (!state.hasLines(2)) return null;
1776
 		if (!state.hasLines(2)) return null;
1689
 		var p = state.p;
1777
 		var p = state.p;
1690
-		if (state.lines[p++].trim() != '```') return null;
1778
+		let openFenceLine = state.lines[p++];
1779
+		var modifier;
1780
+		[openFenceLine, modifier] = _MDTagModifier.fromLine(openFenceLine);
1781
+		if (openFenceLine.trim() != '```') return null;
1691
 		var codeLines = [];
1782
 		var codeLines = [];
1692
 		while (state.hasLines(1, p)) {
1783
 		while (state.hasLines(1, p)) {
1693
 			let line = state.lines[p++];
1784
 			let line = state.lines[p++];
1694
 			if (line.trim() == '```') {
1785
 			if (line.trim() == '```') {
1695
 				state.p = p;
1786
 				state.p = p;
1696
-				return new _MDCodeBlock(codeLines.join("\n"));
1787
+				let block = new _MDCodeBlock(codeLines.join("\n"));
1788
+				if (modifier) modifier.applyTo(block);
1789
+				return block;
1697
 			}
1790
 			}
1698
 			codeLines.push(line);
1791
 			codeLines.push(line);
1699
 		}
1792
 		}
1729
 	static #readHorizontalRule(state) {
1822
 	static #readHorizontalRule(state) {
1730
 		var p = state.p;
1823
 		var p = state.p;
1731
 		let line = state.lines[p++];
1824
 		let line = state.lines[p++];
1825
+		var modifier;
1826
+		[line, modifier] = _MDTagModifier.fromLine(line);
1732
 		if (this.#horizontalRuleRegex.exec(line)) {
1827
 		if (this.#horizontalRuleRegex.exec(line)) {
1733
 			state.p = p;
1828
 			state.p = p;
1734
-			return new _MDHorizontalRuleBlock();
1829
+			let block = new _MDHorizontalRuleBlock();
1830
+			if (modifier) modifier.applyTo(block);
1831
+			return block;
1735
 		}
1832
 		}
1736
 		return null;
1833
 		return null;
1737
 	}
1834
 	}
1744
 	static #readTableRow(state, isHeader) {
1841
 	static #readTableRow(state, isHeader) {
1745
 		if (!state.hasLines(1)) return null;
1842
 		if (!state.hasLines(1)) return null;
1746
 		var p = state.p;
1843
 		var p = state.p;
1747
-		let line = state.lines[p++].trim();
1844
+		let line = _MDTagModifier.strip(state.lines[p++].trim());
1748
 		if (/.*\|.*/.exec(line) === null) return null;
1845
 		if (/.*\|.*/.exec(line) === null) return null;
1749
 		if (line.startsWith('|')) line = line.substring(1);
1846
 		if (line.startsWith('|')) line = line.substring(1);
1750
 		if (line.endsWith('|')) line = line.substring(0, line.length - 1);
1847
 		if (line.endsWith('|')) line = line.substring(0, line.length - 1);
1788
 	static #readTable(state) {
1885
 	static #readTable(state) {
1789
 		if (!state.hasLines(2)) return null;
1886
 		if (!state.hasLines(2)) return null;
1790
 		let startP = state.p;
1887
 		let startP = state.p;
1888
+		let firstLine = state.lines[startP];
1889
+		var ignore, modifier;
1890
+		[ignore, modifier] = _MDTagModifier.fromLine(firstLine);
1791
 		let headerRow = this.#readTableRow(state, true);
1891
 		let headerRow = this.#readTableRow(state, true);
1792
 		if (headerRow === null) {
1892
 		if (headerRow === null) {
1793
 			state.p = startP;
1893
 			state.p = startP;
1808
 			row.applyAlignments(columnAlignments);
1908
 			row.applyAlignments(columnAlignments);
1809
 			bodyRows.push(row);
1909
 			bodyRows.push(row);
1810
 		}
1910
 		}
1811
-		return new _MDTableBlock(headerRow, bodyRows);
1911
+		let table = new _MDTableBlock(headerRow, bodyRows);
1912
+		if (modifier) modifier.applyTo(table);
1913
+		return table;
1812
 	}
1914
 	}
1813
 
1915
 
1814
 	/**
1916
 	/**

+ 24
- 7
markdownjs.html Zobrazit soubor

5
 		<title>Markdown Test</title>
5
 		<title>Markdown Test</title>
6
 		<link rel="icon" href="data:;base64,iVBORw0KGgo=">
6
 		<link rel="icon" href="data:;base64,iVBORw0KGgo=">
7
 		<style type="text/css">
7
 		<style type="text/css">
8
+			:root {
9
+				--color-background: white;
10
+				--color-text: black;
11
+				--color-editor-background: #0000aa;
12
+				--color-editor-text: white;
13
+				--color-table-header: #ddd;
14
+				--color-table-border: black;
15
+			}
8
 			:root, body {
16
 			:root, body {
9
 				width: 100%;
17
 				width: 100%;
10
 				height: 100%;
18
 				height: 100%;
11
 				padding: 0;
19
 				padding: 0;
12
 				margin: 0;
20
 				margin: 0;
21
+				background-color: var(--color-background);
22
+				color: var(--color-text);
13
 			}
23
 			}
14
 			.inputhalf {
24
 			.inputhalf {
15
 				position: absolute;
25
 				position: absolute;
17
 				height: 100%;
27
 				height: 100%;
18
 				left: 0;
28
 				left: 0;
19
 				top: 0;
29
 				top: 0;
20
-				background-color: #0000aa;
21
-				color: white;
30
+				background-color: var(--color-editor-background);
31
+				color: var(--color-editor-text);
22
 			}
32
 			}
23
 			.previewhalf {
33
 			.previewhalf {
24
 				position: absolute;
34
 				position: absolute;
26
 				height: 100%;
36
 				height: 100%;
27
 				left: 50%;
37
 				left: 50%;
28
 				top: 0;
38
 				top: 0;
29
-				background-color: white;
30
 				overflow-y: auto;
39
 				overflow-y: auto;
31
 			}
40
 			}
32
 			#preview {
41
 			#preview {
41
 				margin: 0;
50
 				margin: 0;
42
 				padding: 0.5em;
51
 				padding: 0.5em;
43
 				border: 0;
52
 				border: 0;
44
-				background-color: #0000aa;
45
-				color: white;
53
+				background-color: var(--color-editor-background);
54
+				color: var(--color-editor-text);
46
 			}
55
 			}
47
 			table {
56
 			table {
48
 				border-collapse: collapse;
57
 				border-collapse: collapse;
49
 			}
58
 			}
50
 			table th {
59
 			table th {
51
-				background-color: #ddd;
60
+				background-color: var(--color-table-header);
52
 			}
61
 			}
53
 			table td, table th {
62
 			table td, table th {
54
-				border: 1px solid black;
63
+				border: 1px solid var(--color-table-border);
55
 				padding: 0.2em 0.5em;
64
 				padding: 0.2em 0.5em;
56
 			}
65
 			}
66
+			@media (prefers-color-scheme: dark) {
67
+				:root {
68
+					--color-background: black;
69
+					--color-text: white;
70
+					--color-table-header: #333;
71
+					--color-table-border: #ccc;
72
+				}
73
+			}
57
 		</style>
74
 		</style>
58
 		<script src="js/markdown.js"></script>
75
 		<script src="js/markdown.js"></script>
59
 		<script>
76
 		<script>

Načítá se…
Zrušit
Uložit