Переглянути джерело

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

main
Rocketsoup 1 рік тому
джерело
коміт
105b4a2087
2 змінених файлів з 140 додано та 21 видалено
  1. 116
    14
      js/markdown.js
  2. 24
    7
      markdownjs.html

+ 116
- 14
js/markdown.js Переглянути файл

@@ -1,14 +1,11 @@
1
-// FIXME: Nested lists not working right
2 1
 // FIXME: Nested blockquotes require blank line
3 2
 // TODO: HTML tags probably need better handling. Consider whether interior of matched tags should be interpreted as markdown.
4 3
 // TODO: {.class #cssid lang=fr}
5
-//     # Header {.class}
6
-//     Header {.class}
7
-//     ---
8 4
 //     [link](url){.class}
9
-//     ``` {.class}
10 5
 // TODO: Test broken/incomplete syntax thoroughly
11 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 10
 class _MDHAlign {
14 11
 	static Left = new _MDHAlign('Left');
@@ -53,6 +50,7 @@ class _MDTokenType {
53 50
 	static SimpleLink = new _MDTokenType('SimpleLink'); // content=URL
54 51
 	static SimpleEmail = new _MDTokenType('SimpleEmail'); // content=email address
55 52
 	static Footnote = new _MDTokenType('Footnote'); // content=symbol
53
+	static Class = new _MDTokenType('Class'); // content
56 54
 
57 55
 	static HTMLTag = new _MDTokenType('HTMLTag'); // content=tag string, tag=_MDHTMLTag
58 56
 
@@ -917,6 +915,85 @@ class _MDState {
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 997
 class Markdown {
921 998
 	/**
922 999
 	 * @param {String} line
@@ -1563,16 +1640,22 @@ class Markdown {
1563 1640
 	static #readUnderlineHeader(state) {
1564 1641
 		var p = state.p;
1565 1642
 		if (!state.hasLines(2)) return null;
1643
+		var modifier;
1566 1644
 		let contentLine = state.lines[p++].trim();
1645
+		[contentLine, modifier] = _MDTagModifier.fromLine(contentLine);
1567 1646
 		let underLine = state.lines[p++].trim();
1568 1647
 		if (contentLine == '') return null;
1569 1648
 		if (/^=+$/.exec(underLine)) {
1570 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 1654
 		if (/^\-+$/.exec(underLine)) {
1574 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 1660
 		return null;
1578 1661
 	}
@@ -1585,10 +1668,15 @@ class Markdown {
1585 1668
 	 */
1586 1669
 	static #readHashHeader(state) {
1587 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 1675
 		if (groups === null) return null;
1590 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,13 +1775,18 @@ class Markdown {
1687 1775
 	static #readFencedCodeBlock(state) {
1688 1776
 		if (!state.hasLines(2)) return null;
1689 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 1782
 		var codeLines = [];
1692 1783
 		while (state.hasLines(1, p)) {
1693 1784
 			let line = state.lines[p++];
1694 1785
 			if (line.trim() == '```') {
1695 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 1791
 			codeLines.push(line);
1699 1792
 		}
@@ -1729,9 +1822,13 @@ class Markdown {
1729 1822
 	static #readHorizontalRule(state) {
1730 1823
 		var p = state.p;
1731 1824
 		let line = state.lines[p++];
1825
+		var modifier;
1826
+		[line, modifier] = _MDTagModifier.fromLine(line);
1732 1827
 		if (this.#horizontalRuleRegex.exec(line)) {
1733 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 1833
 		return null;
1737 1834
 	}
@@ -1744,7 +1841,7 @@ class Markdown {
1744 1841
 	static #readTableRow(state, isHeader) {
1745 1842
 		if (!state.hasLines(1)) return null;
1746 1843
 		var p = state.p;
1747
-		let line = state.lines[p++].trim();
1844
+		let line = _MDTagModifier.strip(state.lines[p++].trim());
1748 1845
 		if (/.*\|.*/.exec(line) === null) return null;
1749 1846
 		if (line.startsWith('|')) line = line.substring(1);
1750 1847
 		if (line.endsWith('|')) line = line.substring(0, line.length - 1);
@@ -1788,6 +1885,9 @@ class Markdown {
1788 1885
 	static #readTable(state) {
1789 1886
 		if (!state.hasLines(2)) return null;
1790 1887
 		let startP = state.p;
1888
+		let firstLine = state.lines[startP];
1889
+		var ignore, modifier;
1890
+		[ignore, modifier] = _MDTagModifier.fromLine(firstLine);
1791 1891
 		let headerRow = this.#readTableRow(state, true);
1792 1892
 		if (headerRow === null) {
1793 1893
 			state.p = startP;
@@ -1808,7 +1908,9 @@ class Markdown {
1808 1908
 			row.applyAlignments(columnAlignments);
1809 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 Переглянути файл

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

Завантаження…
Відмінити
Зберегти