markdown-parser 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -4
- package/dist/index.js +190 -125
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,6 @@ type InlineNode = TextNode | CodeSpanNode | HardBreakNode | SoftBreakNode | Stro
|
|
|
41
41
|
declare class MarkdownParser {
|
|
42
42
|
private splitter;
|
|
43
43
|
private root;
|
|
44
|
-
private nextLineIndex;
|
|
45
44
|
private nextNodeIndex;
|
|
46
45
|
private referenceDefinitions;
|
|
47
46
|
parse(input: string, options?: {
|
|
@@ -50,6 +49,8 @@ declare class MarkdownParser {
|
|
|
50
49
|
private parseLine;
|
|
51
50
|
private parseReferenceLinkDefinitions;
|
|
52
51
|
private convertInternalBlockToPublicBlock;
|
|
52
|
+
get experimental_partialNodes(): BlockNode[];
|
|
53
|
+
private convertInternalBlockToPartialBlock;
|
|
53
54
|
}
|
|
54
55
|
interface TableNode {
|
|
55
56
|
type: "table";
|
|
@@ -83,9 +84,7 @@ interface BlockquoteNode {
|
|
|
83
84
|
type ListNode = {
|
|
84
85
|
type: "list";
|
|
85
86
|
tight: boolean;
|
|
86
|
-
items: Array<
|
|
87
|
-
children: Array<BlockNode>;
|
|
88
|
-
}>;
|
|
87
|
+
items: Array<ListItemNode>;
|
|
89
88
|
} & ({
|
|
90
89
|
kind: "ordered";
|
|
91
90
|
start: number;
|
|
@@ -93,6 +92,10 @@ type ListNode = {
|
|
|
93
92
|
kind: "unordered";
|
|
94
93
|
marker: string;
|
|
95
94
|
});
|
|
95
|
+
type ListItemNode = {
|
|
96
|
+
type: "list-item";
|
|
97
|
+
children: Array<BlockNode>;
|
|
98
|
+
};
|
|
96
99
|
interface HtmlBlockNode {
|
|
97
100
|
type: "html-block";
|
|
98
101
|
content: string;
|
package/dist/index.js
CHANGED
|
@@ -1140,12 +1140,10 @@ const AUTOLINK_REGEX = /^<[A-Za-z][A-Za-z0-9.+-]{1,31}:[^<>\x00-\x20]*>/i;
|
|
|
1140
1140
|
class MarkdownParser {
|
|
1141
1141
|
parse(input, options) {
|
|
1142
1142
|
const stream = options?.stream ?? false;
|
|
1143
|
-
const
|
|
1143
|
+
for (const line of this.splitter.split(input, {
|
|
1144
1144
|
stream
|
|
1145
|
-
})
|
|
1146
|
-
for (const line of lines){
|
|
1145
|
+
})){
|
|
1147
1146
|
this.parseLine(line.replace(/\0/g, "\uFFFD"));
|
|
1148
|
-
this.nextLineIndex++;
|
|
1149
1147
|
}
|
|
1150
1148
|
// If stream is false, we need to finalize all remaining open blocks before returning the nodes.
|
|
1151
1149
|
if (!stream) {
|
|
@@ -1165,9 +1163,9 @@ class MarkdownParser {
|
|
|
1165
1163
|
return nodes;
|
|
1166
1164
|
}
|
|
1167
1165
|
parseLine(line) {
|
|
1168
|
-
const lineIndex = this.nextLineIndex;
|
|
1169
1166
|
// We start from the root node and descend through the rightmost path of the current node until we find the last open node that the current line continues.
|
|
1170
1167
|
let lastMatchedNode = this.root;
|
|
1168
|
+
const lists = []; // Tracks contiguous list items in an empty line
|
|
1171
1169
|
while(true){
|
|
1172
1170
|
if (!("children" in lastMatchedNode)) break;
|
|
1173
1171
|
// If there are no children in the current node or if the last/rightmost child of the current node is not open, we do not traverse any further, in which case the last matched node is the current node.
|
|
@@ -1181,7 +1179,6 @@ class MarkdownParser {
|
|
|
1181
1179
|
if (blockquote !== null) {
|
|
1182
1180
|
line = blockquote.content;
|
|
1183
1181
|
lastMatchedNode = child;
|
|
1184
|
-
child.endLineIndex = lineIndex;
|
|
1185
1182
|
continue;
|
|
1186
1183
|
} else {
|
|
1187
1184
|
break;
|
|
@@ -1192,8 +1189,13 @@ class MarkdownParser {
|
|
|
1192
1189
|
if (item === undefined || item.isClosed) break;
|
|
1193
1190
|
if (isLineEmpty(line)) {
|
|
1194
1191
|
const lastItemChild = item.children.at(-1);
|
|
1192
|
+
lists.push(item);
|
|
1195
1193
|
// If the current line is empty and there are no children in the list item, we close the list item.
|
|
1196
1194
|
if (lastItemChild === undefined) {
|
|
1195
|
+
// Mark all contiguous list items as having a pending blank line
|
|
1196
|
+
for (const list of lists){
|
|
1197
|
+
list.hasPendingBlankLine = true;
|
|
1198
|
+
}
|
|
1197
1199
|
closeRightmostPath(child);
|
|
1198
1200
|
return;
|
|
1199
1201
|
} else {
|
|
@@ -1221,11 +1223,9 @@ class MarkdownParser {
|
|
|
1221
1223
|
marker,
|
|
1222
1224
|
numOfMarkers
|
|
1223
1225
|
})) {
|
|
1224
|
-
child.endLineIndex = lineIndex;
|
|
1225
1226
|
child.isClosed = true;
|
|
1226
1227
|
return;
|
|
1227
1228
|
} else {
|
|
1228
|
-
child.endLineIndex = lineIndex;
|
|
1229
1229
|
child.lines.push(sliceLeadingIndent(line, child.indentLevel));
|
|
1230
1230
|
return;
|
|
1231
1231
|
}
|
|
@@ -1234,11 +1234,9 @@ class MarkdownParser {
|
|
|
1234
1234
|
if (child.type === "indented-code-block") {
|
|
1235
1235
|
// We continue the indented code block if the current line is indented enough or if it's empty.
|
|
1236
1236
|
if (isIndentedCodeLine(line)) {
|
|
1237
|
-
child.endLineIndex = lineIndex;
|
|
1238
1237
|
child.lines.push(sliceLeadingIndent(line, 4));
|
|
1239
1238
|
return;
|
|
1240
1239
|
} else if (isLineEmpty(line)) {
|
|
1241
|
-
child.endLineIndex = lineIndex;
|
|
1242
1240
|
child.lines.push("");
|
|
1243
1241
|
return;
|
|
1244
1242
|
} else {
|
|
@@ -1249,10 +1247,12 @@ class MarkdownParser {
|
|
|
1249
1247
|
if (child.type === "html-block") {
|
|
1250
1248
|
// If the HTML block can be interrupted by a blank line and the current line is empty, we mark the HTML block as closed.
|
|
1251
1249
|
if (child.canBeInterruptedByBlankLine && isLineEmpty(line)) {
|
|
1250
|
+
for (const list of lists){
|
|
1251
|
+
list.hasPendingBlankLine = true;
|
|
1252
|
+
}
|
|
1252
1253
|
child.isClosed = true;
|
|
1253
1254
|
return;
|
|
1254
1255
|
}
|
|
1255
|
-
child.endLineIndex = lineIndex;
|
|
1256
1256
|
child.lines.push(line);
|
|
1257
1257
|
// If the HTML block ends with the current line, we mark the HTML block as closed.
|
|
1258
1258
|
if (child.endPattern?.test(line.trim())) {
|
|
@@ -1265,6 +1265,10 @@ class MarkdownParser {
|
|
|
1265
1265
|
if (child.type === "paragraph" || child.type === "table") {
|
|
1266
1266
|
// If the current line is empty, we mark the paragraph or table node as closed and exit as the line has been fully processed and doesn't require any further processing.
|
|
1267
1267
|
if (isLineEmpty(line)) {
|
|
1268
|
+
// If the parent of the paragraph or table node is a list item, we mark it as having a pending blank line.
|
|
1269
|
+
for (const list of lists){
|
|
1270
|
+
list.hasPendingBlankLine = true;
|
|
1271
|
+
}
|
|
1268
1272
|
child.isClosed = true;
|
|
1269
1273
|
return;
|
|
1270
1274
|
} else {
|
|
@@ -1272,6 +1276,20 @@ class MarkdownParser {
|
|
|
1272
1276
|
}
|
|
1273
1277
|
}
|
|
1274
1278
|
}
|
|
1279
|
+
// If the last matched node is a list item and it has a pending blank line, we mark the parent list as loose.
|
|
1280
|
+
if (lastMatchedNode.type === "list-item" && lastMatchedNode.hasPendingBlankLine) {
|
|
1281
|
+
lastMatchedNode.parent.isTight = false;
|
|
1282
|
+
let node = lastMatchedNode;
|
|
1283
|
+
while(node !== null){
|
|
1284
|
+
if (node.type === "list-item") {
|
|
1285
|
+
node.hasPendingBlankLine = false;
|
|
1286
|
+
}
|
|
1287
|
+
node = node.parent;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
for (const list of lists){
|
|
1291
|
+
list.hasPendingBlankLine = true;
|
|
1292
|
+
}
|
|
1275
1293
|
// Represents the node where newly matched nodes are expected to be added. This may not always be true for exceptional cases, like paragraph continuation.
|
|
1276
1294
|
let currentContainer = lastMatchedNode;
|
|
1277
1295
|
while(true){
|
|
@@ -1282,9 +1300,7 @@ class MarkdownParser {
|
|
|
1282
1300
|
type: "blockquote",
|
|
1283
1301
|
children: [],
|
|
1284
1302
|
isClosed: false,
|
|
1285
|
-
parent: currentContainer
|
|
1286
|
-
startLineIndex: lineIndex,
|
|
1287
|
-
endLineIndex: lineIndex
|
|
1303
|
+
parent: currentContainer
|
|
1288
1304
|
};
|
|
1289
1305
|
addNode(node);
|
|
1290
1306
|
currentContainer = node;
|
|
@@ -1299,9 +1315,7 @@ class MarkdownParser {
|
|
|
1299
1315
|
level: heading.level,
|
|
1300
1316
|
content: heading.content,
|
|
1301
1317
|
isClosed: true,
|
|
1302
|
-
parent: currentContainer
|
|
1303
|
-
startLineIndex: lineIndex,
|
|
1304
|
-
endLineIndex: lineIndex
|
|
1318
|
+
parent: currentContainer
|
|
1305
1319
|
});
|
|
1306
1320
|
break;
|
|
1307
1321
|
}
|
|
@@ -1316,9 +1330,7 @@ class MarkdownParser {
|
|
|
1316
1330
|
info: fencedCode.info,
|
|
1317
1331
|
lines: [],
|
|
1318
1332
|
isClosed: false,
|
|
1319
|
-
parent: currentContainer
|
|
1320
|
-
startLineIndex: lineIndex,
|
|
1321
|
-
endLineIndex: lineIndex
|
|
1333
|
+
parent: currentContainer
|
|
1322
1334
|
});
|
|
1323
1335
|
break;
|
|
1324
1336
|
}
|
|
@@ -1333,9 +1345,7 @@ class MarkdownParser {
|
|
|
1333
1345
|
line
|
|
1334
1346
|
],
|
|
1335
1347
|
isClosed: false,
|
|
1336
|
-
parent: currentContainer
|
|
1337
|
-
startLineIndex: lineIndex,
|
|
1338
|
-
endLineIndex: lineIndex
|
|
1348
|
+
parent: currentContainer
|
|
1339
1349
|
};
|
|
1340
1350
|
if (htmlBlock.canInterruptParagraph) {
|
|
1341
1351
|
addNode(node);
|
|
@@ -1380,30 +1390,25 @@ class MarkdownParser {
|
|
|
1380
1390
|
rows: []
|
|
1381
1391
|
},
|
|
1382
1392
|
isClosed: false,
|
|
1383
|
-
parent: currentContainer
|
|
1384
|
-
startLineIndex: lineIndex,
|
|
1385
|
-
endLineIndex: lineIndex
|
|
1393
|
+
parent: currentContainer
|
|
1386
1394
|
});
|
|
1387
1395
|
break;
|
|
1388
1396
|
}
|
|
1389
1397
|
// Setext heading
|
|
1390
1398
|
const heading = parseSetextHeading(line);
|
|
1391
1399
|
if (heading !== null) {
|
|
1392
|
-
let lines = lastChild.lines;
|
|
1393
|
-
const originalNumOfLines = lines.length;
|
|
1394
1400
|
// Parse link reference definitions (if any) from the paragraph lines
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
const { label, href, title } = definition
|
|
1401
|
+
const result = parseLinkReferenceDefinitions(lastChild.lines);
|
|
1402
|
+
for (const definition of result.definitions){
|
|
1403
|
+
const { label, href, title } = definition;
|
|
1398
1404
|
if (!this.referenceDefinitions.has(label)) {
|
|
1399
1405
|
this.referenceDefinitions.set(label, {
|
|
1400
1406
|
href,
|
|
1401
1407
|
title
|
|
1402
1408
|
});
|
|
1403
1409
|
}
|
|
1404
|
-
lines = lines.slice(definition.nextLineIndex);
|
|
1405
|
-
definition = parseLinkReferenceDefinition(lines);
|
|
1406
1410
|
}
|
|
1411
|
+
const lines = lastChild.lines.slice(result.nextLineIndex);
|
|
1407
1412
|
// If there are remaining lines after parsing link reference definitions, we create a new heading node with the remaining lines
|
|
1408
1413
|
if (lines.length > 0) {
|
|
1409
1414
|
// Remove the current paragraph node from the container node as it'll replaced by a heading node
|
|
@@ -1413,9 +1418,7 @@ class MarkdownParser {
|
|
|
1413
1418
|
level: heading.level,
|
|
1414
1419
|
content: lines.join("\n").trim(),
|
|
1415
1420
|
isClosed: true,
|
|
1416
|
-
parent: currentContainer
|
|
1417
|
-
startLineIndex: lastChild.startLineIndex - (originalNumOfLines - lines.length),
|
|
1418
|
-
endLineIndex: lineIndex
|
|
1421
|
+
parent: currentContainer
|
|
1419
1422
|
});
|
|
1420
1423
|
break;
|
|
1421
1424
|
} else {
|
|
@@ -1429,9 +1432,7 @@ class MarkdownParser {
|
|
|
1429
1432
|
addNode({
|
|
1430
1433
|
type: "thematic-break",
|
|
1431
1434
|
isClosed: true,
|
|
1432
|
-
parent: currentContainer
|
|
1433
|
-
startLineIndex: lineIndex,
|
|
1434
|
-
endLineIndex: lineIndex
|
|
1435
|
+
parent: currentContainer
|
|
1435
1436
|
});
|
|
1436
1437
|
break;
|
|
1437
1438
|
}
|
|
@@ -1442,7 +1443,6 @@ class MarkdownParser {
|
|
|
1442
1443
|
const lastChild = currentContainer.children.at(-1);
|
|
1443
1444
|
if (lastChild?.type === "paragraph" && !lastChild.isClosed) {
|
|
1444
1445
|
if (isLineEmpty(list.content) || list.kind === "ordered" && list.value !== 1) {
|
|
1445
|
-
lastChild.endLineIndex = lineIndex;
|
|
1446
1446
|
lastChild.lines.push(line);
|
|
1447
1447
|
break;
|
|
1448
1448
|
}
|
|
@@ -1461,10 +1461,8 @@ class MarkdownParser {
|
|
|
1461
1461
|
numOfColumns: list.numOfColumns,
|
|
1462
1462
|
children: [],
|
|
1463
1463
|
isClosed: false,
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
endLineIndex: lineIndex,
|
|
1467
|
-
isTight: true
|
|
1464
|
+
isTight: true,
|
|
1465
|
+
parent: currentContainer
|
|
1468
1466
|
};
|
|
1469
1467
|
addNode(parent);
|
|
1470
1468
|
}
|
|
@@ -1479,23 +1477,24 @@ class MarkdownParser {
|
|
|
1479
1477
|
marker: list.marker,
|
|
1480
1478
|
children: [],
|
|
1481
1479
|
isClosed: false,
|
|
1482
|
-
parent: currentContainer,
|
|
1483
1480
|
numOfColumns: list.numOfColumns,
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
isTight: true
|
|
1481
|
+
isTight: true,
|
|
1482
|
+
parent: currentContainer
|
|
1487
1483
|
};
|
|
1488
1484
|
addNode(parent);
|
|
1489
1485
|
}
|
|
1490
1486
|
}
|
|
1487
|
+
// If the last child of the parent list is marked as having a pending blank line, we mark this list as loose (i.e., not tight)
|
|
1488
|
+
if (parent.children.at(-1)?.hasPendingBlankLine) {
|
|
1489
|
+
parent.isTight = false;
|
|
1490
|
+
}
|
|
1491
1491
|
parent.numOfColumns = list.numOfColumns;
|
|
1492
1492
|
const item = {
|
|
1493
1493
|
type: "list-item",
|
|
1494
1494
|
children: [],
|
|
1495
1495
|
isClosed: false,
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
endLineIndex: lineIndex
|
|
1496
|
+
hasPendingBlankLine: false,
|
|
1497
|
+
parent: parent
|
|
1499
1498
|
};
|
|
1500
1499
|
addNode(item);
|
|
1501
1500
|
currentContainer = item;
|
|
@@ -1511,9 +1510,7 @@ class MarkdownParser {
|
|
|
1511
1510
|
sliceLeadingIndent(line, 4)
|
|
1512
1511
|
],
|
|
1513
1512
|
isClosed: false,
|
|
1514
|
-
parent: currentContainer
|
|
1515
|
-
startLineIndex: lineIndex,
|
|
1516
|
-
endLineIndex: lineIndex
|
|
1513
|
+
parent: currentContainer
|
|
1517
1514
|
});
|
|
1518
1515
|
break;
|
|
1519
1516
|
}
|
|
@@ -1523,7 +1520,6 @@ class MarkdownParser {
|
|
|
1523
1520
|
}
|
|
1524
1521
|
// Table continuation
|
|
1525
1522
|
if (lastChild?.type === "table" && !lastChild.isClosed) {
|
|
1526
|
-
lastChild.endLineIndex = lineIndex;
|
|
1527
1523
|
const numOfColumns = lastChild.head.cells.length;
|
|
1528
1524
|
let cells = parseTableRow(line);
|
|
1529
1525
|
if (cells.length > numOfColumns) {
|
|
@@ -1543,7 +1539,6 @@ class MarkdownParser {
|
|
|
1543
1539
|
}
|
|
1544
1540
|
// Paragraph
|
|
1545
1541
|
if (latestOpenNode?.type === "paragraph") {
|
|
1546
|
-
latestOpenNode.endLineIndex = lineIndex;
|
|
1547
1542
|
latestOpenNode.lines.push(line);
|
|
1548
1543
|
break;
|
|
1549
1544
|
} else {
|
|
@@ -1553,9 +1548,7 @@ class MarkdownParser {
|
|
|
1553
1548
|
line
|
|
1554
1549
|
],
|
|
1555
1550
|
isClosed: false,
|
|
1556
|
-
parent: currentContainer
|
|
1557
|
-
startLineIndex: lineIndex,
|
|
1558
|
-
endLineIndex: lineIndex
|
|
1551
|
+
parent: currentContainer
|
|
1559
1552
|
});
|
|
1560
1553
|
break;
|
|
1561
1554
|
}
|
|
@@ -1568,19 +1561,17 @@ class MarkdownParser {
|
|
|
1568
1561
|
// If a block is not closed/finalized yet, we do not parse reference link definitions for it
|
|
1569
1562
|
if (!block.isClosed) break;
|
|
1570
1563
|
if (block.type === "paragraph") {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
const { label, href, title } = definition.definition;
|
|
1564
|
+
const result = parseLinkReferenceDefinitions(block.lines);
|
|
1565
|
+
for (const definition of result.definitions){
|
|
1566
|
+
const { label, href, title } = definition;
|
|
1575
1567
|
if (!this.referenceDefinitions.has(label)) {
|
|
1576
1568
|
this.referenceDefinitions.set(label, {
|
|
1577
1569
|
href,
|
|
1578
1570
|
title
|
|
1579
1571
|
});
|
|
1580
1572
|
}
|
|
1581
|
-
lines = lines.slice(definition.nextLineIndex);
|
|
1582
|
-
definition = parseLinkReferenceDefinition(lines);
|
|
1583
1573
|
}
|
|
1574
|
+
const lines = block.lines.slice(result.nextLineIndex);
|
|
1584
1575
|
if (lines.length > 0) {
|
|
1585
1576
|
block.lines = lines;
|
|
1586
1577
|
} else {
|
|
@@ -1659,6 +1650,7 @@ class MarkdownParser {
|
|
|
1659
1650
|
case "list":
|
|
1660
1651
|
{
|
|
1661
1652
|
const items = block.children.map((item)=>({
|
|
1653
|
+
type: "list-item",
|
|
1662
1654
|
children: item.children.map((child)=>this.convertInternalBlockToPublicBlock(child))
|
|
1663
1655
|
}));
|
|
1664
1656
|
if (block.kind === "ordered") {
|
|
@@ -1705,6 +1697,106 @@ class MarkdownParser {
|
|
|
1705
1697
|
}
|
|
1706
1698
|
}
|
|
1707
1699
|
}
|
|
1700
|
+
get experimental_partialNodes() {
|
|
1701
|
+
return this.root.children.map((child)=>this.convertInternalBlockToPartialBlock(child)).filter((child)=>child !== null);
|
|
1702
|
+
}
|
|
1703
|
+
convertInternalBlockToPartialBlock(block) {
|
|
1704
|
+
switch(block.type){
|
|
1705
|
+
case "paragraph":
|
|
1706
|
+
{
|
|
1707
|
+
if (!block.isClosed) return null;
|
|
1708
|
+
return this.convertInternalBlockToPublicBlock(block);
|
|
1709
|
+
}
|
|
1710
|
+
case "heading":
|
|
1711
|
+
{
|
|
1712
|
+
return this.convertInternalBlockToPublicBlock(block);
|
|
1713
|
+
}
|
|
1714
|
+
case "thematic-break":
|
|
1715
|
+
{
|
|
1716
|
+
return this.convertInternalBlockToPublicBlock(block);
|
|
1717
|
+
}
|
|
1718
|
+
case "fenced-code-block":
|
|
1719
|
+
{
|
|
1720
|
+
let content = "";
|
|
1721
|
+
if (block.isClosed) {
|
|
1722
|
+
content = block.lines.length > 0 ? block.lines.join("\n") + "\n" : "";
|
|
1723
|
+
} else {
|
|
1724
|
+
content = block.lines.length > 0 ? block.lines.join("\n") : "";
|
|
1725
|
+
}
|
|
1726
|
+
return {
|
|
1727
|
+
type: "code-block",
|
|
1728
|
+
content: content,
|
|
1729
|
+
info: block.info
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
case "indented-code-block":
|
|
1733
|
+
{
|
|
1734
|
+
let startIndex = 0;
|
|
1735
|
+
let endIndex = block.lines.length - 1;
|
|
1736
|
+
while(startIndex < endIndex){
|
|
1737
|
+
const line = block.lines[startIndex];
|
|
1738
|
+
if (line === undefined) break;
|
|
1739
|
+
if (!isLineEmpty(line)) break;
|
|
1740
|
+
startIndex++;
|
|
1741
|
+
}
|
|
1742
|
+
while(endIndex > startIndex){
|
|
1743
|
+
const line = block.lines[endIndex];
|
|
1744
|
+
if (line === undefined) break;
|
|
1745
|
+
if (!isLineEmpty(line)) break;
|
|
1746
|
+
endIndex--;
|
|
1747
|
+
}
|
|
1748
|
+
let content = "";
|
|
1749
|
+
if (block.isClosed) {
|
|
1750
|
+
content = block.lines.slice(startIndex, endIndex + 1).join("\n") + "\n";
|
|
1751
|
+
} else {
|
|
1752
|
+
content = block.lines.slice(startIndex, endIndex + 1).join("\n");
|
|
1753
|
+
}
|
|
1754
|
+
return {
|
|
1755
|
+
type: "code-block",
|
|
1756
|
+
content: content
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
case "html-block":
|
|
1760
|
+
{
|
|
1761
|
+
return this.convertInternalBlockToPublicBlock(block);
|
|
1762
|
+
}
|
|
1763
|
+
case "table":
|
|
1764
|
+
{
|
|
1765
|
+
return this.convertInternalBlockToPublicBlock(block);
|
|
1766
|
+
}
|
|
1767
|
+
case "blockquote":
|
|
1768
|
+
{
|
|
1769
|
+
return {
|
|
1770
|
+
type: "blockquote",
|
|
1771
|
+
children: block.children.map((child)=>this.convertInternalBlockToPartialBlock(child)).filter((child)=>child !== null)
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
case "list":
|
|
1775
|
+
{
|
|
1776
|
+
const items = block.children.map((item)=>({
|
|
1777
|
+
type: "list-item",
|
|
1778
|
+
children: item.children.map((child)=>this.convertInternalBlockToPartialBlock(child)).filter((child)=>child !== null)
|
|
1779
|
+
}));
|
|
1780
|
+
if (block.kind === "ordered") {
|
|
1781
|
+
return {
|
|
1782
|
+
type: "list",
|
|
1783
|
+
kind: "ordered",
|
|
1784
|
+
start: block.start,
|
|
1785
|
+
tight: block.isTight,
|
|
1786
|
+
items: items
|
|
1787
|
+
};
|
|
1788
|
+
} else {
|
|
1789
|
+
return {
|
|
1790
|
+
type: "list",
|
|
1791
|
+
kind: "unordered",
|
|
1792
|
+
marker: block.marker,
|
|
1793
|
+
tight: block.isTight,
|
|
1794
|
+
items: items
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1708
1800
|
constructor(){
|
|
1709
1801
|
this.splitter = new LineSplitter();
|
|
1710
1802
|
this.root = {
|
|
@@ -1712,17 +1804,13 @@ class MarkdownParser {
|
|
|
1712
1804
|
children: [],
|
|
1713
1805
|
parent: null
|
|
1714
1806
|
};
|
|
1715
|
-
this.nextLineIndex = 0;
|
|
1716
1807
|
this.nextNodeIndex = 0;
|
|
1717
1808
|
this.referenceDefinitions = new Map();
|
|
1718
1809
|
}
|
|
1719
1810
|
}
|
|
1720
1811
|
function addNode(node) {
|
|
1721
|
-
// Ensure we don't have any dangling "open" nodes on the last-child chain before inserting a new sibling at this level.
|
|
1722
1812
|
closeRightmostPath(node.parent);
|
|
1723
|
-
// Insert the new node as the next child of the parent.
|
|
1724
1813
|
if (node.type === "list-item") {
|
|
1725
|
-
// This 'if' condition exists only for TypeScript's type narrowing purposes.
|
|
1726
1814
|
node.parent.children.push(node);
|
|
1727
1815
|
} else {
|
|
1728
1816
|
node.parent.children.push(node);
|
|
@@ -1738,61 +1826,20 @@ function addNode(node) {
|
|
|
1738
1826
|
* @param parent The parent/container node whose last-child chain will be traversed and closed.
|
|
1739
1827
|
* @param options.endLineIndex The end line index to update the nodes with.
|
|
1740
1828
|
*/ function closeRightmostPath(parent) {
|
|
1741
|
-
//
|
|
1742
|
-
//
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
if (
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
case "list":
|
|
1754
|
-
case "list-item":
|
|
1755
|
-
{
|
|
1756
|
-
// When closing a container node, we update the start and end line indices based on their first and last child's start and end line indices.
|
|
1757
|
-
const firstChild = currentNode.children.at(0);
|
|
1758
|
-
const lastChild = currentNode.children.at(-1);
|
|
1759
|
-
if (firstChild !== undefined) {
|
|
1760
|
-
currentNode.startLineIndex = Math.min(currentNode.startLineIndex, firstChild.startLineIndex);
|
|
1761
|
-
}
|
|
1762
|
-
if (lastChild !== undefined) {
|
|
1763
|
-
currentNode.endLineIndex = Math.max(currentNode.endLineIndex, lastChild.endLineIndex);
|
|
1764
|
-
}
|
|
1765
|
-
break;
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
if (currentNode.parent?.type === "root") break;
|
|
1769
|
-
currentNode = currentNode.parent;
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
function isListTight(list) {
|
|
1773
|
-
// 1) Check if there are gaps between list items
|
|
1774
|
-
const items = list.children;
|
|
1775
|
-
for(let i = 0; i < items.length - 1; i++){
|
|
1776
|
-
const a = items[i];
|
|
1777
|
-
const b = items[i + 1];
|
|
1778
|
-
if (a === undefined || b === undefined) break;
|
|
1779
|
-
if (a.endLineIndex < b.startLineIndex - 1) {
|
|
1780
|
-
return false;
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
// 2) Check if there are gaps between block children inside each item
|
|
1784
|
-
for (const item of items){
|
|
1785
|
-
const blocks = item.children;
|
|
1786
|
-
for(let j = 0; j < blocks.length - 1; j++){
|
|
1787
|
-
const a = blocks[j];
|
|
1788
|
-
const b = blocks[j + 1];
|
|
1789
|
-
if (a === undefined || b === undefined) break;
|
|
1790
|
-
if (a.endLineIndex < b.startLineIndex - 1) {
|
|
1791
|
-
return false;
|
|
1792
|
-
}
|
|
1829
|
+
// Walk down the rightmost/last-child chain and close any still-open nodes until we hit a closed one (or run out).
|
|
1830
|
+
// Start at the last child of the parent; we'll keep descending into the last child of certain container nodes (e.g., blockquotes).
|
|
1831
|
+
let child = parent.children.at(-1);
|
|
1832
|
+
while(child !== undefined){
|
|
1833
|
+
// If we've hit a node that is already closed, everything below it on this rightmost chain is assumed to be closed too, so we can stop early.
|
|
1834
|
+
if (child.isClosed) break;
|
|
1835
|
+
child.isClosed = true;
|
|
1836
|
+
// If the child is a container node, we continue descending into the last child of the container node.
|
|
1837
|
+
if (child.type === "blockquote" || child.type === "list" || child.type === "list-item") {
|
|
1838
|
+
child = child.children.at(-1);
|
|
1839
|
+
} else {
|
|
1840
|
+
break;
|
|
1793
1841
|
}
|
|
1794
1842
|
}
|
|
1795
|
-
return true;
|
|
1796
1843
|
}
|
|
1797
1844
|
function getDeepestOpenNodeOnRightmostPath(node) {
|
|
1798
1845
|
const child = node.children.at(-1);
|
|
@@ -2307,10 +2354,28 @@ function parseTableRow(line) {
|
|
|
2307
2354
|
}
|
|
2308
2355
|
return cells;
|
|
2309
2356
|
}
|
|
2310
|
-
function
|
|
2311
|
-
const
|
|
2357
|
+
function parseLinkReferenceDefinitions(lines) {
|
|
2358
|
+
const definitions = [];
|
|
2359
|
+
let nextLineIndex = 0;
|
|
2360
|
+
let definition = _parseLinkReferenceDefinition(lines, {
|
|
2361
|
+
startLineIndex: nextLineIndex
|
|
2362
|
+
});
|
|
2363
|
+
while(definition !== null){
|
|
2364
|
+
definitions.push(definition.definition);
|
|
2365
|
+
nextLineIndex = definition.nextLineIndex;
|
|
2366
|
+
definition = _parseLinkReferenceDefinition(lines, {
|
|
2367
|
+
startLineIndex: nextLineIndex
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
return {
|
|
2371
|
+
definitions,
|
|
2372
|
+
nextLineIndex
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
function _parseLinkReferenceDefinition(lines, options) {
|
|
2376
|
+
const firstLine = lines[options.startLineIndex];
|
|
2312
2377
|
if (firstLine === undefined) return null;
|
|
2313
|
-
let nextLineIndex = 1;
|
|
2378
|
+
let nextLineIndex = options.startLineIndex + 1;
|
|
2314
2379
|
let content = firstLine.trim() + "\n";
|
|
2315
2380
|
// A link reference definition must start with a left bracket '['
|
|
2316
2381
|
if (content.charAt(0) !== "[") return null;
|