|
|
@@ -50,7 +50,7 @@ class _MDTokenType {
|
|
50
|
50
|
static SimpleLink = new _MDTokenType('SimpleLink'); // content=URL
|
|
51
|
51
|
static SimpleEmail = new _MDTokenType('SimpleEmail'); // content=email address
|
|
52
|
52
|
static Footnote = new _MDTokenType('Footnote'); // content=symbol
|
|
53
|
|
- static Class = new _MDTokenType('Class'); // content
|
|
|
53
|
+ static Modifier = new _MDTokenType('Modifier'); // content
|
|
54
|
54
|
|
|
55
|
55
|
static HTMLTag = new _MDTokenType('HTMLTag'); // content=tag string, tag=_MDHTMLTag
|
|
56
|
56
|
|
|
|
@@ -82,18 +82,26 @@ class _MDToken {
|
|
82
|
82
|
extra;
|
|
83
|
83
|
/** @var {_MDHTMLTag|null} */
|
|
84
|
84
|
tag;
|
|
|
85
|
+ /** @var {_MDTagModifier|null} */
|
|
|
86
|
+ modifier;
|
|
85
|
87
|
|
|
86
|
88
|
/**
|
|
87
|
89
|
* @param {String} original
|
|
88
|
90
|
* @param {_MDTokenType} type
|
|
89
|
|
- * @param {String|null} content
|
|
|
91
|
+ * @param {String|_MDTagModifier|null} content
|
|
90
|
92
|
* @param {String|null} extra
|
|
91
|
93
|
* @param {_MDHTMLTag|null} tag
|
|
92
|
94
|
*/
|
|
93
|
95
|
constructor(original, type, content=null, extra=null, tag=null) {
|
|
94
|
96
|
this.original = original;
|
|
95
|
97
|
this.type = type;
|
|
96
|
|
- this.content = content;
|
|
|
98
|
+ if (content instanceof _MDTagModifier) {
|
|
|
99
|
+ this.content = null;
|
|
|
100
|
+ this.modifier = content;
|
|
|
101
|
+ } else {
|
|
|
102
|
+ this.content = content;
|
|
|
103
|
+ this.modifier = null;
|
|
|
104
|
+ }
|
|
97
|
105
|
this.extra = extra;
|
|
98
|
106
|
this.tag = tag;
|
|
99
|
107
|
}
|
|
|
@@ -925,12 +933,10 @@ class _MDTagModifier {
|
|
925
|
933
|
/** @var {Object} */
|
|
926
|
934
|
attributes = {};
|
|
927
|
935
|
|
|
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;
|
|
|
936
|
+ static #baseClassRegex = /\.([a-z_\-][a-z0-9_\-]*?)/i;
|
|
|
937
|
+ static #baseIdRegex = /#([a-z_\-][a-z0-9_\-]*?)/i;
|
|
|
938
|
+ static #baseAttributeRegex = /([a-z0-9]+?)=([^\s\}]+?)/i;
|
|
|
939
|
+ static #baseRegex = /\{([^}]+?)}/i;
|
|
934
|
940
|
static #leadingClassRegex = new RegExp('^' + this.#baseRegex.source, 'i');
|
|
935
|
941
|
static #trailingClassRegex = new RegExp('^(.*?)\\s*' + this.#baseRegex.source + '\\s*$', 'i');
|
|
936
|
942
|
static #classRegex = new RegExp('^' + this.#baseClassRegex.source + '$', 'i'); // 1=classname
|
|
|
@@ -984,6 +990,17 @@ class _MDTagModifier {
|
|
984
|
990
|
}
|
|
985
|
991
|
|
|
986
|
992
|
/**
|
|
|
993
|
+ * Extracts modifier from head of string.
|
|
|
994
|
+ * @param {String} line
|
|
|
995
|
+ * @returns {_MDTagModifier}
|
|
|
996
|
+ */
|
|
|
997
|
+ static fromStart(line) {
|
|
|
998
|
+ let groups = this.#leadingClassRegex.exec(line);
|
|
|
999
|
+ if (groups === null) return null;
|
|
|
1000
|
+ return this.#fromContents(groups[1]);
|
|
|
1001
|
+ }
|
|
|
1002
|
+
|
|
|
1003
|
+ /**
|
|
987
|
1004
|
* @param {String} line
|
|
988
|
1005
|
* @returns {String}
|
|
989
|
1006
|
*/
|
|
|
@@ -1243,7 +1260,7 @@ class Markdown {
|
|
1243
|
1260
|
static #footnoteWithTitleRegex = /^\[\^(\d+?)\s+"(.*?)"\]/; // 1=symbol, 2=title
|
|
1244
|
1261
|
static #footnoteRegex = /^\[\^(\d+?)\]/; // 1=symbol
|
|
1245
|
1262
|
// Note: label contents have to have matching pairs of [] and (). Handles images inside links.
|
|
1246
|
|
- static #labelRegex = /^\[((?:[^\[\]]*\[[^\[\]]*\][^\[\]]*|[^\(\)]*\([^\(\)]*?\)[^\(\)]*|[^\[\]\(\)]*?)*)\]/; // 1=content
|
|
|
1263
|
+ static #labelRegex = /^\[((?:[^\[\]]*?\[[^\[\]]*?\][^\[\]]*?|[^\(\)]*?\([^\(\)]*?\)[^\(\)]*?|[^\[\]\(\)]*?)*?)\]/; // 1=content
|
|
1247
|
1264
|
static #urlWithTitleRegex = /^\((\S+?)\s+"(.*?)"\)/i; // 1=URL, 2=title
|
|
1248
|
1265
|
static #urlRegex = /^\((\S+?)\)/i; // 1=URL
|
|
1249
|
1266
|
static #emailWithTitleRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s+\"(.*?)\"\\s*\\)", "i"); // 1=email, 2=title
|
|
|
@@ -1253,6 +1270,34 @@ class Markdown {
|
|
1253
|
1270
|
|
|
1254
|
1271
|
/**
|
|
1255
|
1272
|
* @param {String} line
|
|
|
1273
|
+ * @returns {String[]}
|
|
|
1274
|
+ */
|
|
|
1275
|
+ static #matchLabel(line) {
|
|
|
1276
|
+ if (!line.startsWith('[')) return null;
|
|
|
1277
|
+ var parenCount = 0;
|
|
|
1278
|
+ var bracketCount = 0;
|
|
|
1279
|
+ for (var p = 1; p < line.length; p++) {
|
|
|
1280
|
+ let ch = line.substring(p, p + 1);
|
|
|
1281
|
+ if (ch == '(') {
|
|
|
1282
|
+ parenCount++;
|
|
|
1283
|
+ } else if (ch == ')') {
|
|
|
1284
|
+ parenCount--;
|
|
|
1285
|
+ if (parenCount < 0) return null;
|
|
|
1286
|
+ } else if (ch == '[') {
|
|
|
1287
|
+ bracketCount++;
|
|
|
1288
|
+ } else if (ch == ']') {
|
|
|
1289
|
+ if (bracketCount > 0) {
|
|
|
1290
|
+ bracketCount--;
|
|
|
1291
|
+ } else {
|
|
|
1292
|
+ return [ line.substring(0, p + 1), line.substring(1, p) ];
|
|
|
1293
|
+ }
|
|
|
1294
|
+ }
|
|
|
1295
|
+ }
|
|
|
1296
|
+ return null;
|
|
|
1297
|
+ }
|
|
|
1298
|
+
|
|
|
1299
|
+ /**
|
|
|
1300
|
+ * @param {String} line
|
|
1256
|
1301
|
* @returns {_MDToken[]} tokens
|
|
1257
|
1302
|
*/
|
|
1258
|
1303
|
static #tokenize(line) {
|
|
|
@@ -1261,6 +1306,7 @@ class Markdown {
|
|
1261
|
1306
|
var expectLiteral = false;
|
|
1262
|
1307
|
var groups = null;
|
|
1263
|
1308
|
var tag = null;
|
|
|
1309
|
+ var modifier = null;
|
|
1264
|
1310
|
const endText = function() {
|
|
1265
|
1311
|
if (text.length == 0) return;
|
|
1266
|
1312
|
let textGroups = Markdown.#textWhitespaceRegex.exec(text);
|
|
|
@@ -1315,7 +1361,7 @@ class Markdown {
|
|
1315
|
1361
|
endText();
|
|
1316
|
1362
|
tokens.push(new _MDToken(groups[0], _MDTokenType.Footnote, groups[1]));
|
|
1317
|
1363
|
p += groups[0].length - 1;
|
|
1318
|
|
- } else if (groups = this.#labelRegex.exec(remainder)) {
|
|
|
1364
|
+ } else if (groups = this.#matchLabel(remainder)) {
|
|
1319
|
1365
|
// Label/ref for link/image [Foo]
|
|
1320
|
1366
|
endText();
|
|
1321
|
1367
|
tokens.push(new _MDToken(groups[0], _MDTokenType.Label, groups[1]));
|
|
|
@@ -1354,6 +1400,10 @@ class Markdown {
|
|
1354
|
1400
|
endText();
|
|
1355
|
1401
|
tokens.push(new _MDToken(tag.fullTag, _MDTokenType.HTMLTag, tag.fullTag, null, tag));
|
|
1356
|
1402
|
p += tag.fullTag.length - 1;
|
|
|
1403
|
+ } else if (modifier = _MDTagModifier.fromStart(remainder)) {
|
|
|
1404
|
+ endText();
|
|
|
1405
|
+ tokens.push(new _MDToken(modifier.original, _MDTokenType.Modifier, modifier));
|
|
|
1406
|
+ p += modifier.original.length - 1;
|
|
1357
|
1407
|
} else {
|
|
1358
|
1408
|
text += ch;
|
|
1359
|
1409
|
}
|
|
|
@@ -1551,10 +1601,18 @@ class Markdown {
|
|
1551
|
1601
|
}
|
|
1552
|
1602
|
} while (anyChanges);
|
|
1553
|
1603
|
}
|
|
|
1604
|
+ var lastSpan = null;
|
|
1554
|
1605
|
spans = spans.map(function(span) {
|
|
1555
|
1606
|
if (span instanceof _MDToken) {
|
|
|
1607
|
+ if (span.type == _MDTokenType.Modifier && lastSpan) {
|
|
|
1608
|
+ span.modifier.applyTo(lastSpan);
|
|
|
1609
|
+ lastSpan = null;
|
|
|
1610
|
+ return new _MDTextSpan('');
|
|
|
1611
|
+ }
|
|
|
1612
|
+ lastSpan = null;
|
|
1556
|
1613
|
return new _MDTextSpan(span.original);
|
|
1557
|
1614
|
} else if (span instanceof _MDSpan) {
|
|
|
1615
|
+ lastSpan = (span instanceof _MDTextSpan) ? null : span;
|
|
1558
|
1616
|
return span;
|
|
1559
|
1617
|
} else {
|
|
1560
|
1618
|
throw new Error(`Unexpected span type ${span.constructor.name}`);
|