|
|
@@ -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
|
/**
|