|
|
@@ -1,5 +1,3 @@
|
|
1
|
|
-// TODO: Linked image not parsed correctly. [](link.html)
|
|
2
|
|
-// TODO: Referenced URL definitions
|
|
3
|
1
|
// TODO: HTML tags probably need better handling. Consider whether interior of matched tags should be interpreted as markdown.
|
|
4
|
2
|
// TODO: {.class #cssid lang=fr}
|
|
5
|
3
|
// # Header {.class}
|
|
|
@@ -213,24 +211,30 @@ class _MDLinkSpan extends _MDSpan {
|
|
213
|
211
|
target = null;
|
|
214
|
212
|
/** @var {_MDSpan} */
|
|
215
|
213
|
content;
|
|
|
214
|
+ /** @var {String|null} */
|
|
|
215
|
+ title = null;
|
|
216
|
216
|
|
|
217
|
217
|
/**
|
|
218
|
218
|
* @param {String} link
|
|
219
|
219
|
* @param {_MDSpan} content
|
|
220
|
220
|
*/
|
|
221
|
|
- constructor(link, content) {
|
|
|
221
|
+ constructor(link, content, title=null) {
|
|
222
|
222
|
super();
|
|
223
|
223
|
this.link = link;
|
|
224
|
224
|
this.content = content;
|
|
|
225
|
+ this.title = title;
|
|
225
|
226
|
}
|
|
226
|
227
|
|
|
227
|
228
|
toHTML(state) {
|
|
228
|
229
|
let escapedLink = this.link.replace('"', '"');
|
|
229
|
230
|
var html = `<a href="${escapedLink}"`;
|
|
230
|
|
- if (target) {
|
|
|
231
|
+ if (this.target) {
|
|
231
|
232
|
let escapedTarget = this.target.replace('"', '"');
|
|
232
|
233
|
html += ` target="${escapedTarget}"`;
|
|
233
|
234
|
}
|
|
|
235
|
+ if (this.title) {
|
|
|
236
|
+ html += ` title="${this.title.replace('"', '"')}"`;
|
|
|
237
|
+ }
|
|
234
|
238
|
html += this.htmlAttributes();
|
|
235
|
239
|
html += '>' + this.content.toHTML(state) + '</a>';
|
|
236
|
240
|
return html;
|
|
|
@@ -246,7 +250,16 @@ class _MDReferencedLinkSpan extends _MDLinkSpan {
|
|
246
|
250
|
this.id = id;
|
|
247
|
251
|
}
|
|
248
|
252
|
|
|
|
253
|
+ /**
|
|
|
254
|
+ * @param {_MDState} state
|
|
|
255
|
+ */
|
|
249
|
256
|
toHTML(state) {
|
|
|
257
|
+ if (!this.link) {
|
|
|
258
|
+ let url = state.urls[this.id.toLowerCase()];
|
|
|
259
|
+ let title = state.urlTitles[this.id.toLowerCase()];
|
|
|
260
|
+ this.link = url;
|
|
|
261
|
+ this.title = title || this.title;
|
|
|
262
|
+ }
|
|
250
|
263
|
if (this.link) {
|
|
251
|
264
|
return super.toHTML(state);
|
|
252
|
265
|
} else {
|
|
|
@@ -376,6 +389,12 @@ class _MDReferencedImageSpan extends _MDImageSpan {
|
|
376
|
389
|
}
|
|
377
|
390
|
|
|
378
|
391
|
toHTML(state) {
|
|
|
392
|
+ if (!this.source) {
|
|
|
393
|
+ let url = state.urls[this.id.toLowerCase()];
|
|
|
394
|
+ let title = state.urlTitles[this.id.toLowerCase()];
|
|
|
395
|
+ this.source = url;
|
|
|
396
|
+ this.title = title || this.title;
|
|
|
397
|
+ }
|
|
379
|
398
|
if (this.source) {
|
|
380
|
399
|
return super.toHTML(state);
|
|
381
|
400
|
} else {
|
|
|
@@ -791,6 +810,12 @@ class _MDState {
|
|
791
|
810
|
/** @var {Object} */
|
|
792
|
811
|
#footnotes = {};
|
|
793
|
812
|
|
|
|
813
|
+ /** @var {Object} */
|
|
|
814
|
+ #urlDefinitions = {};
|
|
|
815
|
+
|
|
|
816
|
+ /** @var {Object} */
|
|
|
817
|
+ #urlTitles = {};
|
|
|
818
|
+
|
|
794
|
819
|
/** @var {number} */
|
|
795
|
820
|
p = 0;
|
|
796
|
821
|
|
|
|
@@ -812,6 +837,16 @@ class _MDState {
|
|
812
|
837
|
return (this.#parent) ? this.#parent.footnotes : this.#footnotes;
|
|
813
|
838
|
}
|
|
814
|
839
|
|
|
|
840
|
+ /** @var {Object} */
|
|
|
841
|
+ get urls() {
|
|
|
842
|
+ return (this.#parent) ? this.#parent.urls : this.#urlDefinitions;
|
|
|
843
|
+ }
|
|
|
844
|
+
|
|
|
845
|
+ /** @var {Object} */
|
|
|
846
|
+ get urlTitles() {
|
|
|
847
|
+ return (this.#parent) ? this.#parent.urlTitles : this.#urlTitles;
|
|
|
848
|
+ }
|
|
|
849
|
+
|
|
815
|
850
|
/**
|
|
816
|
851
|
* @param {String[]} lines
|
|
817
|
852
|
*/
|
|
|
@@ -849,6 +884,17 @@ class _MDState {
|
|
849
|
884
|
}
|
|
850
|
885
|
}
|
|
851
|
886
|
|
|
|
887
|
+ defineURL(symbol, url, title=null) {
|
|
|
888
|
+ if (this.#parent) {
|
|
|
889
|
+ this.#parent.defineURL(symbol, url, title);
|
|
|
890
|
+ } else {
|
|
|
891
|
+ this.#urlDefinitions[symbol.toLowerCase()] = url;
|
|
|
892
|
+ if (title !== null) {
|
|
|
893
|
+ this.#urlTitles[symbol.toLowerCase()] = title;
|
|
|
894
|
+ }
|
|
|
895
|
+ }
|
|
|
896
|
+ }
|
|
|
897
|
+
|
|
852
|
898
|
hasLines(minCount, p=-1) {
|
|
853
|
899
|
let relativeTo = (p < 0) ? this.p : p;
|
|
854
|
900
|
return relativeTo + minCount <= this.lines.length;
|
|
|
@@ -923,6 +969,7 @@ class Markdown {
|
|
923
|
969
|
block = this.#readTable(state); if (block) return block;
|
|
924
|
970
|
block = this.#readFootnoteDef(state); if (block) return block;
|
|
925
|
971
|
block = this.#readAbbreviationDef(state); if (block) return block;
|
|
|
972
|
+ block = this.#readURLDef(state); if (block) return block;
|
|
926
|
973
|
block = this.#readDefinitionList(state); if (block) return block;
|
|
927
|
974
|
block = this.#readParagraph(state); if (block) return block;
|
|
928
|
975
|
return null;
|
|
|
@@ -1102,7 +1149,8 @@ class Markdown {
|
|
1102
|
1149
|
|
|
1103
|
1150
|
static #footnoteWithTitleRegex = /^\[\^(\d+?)\s+"(.*?)"\]/; // 1=symbol, 2=title
|
|
1104
|
1151
|
static #footnoteRegex = /^\[\^(\d+?)\]/; // 1=symbol
|
|
1105
|
|
- static #labelRegex = /^\[(.*?)\]/; // 1=content
|
|
|
1152
|
+ // Note: label contents have to have matching pairs of [] and (). Handles images inside links.
|
|
|
1153
|
+ static #labelRegex = /^\[((?:[^\[\]]*\[[^\[\]]*\][^\[\]]*|[^\(\)]*\([^\(\)]*?\)[^\(\)]*|[^\[\]\(\)]*?)*)\]/; // 1=content
|
|
1106
|
1154
|
static #urlWithTitleRegex = /^\((\S+?)\s+"(.*?)"\)/i; // 1=URL, 2=title
|
|
1107
|
1155
|
static #urlRegex = /^\((\S+?)\)/i; // 1=URL
|
|
1108
|
1156
|
static #emailWithTitleRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s+\"(.*?)\"\\s*\\)", "i"); // 1=email, 2=title
|
|
|
@@ -1255,14 +1303,14 @@ class Markdown {
|
|
1255
|
1303
|
*/
|
|
1256
|
1304
|
static #readInline(state, line) {
|
|
1257
|
1305
|
var tokens = this.#tokenize(line);
|
|
1258
|
|
- return new _MDInlineBlock(this.#tokensToSpans(tokens));
|
|
|
1306
|
+ return new _MDInlineBlock(this.#tokensToSpans(tokens, state));
|
|
1259
|
1307
|
}
|
|
1260
|
1308
|
|
|
1261
|
1309
|
/**
|
|
1262
|
1310
|
* @param {Array} tokens
|
|
1263
|
1311
|
* @returns {_MDSpan[]} spans
|
|
1264
|
1312
|
*/
|
|
1265
|
|
- static #tokensToSpans(tokens) {
|
|
|
1313
|
+ static #tokensToSpans(tokens, state) {
|
|
1266
|
1314
|
var spans = tokens.slice(0, tokens.length);
|
|
1267
|
1315
|
var anyChanges = false;
|
|
1268
|
1316
|
var index, index0;
|
|
|
@@ -1275,9 +1323,10 @@ class Markdown {
|
|
1275
|
1323
|
_MDTokenType.Label,
|
|
1276
|
1324
|
_MDTokenType.URL,
|
|
1277
|
1325
|
])) !== null) {
|
|
1278
|
|
- let alt = spans[index + 1];
|
|
1279
|
|
- let url = spans[index + 2];
|
|
1280
|
|
- spans.splice(index, 3, new _MDImageSpan(url.content, alt.content, url.extra));
|
|
|
1326
|
+ let alt = spans[index + 1].content;
|
|
|
1327
|
+ let url = spans[index + 2].content;
|
|
|
1328
|
+ let title = spans[index + 2].extra;
|
|
|
1329
|
+ spans.splice(index, 3, new _MDImageSpan(url, alt, title));
|
|
1281
|
1330
|
anyChanges = true;
|
|
1282
|
1331
|
}
|
|
1283
|
1332
|
|
|
|
@@ -1287,9 +1336,9 @@ class Markdown {
|
|
1287
|
1336
|
_MDTokenType.Label,
|
|
1288
|
1337
|
_MDTokenType.Label,
|
|
1289
|
1338
|
])) !== null) {
|
|
1290
|
|
- let alt = spans[index + 1];
|
|
1291
|
|
- let ref = spans[index + 2];
|
|
1292
|
|
- spans.splice(index, 3, new _MDReferencedImageSpan(ref.content, alt.content));
|
|
|
1339
|
+ let alt = spans[index + 1].content;
|
|
|
1340
|
+ let ref = spans[index + 2].content;
|
|
|
1341
|
+ spans.splice(index, 3, new _MDReferencedImageSpan(ref, alt));
|
|
1293
|
1342
|
anyChanges = true;
|
|
1294
|
1343
|
}
|
|
1295
|
1344
|
|
|
|
@@ -1298,9 +1347,9 @@ class Markdown {
|
|
1298
|
1347
|
_MDTokenType.Label,
|
|
1299
|
1348
|
_MDTokenType.URL,
|
|
1300
|
1349
|
])) !== null) {
|
|
1301
|
|
- let text = spans[index + 0];
|
|
1302
|
|
- let url = spans[index + 1];
|
|
1303
|
|
- spans.splice(index, 2, new _MDLinkSpan(url.content, this.#readInline(state, text.content)));
|
|
|
1350
|
+ let text = spans[index + 0].content;
|
|
|
1351
|
+ let url = spans[index + 1].content;
|
|
|
1352
|
+ spans.splice(index, 2, new _MDLinkSpan(url, this.#readInline(state, text)));
|
|
1304
|
1353
|
anyChanges = true;
|
|
1305
|
1354
|
}
|
|
1306
|
1355
|
|
|
|
@@ -1309,8 +1358,8 @@ class Markdown {
|
|
1309
|
1358
|
_MDTokenType.Label,
|
|
1310
|
1359
|
_MDTokenType.Label,
|
|
1311
|
1360
|
])) !== null) {
|
|
1312
|
|
- let text = spans[index + 0];
|
|
1313
|
|
- let ref = spans[index + 1];
|
|
|
1361
|
+ let text = spans[index + 0].content;
|
|
|
1362
|
+ let ref = spans[index + 1].content;
|
|
1314
|
1363
|
spans.splice(index, 2, new _MDReferencedLinkSpan(ref, this.#readInline(state, text)));
|
|
1315
|
1364
|
anyChanges = true;
|
|
1316
|
1365
|
}
|
|
|
@@ -1319,8 +1368,8 @@ class Markdown {
|
|
1319
|
1368
|
else if ((index = this.#firstTokenIndex(spans, [
|
|
1320
|
1369
|
_MDTokenType.Footnote,
|
|
1321
|
1370
|
])) !== null) {
|
|
1322
|
|
- let symbol = spans[index];
|
|
1323
|
|
- spans.splice(index, 1, new _MDFootnoteReferenceSpan(symbol.content));
|
|
|
1371
|
+ let symbol = spans[index].content;
|
|
|
1372
|
+ spans.splice(index, 1, new _MDFootnoteReferenceSpan(symbol));
|
|
1324
|
1373
|
anyChanges = true;
|
|
1325
|
1374
|
}
|
|
1326
|
1375
|
} while (anyChanges);
|
|
|
@@ -1349,7 +1398,7 @@ class Markdown {
|
|
1349
|
1398
|
}
|
|
1350
|
1399
|
if (hasNewStart) continue;
|
|
1351
|
1400
|
}
|
|
1352
|
|
- let contentSpans = Markdown.#tokensToSpans(contentTokens);
|
|
|
1401
|
+ let contentSpans = Markdown.#tokensToSpans(contentTokens, state);
|
|
1353
|
1402
|
return {
|
|
1354
|
1403
|
startIndex: startIndex,
|
|
1355
|
1404
|
toDelete: endIndex - startIndex + delimiter.length + 1,
|
|
|
@@ -1812,6 +1861,35 @@ class Markdown {
|
|
1812
|
1861
|
* @param {_MDState} state
|
|
1813
|
1862
|
* @returns {_MDBlock|null}
|
|
1814
|
1863
|
*/
|
|
|
1864
|
+ static #readURLDef(state) {
|
|
|
1865
|
+ var p = state.p;
|
|
|
1866
|
+ let line = state.lines[p++];
|
|
|
1867
|
+ var symbol;
|
|
|
1868
|
+ var url;
|
|
|
1869
|
+ var title = null;
|
|
|
1870
|
+ let groups = /^\s*\[(.+?)]:\s*(\S+)\s+"(.*?)"\s*$/.exec(line);
|
|
|
1871
|
+ if (groups) {
|
|
|
1872
|
+ symbol = groups[1];
|
|
|
1873
|
+ url = groups[2];
|
|
|
1874
|
+ title = groups[3];
|
|
|
1875
|
+ } else {
|
|
|
1876
|
+ groups = /^\s*\[(.+?)]:\s*(\S+)\s*$/.exec(line);
|
|
|
1877
|
+ if (groups) {
|
|
|
1878
|
+ symbol = groups[1];
|
|
|
1879
|
+ url = groups[2];
|
|
|
1880
|
+ } else {
|
|
|
1881
|
+ return null;
|
|
|
1882
|
+ }
|
|
|
1883
|
+ }
|
|
|
1884
|
+ state.defineURL(symbol, url, title);
|
|
|
1885
|
+ state.p = p;
|
|
|
1886
|
+ return new _MDInlineBlock([]);
|
|
|
1887
|
+ }
|
|
|
1888
|
+
|
|
|
1889
|
+ /**
|
|
|
1890
|
+ * @param {_MDState} state
|
|
|
1891
|
+ * @returns {_MDBlock|null}
|
|
|
1892
|
+ */
|
|
1815
|
1893
|
static #readParagraph(state) {
|
|
1816
|
1894
|
var paragraphLines = [];
|
|
1817
|
1895
|
var p = state.p;
|
|
|
@@ -1866,7 +1944,6 @@ class Markdown {
|
|
1866
|
1944
|
}
|
|
1867
|
1945
|
html += '</ol>';
|
|
1868
|
1946
|
html += '</div>';
|
|
1869
|
|
- // <!--FNREF:{symbol}-->
|
|
1870
|
1947
|
return html;
|
|
1871
|
1948
|
}
|
|
1872
|
1949
|
|