@abraca/cli 2.23.0 → 2.25.0
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/abracadabra-cli.cjs +2310 -2155
- package/dist/abracadabra-cli.cjs.map +1 -1
- package/dist/abracadabra-cli.esm.js +2310 -2155
- package/dist/abracadabra-cli.esm.js.map +1 -1
- package/package.json +2 -2
package/dist/abracadabra-cli.cjs
CHANGED
|
@@ -1349,2351 +1349,2506 @@ registerCommand({
|
|
|
1349
1349
|
});
|
|
1350
1350
|
|
|
1351
1351
|
//#endregion
|
|
1352
|
-
//#region packages/convert/src/
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
"
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
type:
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
if (line.trim() === "") {
|
|
1629
|
-
let j = i + 1;
|
|
1630
|
-
while (j < lines.length && lines[j].trim() === "") j++;
|
|
1631
|
-
if (j >= lines.length) break;
|
|
1632
|
-
const lookahead = lines[j];
|
|
1633
|
-
if (leadingSpaces(lookahead) < indent) break;
|
|
1634
|
-
if (!matchMarker(lookahead.slice(indent), kind)) break;
|
|
1635
|
-
i = j;
|
|
1636
|
-
continue;
|
|
1637
|
-
}
|
|
1638
|
-
const leading = leadingSpaces(line);
|
|
1639
|
-
if (leading < indent) break;
|
|
1640
|
-
if (leading > indent) break;
|
|
1641
|
-
const m = matchMarker(line.slice(indent), kind);
|
|
1642
|
-
if (!m) break;
|
|
1643
|
-
const item = { text: m.text };
|
|
1644
|
-
if (kind === "task") item.checked = m.checked;
|
|
1645
|
-
i++;
|
|
1646
|
-
const contLines = [];
|
|
1647
|
-
while (i < lines.length) {
|
|
1648
|
-
const next = lines[i];
|
|
1649
|
-
if (next.trim() === "") {
|
|
1650
|
-
let k = i + 1;
|
|
1651
|
-
while (k < lines.length && lines[k].trim() === "") k++;
|
|
1652
|
-
if (k >= lines.length) break;
|
|
1653
|
-
if (leadingSpaces(lines[k]) <= indent) break;
|
|
1654
|
-
contLines.push("");
|
|
1655
|
-
i++;
|
|
1656
|
-
continue;
|
|
1657
|
-
}
|
|
1658
|
-
const nextIndent = leadingSpaces(next);
|
|
1659
|
-
if (nextIndent <= indent) break;
|
|
1660
|
-
const deindentBy = Math.min(nextIndent, indent + 2);
|
|
1661
|
-
contLines.push(next.slice(deindentBy));
|
|
1662
|
-
i++;
|
|
1663
|
-
}
|
|
1664
|
-
if (contLines.length > 0) item.innerBlocks = parseBlocks(contLines.join("\n"));
|
|
1665
|
-
items.push(item);
|
|
1666
|
-
}
|
|
1667
|
-
return {
|
|
1668
|
-
items,
|
|
1669
|
-
next: i
|
|
1670
|
-
};
|
|
1671
|
-
}
|
|
1672
|
-
function leadingSpaces(s) {
|
|
1673
|
-
let n = 0;
|
|
1674
|
-
while (n < s.length && s[n] === " ") n++;
|
|
1675
|
-
return n;
|
|
1676
|
-
}
|
|
1677
|
-
function matchMarker(s, kind) {
|
|
1678
|
-
if (kind === "task") {
|
|
1679
|
-
const m = s.match(TASK_RE);
|
|
1680
|
-
if (!m) return null;
|
|
1681
|
-
return {
|
|
1682
|
-
text: m[2],
|
|
1683
|
-
checked: m[1].toLowerCase() === "x"
|
|
1684
|
-
};
|
|
1685
|
-
}
|
|
1686
|
-
if (kind === "bullet") {
|
|
1687
|
-
if (TASK_RE.test(s)) return null;
|
|
1688
|
-
const m = s.match(/^[-*+]\s+(.*)$/);
|
|
1689
|
-
if (!m) return null;
|
|
1690
|
-
return {
|
|
1691
|
-
text: m[1],
|
|
1692
|
-
checked: false
|
|
1693
|
-
};
|
|
1694
|
-
}
|
|
1695
|
-
const m = s.match(/^\d+\.\s+(.*)$/);
|
|
1696
|
-
if (!m) return null;
|
|
1697
|
-
return {
|
|
1698
|
-
text: m[1],
|
|
1699
|
-
checked: false
|
|
1700
|
-
};
|
|
1701
|
-
}
|
|
1702
|
-
function parseBlocks(markdown) {
|
|
1703
|
-
const rawLines = markdown.split("\n");
|
|
1704
|
-
let firstContentLine = 0;
|
|
1705
|
-
while (firstContentLine < rawLines.length) {
|
|
1706
|
-
const l = rawLines[firstContentLine];
|
|
1707
|
-
if (l.trim() === "" || /^import\s/.test(l) || /^export\s/.test(l)) firstContentLine++;
|
|
1708
|
-
else break;
|
|
1709
|
-
}
|
|
1710
|
-
const stripped = rawLines.slice(firstContentLine).join("\n");
|
|
1711
|
-
const blocks = [];
|
|
1712
|
-
const lines = stripped.split("\n");
|
|
1713
|
-
let i = 0;
|
|
1714
|
-
while (i < lines.length) {
|
|
1715
|
-
const line = lines[i];
|
|
1716
|
-
const fenceBlockMatch = line.match(/^(`{3,})(.*)$/);
|
|
1717
|
-
if (fenceBlockMatch) {
|
|
1718
|
-
const fence = fenceBlockMatch[1];
|
|
1719
|
-
const lang = fenceBlockMatch[2].trim().replace(/\{[^}]*\}$/, "").replace(/\s*\[.*\]$/, "").trim();
|
|
1720
|
-
const codeLines = [];
|
|
1721
|
-
i++;
|
|
1722
|
-
while (i < lines.length && !lines[i].startsWith(fence)) {
|
|
1723
|
-
codeLines.push(lines[i]);
|
|
1724
|
-
i++;
|
|
1725
|
-
}
|
|
1726
|
-
i++;
|
|
1727
|
-
const code = codeLines.join("\n");
|
|
1728
|
-
if (lang === "math") blocks.push({
|
|
1729
|
-
type: "mathBlock",
|
|
1730
|
-
expression: code
|
|
1731
|
-
});
|
|
1732
|
-
else blocks.push({
|
|
1733
|
-
type: "codeBlock",
|
|
1734
|
-
lang,
|
|
1735
|
-
code
|
|
1736
|
-
});
|
|
1737
|
-
continue;
|
|
1738
|
-
}
|
|
1739
|
-
const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
|
|
1740
|
-
if (headingMatch) {
|
|
1741
|
-
blocks.push({
|
|
1742
|
-
type: "heading",
|
|
1743
|
-
level: headingMatch[1].length,
|
|
1744
|
-
text: headingMatch[2].trim()
|
|
1745
|
-
});
|
|
1746
|
-
i++;
|
|
1747
|
-
continue;
|
|
1748
|
-
}
|
|
1749
|
-
if (/^[-*_]{3,}\s*$/.test(line)) {
|
|
1750
|
-
blocks.push({ type: "hr" });
|
|
1751
|
-
i++;
|
|
1752
|
-
continue;
|
|
1753
|
-
}
|
|
1754
|
-
const embedMatch = line.match(/^!\[\[([0-9a-fA-F-]{36})(?:\|([^\]]+))?\]\](\{[^}]*\})?\s*$/);
|
|
1755
|
-
if (embedMatch) {
|
|
1756
|
-
const docId = embedMatch[1];
|
|
1757
|
-
const label = embedMatch[2] ?? "";
|
|
1758
|
-
const props = parseMdcProps(embedMatch[3]);
|
|
1759
|
-
blocks.push({
|
|
1760
|
-
type: "docEmbed",
|
|
1761
|
-
docId,
|
|
1762
|
-
label,
|
|
1763
|
-
props
|
|
1764
|
-
});
|
|
1765
|
-
i++;
|
|
1766
|
-
continue;
|
|
1767
|
-
}
|
|
1768
|
-
const imgMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)(\{[^}]*\})?\s*$/);
|
|
1769
|
-
if (imgMatch) {
|
|
1770
|
-
const alt = imgMatch[1] ?? "";
|
|
1771
|
-
const src = imgMatch[2] ?? "";
|
|
1772
|
-
const attrs = parseMdcProps(imgMatch[3]);
|
|
1773
|
-
blocks.push({
|
|
1774
|
-
type: "image",
|
|
1775
|
-
src,
|
|
1776
|
-
alt,
|
|
1777
|
-
width: attrs["width"],
|
|
1778
|
-
height: attrs["height"]
|
|
1779
|
-
});
|
|
1780
|
-
i++;
|
|
1781
|
-
continue;
|
|
1782
|
-
}
|
|
1783
|
-
if (line.startsWith(">")) {
|
|
1784
|
-
const bqLines = [];
|
|
1785
|
-
while (i < lines.length && lines[i].startsWith(">")) {
|
|
1786
|
-
bqLines.push(lines[i].replace(/^>\s?/, ""));
|
|
1787
|
-
i++;
|
|
1788
|
-
}
|
|
1789
|
-
blocks.push({
|
|
1790
|
-
type: "blockquote",
|
|
1791
|
-
lines: bqLines
|
|
1792
|
-
});
|
|
1793
|
-
continue;
|
|
1794
|
-
}
|
|
1795
|
-
if (/^\s*\|/.test(line)) {
|
|
1796
|
-
const tableLines = [];
|
|
1797
|
-
while (i < lines.length && /^\s*\|/.test(lines[i])) {
|
|
1798
|
-
tableLines.push(lines[i]);
|
|
1799
|
-
i++;
|
|
1800
|
-
}
|
|
1801
|
-
if (tableLines.length >= 2 && isTableSeparator(tableLines[1])) {
|
|
1802
|
-
const headerRow = parseTableRow(tableLines[0]);
|
|
1803
|
-
const dataRows = tableLines.slice(2).filter((l) => !isTableSeparator(l)).map(parseTableRow);
|
|
1804
|
-
blocks.push({
|
|
1805
|
-
type: "table",
|
|
1806
|
-
headerRow,
|
|
1807
|
-
dataRows
|
|
1808
|
-
});
|
|
1809
|
-
} else for (const l of tableLines) blocks.push({
|
|
1810
|
-
type: "paragraph",
|
|
1811
|
-
text: l
|
|
1812
|
-
});
|
|
1813
|
-
continue;
|
|
1814
|
-
}
|
|
1815
|
-
const atomMatch = line.match(/^:(\w[\w-]*)(\{[^}]*\})?\s*$/);
|
|
1816
|
-
if (atomMatch && atomMatch[1] === "file") {
|
|
1817
|
-
const props = parseMdcProps(atomMatch[2]);
|
|
1818
|
-
const uploadId = props["upload-id"] ?? props["uploadId"] ?? "";
|
|
1819
|
-
const filename = props["filename"] ?? "";
|
|
1820
|
-
const mime = props["mime"] ?? "";
|
|
1821
|
-
const src = props["src"] ?? (uploadId && filename ? `.abracadabra/files/${uploadId}-${filename}` : "");
|
|
1822
|
-
blocks.push({
|
|
1823
|
-
type: "fileBlock",
|
|
1824
|
-
src,
|
|
1825
|
-
mime,
|
|
1826
|
-
uploadId,
|
|
1827
|
-
filename
|
|
1828
|
-
});
|
|
1829
|
-
i++;
|
|
1830
|
-
continue;
|
|
1831
|
-
}
|
|
1832
|
-
const MDC_OPEN = /^\s*(:{2,})(\w[\w-]*)(\{[^}]*\})?\s*$/;
|
|
1833
|
-
if (MDC_OPEN.test(line)) {
|
|
1834
|
-
const colons = line.match(/^\s*(:+)/)?.[1]?.length ?? 2;
|
|
1835
|
-
const componentName = line.match(/^\s*:{2,}(\w[\w-]*)/)?.[1] ?? "";
|
|
1836
|
-
const innerLines = [];
|
|
1837
|
-
i++;
|
|
1838
|
-
while (i < lines.length) {
|
|
1839
|
-
const l = lines[i];
|
|
1840
|
-
if (new RegExp(`^\\s*:{${colons}}\\s*$`).test(l)) {
|
|
1841
|
-
i++;
|
|
1842
|
-
break;
|
|
1843
|
-
}
|
|
1844
|
-
const innerFence = l.match(/^(\s*`{3,})/);
|
|
1845
|
-
if (innerFence) {
|
|
1846
|
-
const fenceStr = innerFence[1].trimStart();
|
|
1847
|
-
innerLines.push(l);
|
|
1848
|
-
i++;
|
|
1849
|
-
while (i < lines.length && !lines[i].trimStart().startsWith(fenceStr)) {
|
|
1850
|
-
innerLines.push(lines[i]);
|
|
1851
|
-
i++;
|
|
1852
|
-
}
|
|
1853
|
-
if (i < lines.length) {
|
|
1854
|
-
innerLines.push(lines[i]);
|
|
1855
|
-
i++;
|
|
1856
|
-
}
|
|
1857
|
-
continue;
|
|
1858
|
-
}
|
|
1859
|
-
innerLines.push(l);
|
|
1860
|
-
i++;
|
|
1861
|
-
}
|
|
1862
|
-
const nonBlank = innerLines.filter((l) => l.trim().length > 0);
|
|
1863
|
-
if (nonBlank.length) {
|
|
1864
|
-
const minIndent = Math.min(...nonBlank.map((l) => l.match(/^(\s*)/)?.[1]?.length ?? 0));
|
|
1865
|
-
if (minIndent > 0) for (let j = 0; j < innerLines.length; j++) innerLines[j] = innerLines[j].slice(Math.min(minIndent, innerLines[j].length));
|
|
1866
|
-
}
|
|
1867
|
-
let contentStart = 0;
|
|
1868
|
-
if (innerLines[0]?.trim() === "---") {
|
|
1869
|
-
const fmEnd = innerLines.findIndex((l, idx) => idx > 0 && l.trim() === "---");
|
|
1870
|
-
if (fmEnd !== -1) contentStart = fmEnd + 1;
|
|
1871
|
-
}
|
|
1872
|
-
const contentLines = innerLines.slice(contentStart);
|
|
1873
|
-
const defaultSlotLines = [];
|
|
1874
|
-
const codeSlotLines = [];
|
|
1875
|
-
let currentSlot = "default";
|
|
1876
|
-
for (const l of contentLines) {
|
|
1877
|
-
if (/^#code\s*$/.test(l)) {
|
|
1878
|
-
currentSlot = "code";
|
|
1879
|
-
continue;
|
|
1880
|
-
}
|
|
1881
|
-
if (/^#\w+/.test(l) && !/^#{2,}\s/.test(l)) {
|
|
1882
|
-
currentSlot = "other";
|
|
1883
|
-
continue;
|
|
1884
|
-
}
|
|
1885
|
-
if (currentSlot === "default") defaultSlotLines.push(l);
|
|
1886
|
-
else if (currentSlot === "code") codeSlotLines.push(l);
|
|
1887
|
-
}
|
|
1888
|
-
const innerBlocks = parseBlocks(defaultSlotLines.join("\n"));
|
|
1889
|
-
const codeBlocks = extractFencedCode(codeSlotLines);
|
|
1890
|
-
if (new Set([
|
|
1891
|
-
"tip",
|
|
1892
|
-
"note",
|
|
1893
|
-
"info",
|
|
1894
|
-
"warning",
|
|
1895
|
-
"caution",
|
|
1896
|
-
"danger",
|
|
1897
|
-
"callout",
|
|
1898
|
-
"alert"
|
|
1899
|
-
]).has(componentName.toLowerCase())) blocks.push({
|
|
1900
|
-
type: "callout",
|
|
1901
|
-
calloutType: componentName.toLowerCase(),
|
|
1902
|
-
innerBlocks
|
|
1903
|
-
});
|
|
1904
|
-
else {
|
|
1905
|
-
const mdcProps = parseMdcProps(line.match(MDC_OPEN)?.[3]);
|
|
1906
|
-
const lc = componentName.toLowerCase();
|
|
1907
|
-
if (lc === "collapsible") blocks.push({
|
|
1908
|
-
type: "collapsible",
|
|
1909
|
-
label: mdcProps["label"] || "Details",
|
|
1910
|
-
open: mdcProps["open"] === "true",
|
|
1911
|
-
innerBlocks
|
|
1912
|
-
});
|
|
1913
|
-
else if (lc === "steps") blocks.push({
|
|
1914
|
-
type: "steps",
|
|
1915
|
-
innerBlocks
|
|
1916
|
-
});
|
|
1917
|
-
else if (lc === "card") blocks.push({
|
|
1918
|
-
type: "card",
|
|
1919
|
-
title: mdcProps["title"] || "",
|
|
1920
|
-
icon: mdcProps["icon"] || "",
|
|
1921
|
-
to: mdcProps["to"] || "",
|
|
1922
|
-
innerBlocks
|
|
1923
|
-
});
|
|
1924
|
-
else if (lc === "card-group") {
|
|
1925
|
-
const cards = innerBlocks.filter((b) => b.type === "card");
|
|
1926
|
-
if (cards.length) blocks.push({
|
|
1927
|
-
type: "cardGroup",
|
|
1928
|
-
cards
|
|
1929
|
-
});
|
|
1930
|
-
else blocks.push(...innerBlocks);
|
|
1931
|
-
} else if (lc === "code-collapse") blocks.push({
|
|
1932
|
-
type: "codeCollapse",
|
|
1933
|
-
codeBlocks: codeBlocks.length ? codeBlocks : innerBlocks.filter((b) => b.type === "codeBlock")
|
|
1934
|
-
});
|
|
1935
|
-
else if (lc === "code-group") {
|
|
1936
|
-
const allCode = [...innerBlocks.filter((b) => b.type === "codeBlock"), ...codeBlocks];
|
|
1937
|
-
blocks.push({
|
|
1938
|
-
type: "codeGroup",
|
|
1939
|
-
codeBlocks: allCode
|
|
1940
|
-
});
|
|
1941
|
-
} else if (lc === "code-preview") blocks.push({
|
|
1942
|
-
type: "codePreview",
|
|
1943
|
-
innerBlocks,
|
|
1944
|
-
codeBlocks
|
|
1945
|
-
});
|
|
1946
|
-
else if (lc === "code-tree") blocks.push({
|
|
1947
|
-
type: "codeTree",
|
|
1948
|
-
files: mdcProps["files"] || "[]"
|
|
1949
|
-
});
|
|
1950
|
-
else if (lc === "accordion") {
|
|
1951
|
-
const items = parseMdcChildren(contentLines, "item");
|
|
1952
|
-
if (items.length) blocks.push({
|
|
1953
|
-
type: "accordion",
|
|
1954
|
-
items
|
|
1955
|
-
});
|
|
1956
|
-
else blocks.push({
|
|
1957
|
-
type: "accordion",
|
|
1958
|
-
items: [{
|
|
1959
|
-
label: "Item 1",
|
|
1960
|
-
icon: "",
|
|
1961
|
-
innerBlocks
|
|
1962
|
-
}]
|
|
1963
|
-
});
|
|
1964
|
-
} else if (lc === "tabs") {
|
|
1965
|
-
const items = parseMdcChildren(contentLines, "tab");
|
|
1966
|
-
if (items.length) blocks.push({
|
|
1967
|
-
type: "tabs",
|
|
1968
|
-
items
|
|
1969
|
-
});
|
|
1970
|
-
else blocks.push({
|
|
1971
|
-
type: "tabs",
|
|
1972
|
-
items: [{
|
|
1973
|
-
label: "Tab 1",
|
|
1974
|
-
icon: "",
|
|
1975
|
-
innerBlocks
|
|
1976
|
-
}]
|
|
1977
|
-
});
|
|
1978
|
-
} else if (lc === "field") blocks.push({
|
|
1979
|
-
type: "field",
|
|
1980
|
-
name: mdcProps["name"] || "",
|
|
1981
|
-
fieldType: mdcProps["type"] || "string",
|
|
1982
|
-
required: mdcProps["required"] === "true",
|
|
1983
|
-
innerBlocks
|
|
1984
|
-
});
|
|
1985
|
-
else if (lc === "field-group") {
|
|
1986
|
-
const fields = innerBlocks.filter((b) => b.type === "field");
|
|
1987
|
-
if (fields.length) blocks.push({
|
|
1988
|
-
type: "fieldGroup",
|
|
1989
|
-
fields
|
|
1990
|
-
});
|
|
1991
|
-
else blocks.push(...innerBlocks);
|
|
1992
|
-
} else {
|
|
1993
|
-
blocks.push(...innerBlocks);
|
|
1994
|
-
blocks.push(...codeBlocks);
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
continue;
|
|
1998
|
-
}
|
|
1999
|
-
if (TASK_RE.test(line)) {
|
|
2000
|
-
const { items, next } = consumeList(lines, i, 0, "task");
|
|
2001
|
-
i = next;
|
|
2002
|
-
blocks.push({
|
|
2003
|
-
type: "taskList",
|
|
2004
|
-
items
|
|
2005
|
-
});
|
|
2006
|
-
continue;
|
|
2007
|
-
}
|
|
2008
|
-
if (/^[-*+]\s+/.test(line)) {
|
|
2009
|
-
const { items, next } = consumeList(lines, i, 0, "bullet");
|
|
2010
|
-
if (items.length > 0) {
|
|
2011
|
-
i = next;
|
|
2012
|
-
blocks.push({
|
|
2013
|
-
type: "bulletList",
|
|
2014
|
-
items
|
|
2015
|
-
});
|
|
2016
|
-
continue;
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
if (/^\d+\.\s+/.test(line)) {
|
|
2020
|
-
const { items, next } = consumeList(lines, i, 0, "ordered");
|
|
2021
|
-
if (items.length > 0) {
|
|
2022
|
-
i = next;
|
|
2023
|
-
blocks.push({
|
|
2024
|
-
type: "orderedList",
|
|
2025
|
-
items
|
|
2026
|
-
});
|
|
2027
|
-
continue;
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
if (line.trim() === "") {
|
|
2031
|
-
i++;
|
|
2032
|
-
continue;
|
|
2033
|
-
}
|
|
2034
|
-
const paraLines = [];
|
|
2035
|
-
while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6}\s|[-*+]\s|\d+\.\s|>|`{3,}|\s*\||[-*_]{3,}\s*$|\s*:{2,}\w)/.test(lines[i])) {
|
|
2036
|
-
paraLines.push(lines[i]);
|
|
2037
|
-
i++;
|
|
2038
|
-
}
|
|
2039
|
-
if (paraLines.length) blocks.push({
|
|
2040
|
-
type: "paragraph",
|
|
2041
|
-
text: paraLines.join(" ")
|
|
2042
|
-
});
|
|
2043
|
-
else {
|
|
2044
|
-
blocks.push({
|
|
2045
|
-
type: "paragraph",
|
|
2046
|
-
text: line
|
|
2047
|
-
});
|
|
2048
|
-
i++;
|
|
2049
|
-
}
|
|
1352
|
+
//#region packages/convert/src/spec/universal-meta.ts
|
|
1353
|
+
const UNIVERSAL_META_KEYS = [
|
|
1354
|
+
{
|
|
1355
|
+
key: "title",
|
|
1356
|
+
type: "string",
|
|
1357
|
+
doc: "Display title; the first H1 is hoisted into this field on import."
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
key: "type",
|
|
1361
|
+
type: "string",
|
|
1362
|
+
doc: "Page type (doc, kanban, table, …). Omitted on serialise when \"doc\"."
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
key: "color",
|
|
1366
|
+
type: "string",
|
|
1367
|
+
doc: "Hex or CSS color name."
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
key: "icon",
|
|
1371
|
+
type: "string",
|
|
1372
|
+
doc: "Lucide icon name in kebab-case."
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
key: "datetimeStart",
|
|
1376
|
+
type: "iso-datetime"
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
key: "datetimeEnd",
|
|
1380
|
+
type: "iso-datetime"
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
key: "allDay",
|
|
1384
|
+
type: "boolean"
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
key: "dateTaken",
|
|
1388
|
+
type: "iso-datetime"
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
key: "dateStart",
|
|
1392
|
+
type: "iso-date",
|
|
1393
|
+
parseAliases: ["date", "created"]
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
key: "dateEnd",
|
|
1397
|
+
type: "iso-date",
|
|
1398
|
+
parseAliases: ["due"]
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
key: "timeStart",
|
|
1402
|
+
type: "hh-mm"
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
key: "timeEnd",
|
|
1406
|
+
type: "hh-mm"
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
key: "tags",
|
|
1410
|
+
type: "string[]"
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
key: "checked",
|
|
1414
|
+
type: "boolean",
|
|
1415
|
+
parseAliases: ["done"]
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
key: "priority",
|
|
1419
|
+
type: "integer",
|
|
1420
|
+
min: 0,
|
|
1421
|
+
max: 4,
|
|
1422
|
+
doc: "Numeric or named (low/medium/high/urgent → 1/2/3/4)."
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
key: "status",
|
|
1426
|
+
type: "string"
|
|
1427
|
+
},
|
|
1428
|
+
{
|
|
1429
|
+
key: "rating",
|
|
1430
|
+
type: "number",
|
|
1431
|
+
min: 0,
|
|
1432
|
+
max: 5
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
key: "url",
|
|
1436
|
+
type: "string"
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
key: "email",
|
|
1440
|
+
type: "string"
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
key: "phone",
|
|
1444
|
+
type: "string"
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
key: "number",
|
|
1448
|
+
type: "number"
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
key: "unit",
|
|
1452
|
+
type: "string"
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
key: "subtitle",
|
|
1456
|
+
type: "string",
|
|
1457
|
+
parseAliases: ["description"]
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
key: "note",
|
|
1461
|
+
type: "string"
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
key: "taskProgress",
|
|
1465
|
+
type: "integer",
|
|
1466
|
+
min: 0,
|
|
1467
|
+
max: 100
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
key: "members",
|
|
1471
|
+
type: "members"
|
|
1472
|
+
},
|
|
1473
|
+
{
|
|
1474
|
+
key: "coverUploadId",
|
|
1475
|
+
type: "string"
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
key: "coverDocId",
|
|
1479
|
+
type: "string"
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
key: "coverMimeType",
|
|
1483
|
+
type: "string"
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
key: "geoType",
|
|
1487
|
+
type: "string-enum",
|
|
1488
|
+
values: [
|
|
1489
|
+
"marker",
|
|
1490
|
+
"line",
|
|
1491
|
+
"measure"
|
|
1492
|
+
]
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
key: "geoLat",
|
|
1496
|
+
type: "number"
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
key: "geoLng",
|
|
1500
|
+
type: "number"
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
key: "geoDescription",
|
|
1504
|
+
type: "string"
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
key: "deskX",
|
|
1508
|
+
type: "number"
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
key: "deskY",
|
|
1512
|
+
type: "number"
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
key: "deskZ",
|
|
1516
|
+
type: "number"
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
key: "deskMode",
|
|
1520
|
+
type: "string-enum",
|
|
1521
|
+
values: [
|
|
1522
|
+
"icon",
|
|
1523
|
+
"widget-sm",
|
|
1524
|
+
"widget-lg"
|
|
1525
|
+
]
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
key: "mmX",
|
|
1529
|
+
type: "number"
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
key: "mmY",
|
|
1533
|
+
type: "number"
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
key: "graphX",
|
|
1537
|
+
type: "number"
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
key: "graphY",
|
|
1541
|
+
type: "number"
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
key: "graphPinned",
|
|
1545
|
+
type: "boolean"
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
key: "spX",
|
|
1549
|
+
type: "number"
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
key: "spY",
|
|
1553
|
+
type: "number"
|
|
1554
|
+
},
|
|
1555
|
+
{
|
|
1556
|
+
key: "spZ",
|
|
1557
|
+
type: "number"
|
|
1558
|
+
},
|
|
1559
|
+
{
|
|
1560
|
+
key: "spRX",
|
|
1561
|
+
type: "number"
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
key: "spRY",
|
|
1565
|
+
type: "number"
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
key: "spRZ",
|
|
1569
|
+
type: "number"
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
key: "spSX",
|
|
1573
|
+
type: "number"
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
key: "spSY",
|
|
1577
|
+
type: "number"
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
key: "spSZ",
|
|
1581
|
+
type: "number"
|
|
1582
|
+
},
|
|
1583
|
+
{
|
|
1584
|
+
key: "spShape",
|
|
1585
|
+
type: "string-enum",
|
|
1586
|
+
values: [
|
|
1587
|
+
"box",
|
|
1588
|
+
"sphere",
|
|
1589
|
+
"cylinder",
|
|
1590
|
+
"cone",
|
|
1591
|
+
"plane",
|
|
1592
|
+
"torus",
|
|
1593
|
+
"glb"
|
|
1594
|
+
]
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
key: "spOpacity",
|
|
1598
|
+
type: "integer",
|
|
1599
|
+
min: 0,
|
|
1600
|
+
max: 100
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
key: "spModelUploadId",
|
|
1604
|
+
type: "string"
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
key: "spModelDocId",
|
|
1608
|
+
type: "string"
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
key: "slidesTransition",
|
|
1612
|
+
type: "string-enum",
|
|
1613
|
+
values: [
|
|
1614
|
+
"none",
|
|
1615
|
+
"fade",
|
|
1616
|
+
"slide"
|
|
1617
|
+
]
|
|
1618
|
+
},
|
|
1619
|
+
{
|
|
1620
|
+
key: "slidesTheme",
|
|
1621
|
+
type: "string-enum",
|
|
1622
|
+
values: ["dark", "light"]
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
key: "__schemaVersion",
|
|
1626
|
+
type: "integer",
|
|
1627
|
+
min: 0
|
|
2050
1628
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
1629
|
+
];
|
|
1630
|
+
const UNIVERSAL_META_KEY_NAMES = new Set(UNIVERSAL_META_KEYS.map((k) => k.key));
|
|
2053
1631
|
/**
|
|
2054
|
-
*
|
|
2055
|
-
*
|
|
1632
|
+
* Build a map of every recognised input key (canonical + aliases) to
|
|
1633
|
+
* its canonical key. Used by the frontmatter parser.
|
|
2056
1634
|
*/
|
|
2057
|
-
function
|
|
2058
|
-
const
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
});
|
|
2063
|
-
el.insert(0, children);
|
|
2064
|
-
filtered.forEach((tok, i) => {
|
|
2065
|
-
const node = children[i];
|
|
2066
|
-
if (node instanceof yjs.XmlElement) {
|
|
2067
|
-
const dl = tok.attrs.docLink;
|
|
2068
|
-
node.setAttribute("docId", dl.docId);
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
if (tok.attrs) node.insert(0, tok.text, tok.attrs);
|
|
2072
|
-
else node.insert(0, tok.text);
|
|
2073
|
-
});
|
|
2074
|
-
}
|
|
2075
|
-
function blockElName(b) {
|
|
2076
|
-
switch (b.type) {
|
|
2077
|
-
case "heading": return "heading";
|
|
2078
|
-
case "paragraph": return "paragraph";
|
|
2079
|
-
case "bulletList": return "bulletList";
|
|
2080
|
-
case "orderedList": return "orderedList";
|
|
2081
|
-
case "taskList": return "taskList";
|
|
2082
|
-
case "codeBlock": return "codeBlock";
|
|
2083
|
-
case "blockquote": return "blockquote";
|
|
2084
|
-
case "table": return "table";
|
|
2085
|
-
case "hr": return "horizontalRule";
|
|
2086
|
-
case "callout": return "callout";
|
|
2087
|
-
case "collapsible": return "collapsible";
|
|
2088
|
-
case "steps": return "steps";
|
|
2089
|
-
case "card": return "card";
|
|
2090
|
-
case "cardGroup": return "cardGroup";
|
|
2091
|
-
case "codeCollapse": return "codeCollapse";
|
|
2092
|
-
case "codeGroup": return "codeGroup";
|
|
2093
|
-
case "codePreview": return "codePreview";
|
|
2094
|
-
case "codeTree": return "codeTree";
|
|
2095
|
-
case "accordion": return "accordion";
|
|
2096
|
-
case "tabs": return "tabs";
|
|
2097
|
-
case "field": return "field";
|
|
2098
|
-
case "fieldGroup": return "fieldGroup";
|
|
2099
|
-
case "image": return "image";
|
|
2100
|
-
case "docEmbed": return "docEmbed";
|
|
2101
|
-
case "mathBlock": return "mathBlock";
|
|
2102
|
-
case "fileBlock": return "fileBlock";
|
|
1635
|
+
function buildAliasMap() {
|
|
1636
|
+
const map = /* @__PURE__ */ new Map();
|
|
1637
|
+
for (const entry of UNIVERSAL_META_KEYS) {
|
|
1638
|
+
map.set(entry.key, entry.key);
|
|
1639
|
+
for (const alias of entry.parseAliases ?? []) map.set(alias, entry.key);
|
|
2103
1640
|
}
|
|
1641
|
+
return map;
|
|
2104
1642
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
const innerEls = item.innerBlocks.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2111
|
-
itemEl.insert(itemEl.length, innerEls);
|
|
2112
|
-
item.innerBlocks.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
1643
|
+
|
|
1644
|
+
//#endregion
|
|
1645
|
+
//#region packages/convert/src/markdown-to-yjs.ts
|
|
1646
|
+
function parseInlineArray(raw) {
|
|
1647
|
+
return raw.slice(1, -1).split(",").map((s) => s.trim()).filter(Boolean);
|
|
2113
1648
|
}
|
|
2114
|
-
function
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
}
|
|
2141
|
-
case "codeBlock": {
|
|
2142
|
-
if (block.lang) el.setAttribute("language", block.lang);
|
|
2143
|
-
const xt = new yjs.XmlText();
|
|
2144
|
-
el.insert(0, [xt]);
|
|
2145
|
-
xt.insert(0, block.code);
|
|
2146
|
-
break;
|
|
2147
|
-
}
|
|
2148
|
-
case "blockquote": {
|
|
2149
|
-
const paraEls = block.lines.map(() => new yjs.XmlElement("paragraph"));
|
|
2150
|
-
el.insert(0, paraEls);
|
|
2151
|
-
block.lines.forEach((line, i) => fillTextInto(paraEls[i], parseInline(line)));
|
|
2152
|
-
break;
|
|
2153
|
-
}
|
|
2154
|
-
case "table": {
|
|
2155
|
-
const headerRowEl = new yjs.XmlElement("tableRow");
|
|
2156
|
-
const dataRowEls = block.dataRows.map(() => new yjs.XmlElement("tableRow"));
|
|
2157
|
-
el.insert(0, [headerRowEl, ...dataRowEls]);
|
|
2158
|
-
const headerCellEls = block.headerRow.map(() => new yjs.XmlElement("tableHeader"));
|
|
2159
|
-
headerRowEl.insert(0, headerCellEls);
|
|
2160
|
-
block.headerRow.forEach((cellText, i) => {
|
|
2161
|
-
const paraEl = new yjs.XmlElement("paragraph");
|
|
2162
|
-
headerCellEls[i].insert(0, [paraEl]);
|
|
2163
|
-
fillTextInto(paraEl, parseInline(cellText));
|
|
2164
|
-
});
|
|
2165
|
-
block.dataRows.forEach((row, ri) => {
|
|
2166
|
-
const cellEls = row.map(() => new yjs.XmlElement("tableCell"));
|
|
2167
|
-
dataRowEls[ri].insert(0, cellEls);
|
|
2168
|
-
row.forEach((cellText, ci) => {
|
|
2169
|
-
const paraEl = new yjs.XmlElement("paragraph");
|
|
2170
|
-
cellEls[ci].insert(0, [paraEl]);
|
|
2171
|
-
fillTextInto(paraEl, parseInline(cellText));
|
|
2172
|
-
});
|
|
2173
|
-
});
|
|
2174
|
-
break;
|
|
2175
|
-
}
|
|
2176
|
-
case "hr": break;
|
|
2177
|
-
case "callout": {
|
|
2178
|
-
el.setAttribute("type", block.calloutType);
|
|
2179
|
-
if (!block.innerBlocks.length) {
|
|
2180
|
-
const paraEl = new yjs.XmlElement("paragraph");
|
|
2181
|
-
el.insert(0, [paraEl]);
|
|
2182
|
-
break;
|
|
1649
|
+
function stripQuotes(s) {
|
|
1650
|
+
if (s.length >= 2 && (s.startsWith("\"") && s.endsWith("\"") || s.startsWith("'") && s.endsWith("'"))) return s.slice(1, -1).replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
1651
|
+
return s;
|
|
1652
|
+
}
|
|
1653
|
+
function parseFrontmatter(markdown) {
|
|
1654
|
+
const noResult = {
|
|
1655
|
+
meta: {},
|
|
1656
|
+
body: markdown
|
|
1657
|
+
};
|
|
1658
|
+
const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
1659
|
+
if (!match) return noResult;
|
|
1660
|
+
const yamlBlock = match[1];
|
|
1661
|
+
const body = markdown.slice(match[0].length);
|
|
1662
|
+
const raw = {};
|
|
1663
|
+
const lines = yamlBlock.split("\n");
|
|
1664
|
+
let i = 0;
|
|
1665
|
+
while (i < lines.length) {
|
|
1666
|
+
const line = lines[i];
|
|
1667
|
+
const blockSeqKey = line.match(/^(\w[\w-]*):\s*$/);
|
|
1668
|
+
if (blockSeqKey && i + 1 < lines.length && /^\s+-\s/.test(lines[i + 1])) {
|
|
1669
|
+
const key = blockSeqKey[1];
|
|
1670
|
+
const items = [];
|
|
1671
|
+
i++;
|
|
1672
|
+
while (i < lines.length && /^\s+-\s/.test(lines[i])) {
|
|
1673
|
+
items.push(lines[i].replace(/^\s+-\s/, "").trim());
|
|
1674
|
+
i++;
|
|
2183
1675
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
block.innerBlocks.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2187
|
-
break;
|
|
2188
|
-
}
|
|
2189
|
-
case "collapsible": {
|
|
2190
|
-
el.setAttribute("label", block.label);
|
|
2191
|
-
el.setAttribute("open", block.open);
|
|
2192
|
-
const inner = block.innerBlocks.length ? block.innerBlocks : [{
|
|
2193
|
-
type: "paragraph",
|
|
2194
|
-
text: ""
|
|
2195
|
-
}];
|
|
2196
|
-
const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2197
|
-
el.insert(0, innerEls);
|
|
2198
|
-
inner.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2199
|
-
break;
|
|
1676
|
+
raw[key] = items;
|
|
1677
|
+
continue;
|
|
2200
1678
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
el.insert(0, innerEls);
|
|
2208
|
-
inner.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2209
|
-
break;
|
|
1679
|
+
const kvMatch = line.match(/^(\w[\w-]*):\s*(.*)$/);
|
|
1680
|
+
if (kvMatch) {
|
|
1681
|
+
const key = kvMatch[1];
|
|
1682
|
+
const val = kvMatch[2].trim();
|
|
1683
|
+
if (val.startsWith("[") && val.endsWith("]")) raw[key] = parseInlineArray(val).map(stripQuotes);
|
|
1684
|
+
else raw[key] = stripQuotes(val);
|
|
2210
1685
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
}];
|
|
2219
|
-
const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2220
|
-
el.insert(0, innerEls);
|
|
2221
|
-
inner.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2222
|
-
break;
|
|
1686
|
+
i++;
|
|
1687
|
+
}
|
|
1688
|
+
const meta = {};
|
|
1689
|
+
const getStr = (keys) => {
|
|
1690
|
+
for (const k of keys) {
|
|
1691
|
+
const v = raw[k];
|
|
1692
|
+
if (typeof v === "string" && v) return v;
|
|
2223
1693
|
}
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
1694
|
+
};
|
|
1695
|
+
if (raw["tags"]) meta.tags = Array.isArray(raw["tags"]) ? raw["tags"] : [raw["tags"]];
|
|
1696
|
+
const color = getStr(["color"]);
|
|
1697
|
+
if (color) meta.color = color;
|
|
1698
|
+
const icon = getStr(["icon"]);
|
|
1699
|
+
if (icon) meta.icon = icon;
|
|
1700
|
+
const status = getStr(["status"]);
|
|
1701
|
+
if (status) meta.status = status;
|
|
1702
|
+
const priorityRaw = getStr(["priority"]);
|
|
1703
|
+
if (priorityRaw !== void 0) meta.priority = {
|
|
1704
|
+
low: 1,
|
|
1705
|
+
medium: 2,
|
|
1706
|
+
high: 3,
|
|
1707
|
+
urgent: 4
|
|
1708
|
+
}[priorityRaw.toLowerCase()] ?? (Number(priorityRaw) || 0);
|
|
1709
|
+
const checkedRaw = raw["checked"] ?? raw["done"];
|
|
1710
|
+
if (checkedRaw !== void 0) meta.checked = checkedRaw === "true" || checkedRaw === true;
|
|
1711
|
+
const dateStart = getStr([
|
|
1712
|
+
"dateStart",
|
|
1713
|
+
"date",
|
|
1714
|
+
"created"
|
|
1715
|
+
]);
|
|
1716
|
+
if (dateStart) meta.dateStart = dateStart;
|
|
1717
|
+
const dateEnd = getStr(["dateEnd", "due"]);
|
|
1718
|
+
if (dateEnd) meta.dateEnd = dateEnd;
|
|
1719
|
+
const subtitle = getStr(["subtitle", "description"]);
|
|
1720
|
+
if (subtitle) meta.subtitle = subtitle;
|
|
1721
|
+
const url = getStr(["url"]);
|
|
1722
|
+
if (url) meta.url = url;
|
|
1723
|
+
const language = getStr(["language"]);
|
|
1724
|
+
if (language) meta.language = language;
|
|
1725
|
+
const fileExtension = getStr(["fileExtension"]);
|
|
1726
|
+
if (fileExtension) meta.fileExtension = fileExtension;
|
|
1727
|
+
const codeTheme = getStr(["codeTheme"]);
|
|
1728
|
+
if (codeTheme) meta.codeTheme = codeTheme;
|
|
1729
|
+
const ratingRaw = getStr(["rating"]);
|
|
1730
|
+
if (ratingRaw !== void 0) {
|
|
1731
|
+
const n = Number(ratingRaw);
|
|
1732
|
+
if (!Number.isNaN(n)) meta.rating = Math.min(5, Math.max(0, n));
|
|
1733
|
+
}
|
|
1734
|
+
for (const [rawKey, rawVal] of Object.entries(raw)) {
|
|
1735
|
+
if (CONSUMED_FM_KEYS.has(rawKey)) continue;
|
|
1736
|
+
if (rawKey.startsWith("_")) continue;
|
|
1737
|
+
const canonical = FM_ALIAS_MAP.get(rawKey);
|
|
1738
|
+
if (canonical) {
|
|
1739
|
+
if (canonical === "title" || canonical === "type") continue;
|
|
1740
|
+
if (meta[canonical] !== void 0) continue;
|
|
1741
|
+
const coerced = coerceMetaValue(rawVal, FM_SPEC_BY_KEY.get(canonical)?.type);
|
|
1742
|
+
if (coerced !== void 0) meta[canonical] = coerced;
|
|
1743
|
+
} else {
|
|
1744
|
+
const coerced = coerceMetaValue(rawVal, void 0);
|
|
1745
|
+
if (coerced !== void 0) meta[rawKey] = coerced;
|
|
2229
1746
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
1747
|
+
}
|
|
1748
|
+
const rawTitle = typeof raw["title"] === "string" ? raw["title"] : void 0;
|
|
1749
|
+
return {
|
|
1750
|
+
title: rawTitle !== void 0 ? stripQuotes(rawTitle) : void 0,
|
|
1751
|
+
type: getStr(["type"]),
|
|
1752
|
+
meta,
|
|
1753
|
+
body
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
/** Raw YAML keys the hand-rolled section of parseFrontmatter consumes. */
|
|
1757
|
+
const CONSUMED_FM_KEYS = new Set([
|
|
1758
|
+
"title",
|
|
1759
|
+
"type",
|
|
1760
|
+
"tags",
|
|
1761
|
+
"color",
|
|
1762
|
+
"icon",
|
|
1763
|
+
"status",
|
|
1764
|
+
"priority",
|
|
1765
|
+
"checked",
|
|
1766
|
+
"done",
|
|
1767
|
+
"dateStart",
|
|
1768
|
+
"date",
|
|
1769
|
+
"created",
|
|
1770
|
+
"dateEnd",
|
|
1771
|
+
"due",
|
|
1772
|
+
"subtitle",
|
|
1773
|
+
"description",
|
|
1774
|
+
"url",
|
|
1775
|
+
"language",
|
|
1776
|
+
"fileExtension",
|
|
1777
|
+
"codeTheme",
|
|
1778
|
+
"rating"
|
|
1779
|
+
]);
|
|
1780
|
+
const FM_ALIAS_MAP = buildAliasMap();
|
|
1781
|
+
const FM_SPEC_BY_KEY = new Map(UNIVERSAL_META_KEYS.map((k) => [k.key, k]));
|
|
1782
|
+
/**
|
|
1783
|
+
* Coerce a raw YAML scalar/array to the spec's value type. With no spec
|
|
1784
|
+
* (custom key) the coercion is best-effort: booleans and numbers are
|
|
1785
|
+
* recognised by shape, everything else stays a string. Returns undefined
|
|
1786
|
+
* when the value can't be represented (caller skips the key).
|
|
1787
|
+
*/
|
|
1788
|
+
function coerceMetaValue(rawVal, specType) {
|
|
1789
|
+
if (Array.isArray(rawVal)) return rawVal;
|
|
1790
|
+
const v = rawVal;
|
|
1791
|
+
if (v === "") return void 0;
|
|
1792
|
+
switch (specType) {
|
|
1793
|
+
case "number":
|
|
1794
|
+
case "integer": {
|
|
1795
|
+
const n = Number(v);
|
|
1796
|
+
return Number.isFinite(n) ? n : void 0;
|
|
2240
1797
|
}
|
|
2241
|
-
case "
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
el.insert(0, codeEls);
|
|
2249
|
-
codes.forEach((b, i) => fillBlock(codeEls[i], b));
|
|
2250
|
-
break;
|
|
1798
|
+
case "boolean": return v === "true";
|
|
1799
|
+
case "string[]": return [v];
|
|
1800
|
+
case "members":
|
|
1801
|
+
case "json": try {
|
|
1802
|
+
return JSON.parse(v);
|
|
1803
|
+
} catch {
|
|
1804
|
+
return;
|
|
2251
1805
|
}
|
|
2252
|
-
case "
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
1806
|
+
case "string":
|
|
1807
|
+
case "string-enum":
|
|
1808
|
+
case "iso-date":
|
|
1809
|
+
case "iso-datetime":
|
|
1810
|
+
case "hh-mm": return v;
|
|
1811
|
+
case void 0:
|
|
1812
|
+
if (v === "true") return true;
|
|
1813
|
+
if (v === "false") return false;
|
|
1814
|
+
if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v);
|
|
1815
|
+
if (v.startsWith("{")) try {
|
|
1816
|
+
return JSON.parse(v);
|
|
1817
|
+
} catch {}
|
|
1818
|
+
return v;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
function pushNested(out, inner, wrap) {
|
|
1822
|
+
const children = parseInline(inner);
|
|
1823
|
+
if (children.length === 0) {
|
|
1824
|
+
out.push({
|
|
1825
|
+
text: inner,
|
|
1826
|
+
attrs: { ...wrap }
|
|
1827
|
+
});
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
for (const child of children) out.push({
|
|
1831
|
+
text: child.text,
|
|
1832
|
+
attrs: {
|
|
1833
|
+
...child.attrs ?? {},
|
|
1834
|
+
...wrap
|
|
2262
1835
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
function parseInline(text) {
|
|
1839
|
+
const stripped = text.replace(/\{lang="[^"]*"\}/g, "").replace(/:(?!badge|icon|kbd)(\w[\w-]*)\[([^\]]*)\](\{[^}]*\})?/g, "$2").replace(/:(?!badge|icon|kbd)(\w[\w-]*)(\{[^}]*\})/g, "");
|
|
1840
|
+
const tokens = [];
|
|
1841
|
+
const re = /\$([^$\n]+?)\$|@\[([^\]]+?)\]\(user:([^)]+?)\)|:badge\[([^\]]*)\](\{[^}]*\})?|:icon\{([^}]*)\}|:kbd\{([^}]*)\}|\[\[([0-9a-fA-F-]{36})(?:\|([^\]]+?))?\]\]|~~(.+?)~~|\*\*(.+?)\*\*|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)/g;
|
|
1842
|
+
let lastIndex = 0;
|
|
1843
|
+
let match;
|
|
1844
|
+
while ((match = re.exec(stripped)) !== null) {
|
|
1845
|
+
if (match.index > lastIndex) tokens.push({ text: stripped.slice(lastIndex, match.index) });
|
|
1846
|
+
if (match[1] !== void 0) tokens.push({
|
|
1847
|
+
text: match[1],
|
|
1848
|
+
attrs: { mathInline: { expression: match[1] } }
|
|
1849
|
+
});
|
|
1850
|
+
else if (match[2] !== void 0 && match[3] !== void 0) tokens.push({
|
|
1851
|
+
text: match[2],
|
|
1852
|
+
attrs: { mention: {
|
|
1853
|
+
userId: match[3],
|
|
1854
|
+
label: match[2]
|
|
1855
|
+
} }
|
|
1856
|
+
});
|
|
1857
|
+
else if (match[4] !== void 0) {
|
|
1858
|
+
const badgeProps = parseMdcProps(match[5]);
|
|
1859
|
+
tokens.push({
|
|
1860
|
+
text: match[4] || "Badge",
|
|
1861
|
+
attrs: { badge: {
|
|
1862
|
+
label: match[4] || "Badge",
|
|
1863
|
+
color: badgeProps["color"] || "neutral",
|
|
1864
|
+
variant: badgeProps["variant"] || "subtle"
|
|
1865
|
+
} }
|
|
2279
1866
|
});
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
block.items.forEach((item, i) => {
|
|
2286
|
-
itemEls[i].setAttribute("label", item.label);
|
|
2287
|
-
if (item.icon) itemEls[i].setAttribute("icon", item.icon);
|
|
2288
|
-
const inner = item.innerBlocks.length ? item.innerBlocks : [{
|
|
2289
|
-
type: "paragraph",
|
|
2290
|
-
text: ""
|
|
2291
|
-
}];
|
|
2292
|
-
const childEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2293
|
-
itemEls[i].insert(0, childEls);
|
|
2294
|
-
inner.forEach((b, ci) => fillBlock(childEls[ci], b));
|
|
1867
|
+
} else if (match[6] !== void 0) {
|
|
1868
|
+
const iconProps = parseMdcProps(`{${match[6]}}`);
|
|
1869
|
+
tokens.push({
|
|
1870
|
+
text: "",
|
|
1871
|
+
attrs: { proseIcon: { name: iconProps["name"] || "i-lucide-star" } }
|
|
2295
1872
|
});
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
1873
|
+
} else if (match[7] !== void 0) {
|
|
1874
|
+
const kbdProps = parseMdcProps(`{${match[7]}}`);
|
|
1875
|
+
tokens.push({
|
|
1876
|
+
text: kbdProps["value"] || "",
|
|
1877
|
+
attrs: { kbd: { value: kbdProps["value"] || "" } }
|
|
1878
|
+
});
|
|
1879
|
+
} else if (match[8] !== void 0) {
|
|
1880
|
+
const docId = match[8];
|
|
1881
|
+
const label = match[9] ?? docId;
|
|
1882
|
+
tokens.push({
|
|
1883
|
+
text: label,
|
|
1884
|
+
attrs: { docLink: { docId } }
|
|
1885
|
+
});
|
|
1886
|
+
} else if (match[10] !== void 0) pushNested(tokens, match[10], { strike: true });
|
|
1887
|
+
else if (match[11] !== void 0) pushNested(tokens, match[11], { bold: true });
|
|
1888
|
+
else if (match[12] !== void 0) pushNested(tokens, match[12], { italic: true });
|
|
1889
|
+
else if (match[13] !== void 0) pushNested(tokens, match[13], { italic: true });
|
|
1890
|
+
else if (match[14] !== void 0) tokens.push({
|
|
1891
|
+
text: match[14],
|
|
1892
|
+
attrs: { code: true }
|
|
1893
|
+
});
|
|
1894
|
+
else if (match[15] !== void 0 && match[16] !== void 0) tokens.push({
|
|
1895
|
+
text: match[15],
|
|
1896
|
+
attrs: { link: { href: match[16] } }
|
|
1897
|
+
});
|
|
1898
|
+
lastIndex = match.index + match[0].length;
|
|
1899
|
+
}
|
|
1900
|
+
if (lastIndex < stripped.length) tokens.push({ text: stripped.slice(lastIndex) });
|
|
1901
|
+
return tokens.filter((t) => t.text.length > 0);
|
|
1902
|
+
}
|
|
1903
|
+
function parseTableRow(line) {
|
|
1904
|
+
const parts = line.split("|");
|
|
1905
|
+
return parts.slice(1, parts.length - 1).map((c) => c.trim());
|
|
1906
|
+
}
|
|
1907
|
+
function isTableSeparator(line) {
|
|
1908
|
+
return /^\|[\s|:-]+\|$/.test(line.trim());
|
|
1909
|
+
}
|
|
1910
|
+
/** Extract fenced code blocks from MDC #code slot lines. */
|
|
1911
|
+
function extractFencedCode(lines) {
|
|
1912
|
+
const result = [];
|
|
1913
|
+
let i = 0;
|
|
1914
|
+
while (i < lines.length) {
|
|
1915
|
+
const fenceMatch = lines[i].match(/^(`{3,})(\w*)/);
|
|
1916
|
+
if (fenceMatch) {
|
|
1917
|
+
const fence = fenceMatch[1];
|
|
1918
|
+
const lang = fenceMatch[2] ?? "";
|
|
1919
|
+
const codeLines = [];
|
|
1920
|
+
i++;
|
|
1921
|
+
while (i < lines.length && !lines[i].startsWith(fence)) {
|
|
1922
|
+
codeLines.push(lines[i]);
|
|
1923
|
+
i++;
|
|
1924
|
+
}
|
|
1925
|
+
i++;
|
|
1926
|
+
result.push({
|
|
1927
|
+
type: "codeBlock",
|
|
1928
|
+
lang,
|
|
1929
|
+
code: codeLines.join("\n")
|
|
1930
|
+
});
|
|
1931
|
+
continue;
|
|
2310
1932
|
}
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
1933
|
+
i++;
|
|
1934
|
+
}
|
|
1935
|
+
return result;
|
|
1936
|
+
}
|
|
1937
|
+
/** Extract key="value" pairs from MDC prop syntax `{key="value" other="x"}` */
|
|
1938
|
+
function parseMdcProps(propsStr) {
|
|
1939
|
+
if (!propsStr) return {};
|
|
1940
|
+
const result = {};
|
|
1941
|
+
let s = propsStr.trim();
|
|
1942
|
+
if (s.startsWith("{") && s.endsWith("}")) s = s.slice(1, -1);
|
|
1943
|
+
const re = /(\w[\w-]*)(?:=(?:"([^"]*)"|([^\s"}]+)))?/g;
|
|
1944
|
+
let m;
|
|
1945
|
+
while ((m = re.exec(s)) !== null) {
|
|
1946
|
+
const key = m[1];
|
|
1947
|
+
if (m[2] !== void 0) result[key] = m[2];
|
|
1948
|
+
else if (m[3] !== void 0) result[key] = m[3];
|
|
1949
|
+
else result[key] = "true";
|
|
1950
|
+
}
|
|
1951
|
+
return result;
|
|
1952
|
+
}
|
|
1953
|
+
/** Parse named child MDC blocks from inner lines (e.g. #item for accordion, #tab for tabs) */
|
|
1954
|
+
function parseMdcChildren(innerLines, slotPrefix) {
|
|
1955
|
+
const items = [];
|
|
1956
|
+
let current = null;
|
|
1957
|
+
const slotRe = new RegExp(`^#${slotPrefix}(\\{[^}]*\\})?\\s*$`);
|
|
1958
|
+
for (const line of innerLines) {
|
|
1959
|
+
const slotMatch = line.match(slotRe);
|
|
1960
|
+
if (slotMatch) {
|
|
1961
|
+
if (current) items.push(current);
|
|
1962
|
+
const props = parseMdcProps(slotMatch[1]);
|
|
1963
|
+
current = {
|
|
1964
|
+
label: props["label"] || props["title"] || `Item ${items.length + 1}`,
|
|
1965
|
+
icon: props["icon"] || "",
|
|
1966
|
+
lines: []
|
|
1967
|
+
};
|
|
1968
|
+
continue;
|
|
2316
1969
|
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
case "docEmbed":
|
|
2324
|
-
el.setAttribute("docId", block.docId);
|
|
2325
|
-
for (const flag of [
|
|
2326
|
-
"collapsed",
|
|
2327
|
-
"tall",
|
|
2328
|
-
"seamless"
|
|
2329
|
-
]) if (block.props[flag] === "true" || block.props[flag] === "1") el.setAttribute(flag, true);
|
|
2330
|
-
break;
|
|
2331
|
-
case "mathBlock":
|
|
2332
|
-
el.setAttribute("expression", block.expression);
|
|
2333
|
-
break;
|
|
2334
|
-
case "fileBlock":
|
|
2335
|
-
if (block.src) el.setAttribute("src", block.src);
|
|
2336
|
-
if (block.mime) el.setAttribute("mime", block.mime);
|
|
2337
|
-
if (block.uploadId) el.setAttribute("uploadId", block.uploadId);
|
|
2338
|
-
if (block.filename) el.setAttribute("filename", block.filename);
|
|
2339
|
-
break;
|
|
1970
|
+
if (current) current.lines.push(line);
|
|
1971
|
+
else if (!items.length && !current) current = {
|
|
1972
|
+
label: `Item 1`,
|
|
1973
|
+
icon: "",
|
|
1974
|
+
lines: [line]
|
|
1975
|
+
};
|
|
2340
1976
|
}
|
|
1977
|
+
if (current) items.push(current);
|
|
1978
|
+
return items.map((item) => ({
|
|
1979
|
+
label: item.label,
|
|
1980
|
+
icon: item.icon,
|
|
1981
|
+
innerBlocks: parseBlocks(item.lines.join("\n"))
|
|
1982
|
+
}));
|
|
2341
1983
|
}
|
|
1984
|
+
const TASK_RE = /^[-*+]\s+\[([ xX])\]\s+(.*)/;
|
|
2342
1985
|
/**
|
|
2343
|
-
*
|
|
2344
|
-
*
|
|
2345
|
-
*
|
|
2346
|
-
*
|
|
2347
|
-
* obtained from a live Y.Doc via `ydoc.getXmlFragment('default')`).
|
|
1986
|
+
* Consume a list (bullet / ordered / task) starting at `start`. Indented
|
|
1987
|
+
* continuation lines and nested lists are captured into each item's
|
|
1988
|
+
* `innerBlocks` so the parse → serialise → parse cycle preserves tree
|
|
1989
|
+
* structure instead of flattening nested lists onto a single line.
|
|
2348
1990
|
*
|
|
2349
|
-
*
|
|
2350
|
-
*
|
|
2351
|
-
*
|
|
1991
|
+
* `indent` is the column of the item marker for the current list. A
|
|
1992
|
+
* nested list starts ≥2 columns deeper. Lines with less indent than
|
|
1993
|
+
* `indent` belong to the outer block and stop consumption.
|
|
2352
1994
|
*/
|
|
2353
|
-
function
|
|
2354
|
-
const
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
const
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
case "callout": return new yjs.XmlElement("callout");
|
|
2389
|
-
case "collapsible": return new yjs.XmlElement("collapsible");
|
|
2390
|
-
case "steps": return new yjs.XmlElement("steps");
|
|
2391
|
-
case "card": return new yjs.XmlElement("card");
|
|
2392
|
-
case "cardGroup": return new yjs.XmlElement("cardGroup");
|
|
2393
|
-
case "codeCollapse": return new yjs.XmlElement("codeCollapse");
|
|
2394
|
-
case "codeGroup": return new yjs.XmlElement("codeGroup");
|
|
2395
|
-
case "codePreview": return new yjs.XmlElement("codePreview");
|
|
2396
|
-
case "codeTree": return new yjs.XmlElement("codeTree");
|
|
2397
|
-
case "accordion": return new yjs.XmlElement("accordion");
|
|
2398
|
-
case "tabs": return new yjs.XmlElement("tabs");
|
|
2399
|
-
case "field": return new yjs.XmlElement("field");
|
|
2400
|
-
case "fieldGroup": return new yjs.XmlElement("fieldGroup");
|
|
2401
|
-
case "image": return new yjs.XmlElement("image");
|
|
2402
|
-
case "docEmbed": return new yjs.XmlElement("docEmbed");
|
|
2403
|
-
case "mathBlock": return new yjs.XmlElement("mathBlock");
|
|
2404
|
-
case "fileBlock": return new yjs.XmlElement("fileBlock");
|
|
1995
|
+
function consumeList(lines, start, indent, kind) {
|
|
1996
|
+
const items = [];
|
|
1997
|
+
let i = start;
|
|
1998
|
+
while (i < lines.length) {
|
|
1999
|
+
const line = lines[i];
|
|
2000
|
+
if (line.trim() === "") {
|
|
2001
|
+
let j = i + 1;
|
|
2002
|
+
while (j < lines.length && lines[j].trim() === "") j++;
|
|
2003
|
+
if (j >= lines.length) break;
|
|
2004
|
+
const lookahead = lines[j];
|
|
2005
|
+
if (leadingSpaces(lookahead) < indent) break;
|
|
2006
|
+
if (!matchMarker(lookahead.slice(indent), kind)) break;
|
|
2007
|
+
i = j;
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
2010
|
+
const leading = leadingSpaces(line);
|
|
2011
|
+
if (leading < indent) break;
|
|
2012
|
+
if (leading > indent) break;
|
|
2013
|
+
const m = matchMarker(line.slice(indent), kind);
|
|
2014
|
+
if (!m) break;
|
|
2015
|
+
const item = { text: m.text };
|
|
2016
|
+
if (kind === "task") item.checked = m.checked;
|
|
2017
|
+
i++;
|
|
2018
|
+
const contLines = [];
|
|
2019
|
+
let contMinIndent = Infinity;
|
|
2020
|
+
while (i < lines.length) {
|
|
2021
|
+
const next = lines[i];
|
|
2022
|
+
if (next.trim() === "") {
|
|
2023
|
+
let k = i + 1;
|
|
2024
|
+
while (k < lines.length && lines[k].trim() === "") k++;
|
|
2025
|
+
if (k >= lines.length) break;
|
|
2026
|
+
if (leadingSpaces(lines[k]) <= indent) break;
|
|
2027
|
+
contLines.push("");
|
|
2028
|
+
i++;
|
|
2029
|
+
continue;
|
|
2405
2030
|
}
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
]);
|
|
2412
|
-
if (titleSource) headerEl.setAttribute("titleSource", titleSource);
|
|
2413
|
-
const headerXt = new yjs.XmlText();
|
|
2414
|
-
headerEl.insert(0, [headerXt]);
|
|
2415
|
-
headerXt.insert(0, title);
|
|
2416
|
-
for (const k of Object.keys(fm.meta)) {
|
|
2417
|
-
const v = fm.meta[k];
|
|
2418
|
-
if (v === void 0 || v === null) continue;
|
|
2419
|
-
metaEl.setAttribute(k, v);
|
|
2031
|
+
const nextIndent = leadingSpaces(next);
|
|
2032
|
+
if (nextIndent <= indent) break;
|
|
2033
|
+
if (nextIndent < contMinIndent) contMinIndent = nextIndent;
|
|
2034
|
+
contLines.push(next);
|
|
2035
|
+
i++;
|
|
2420
2036
|
}
|
|
2421
|
-
if (
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2037
|
+
if (contLines.length > 0) {
|
|
2038
|
+
const deindentBy = contMinIndent === Infinity ? 0 : contMinIndent;
|
|
2039
|
+
item.innerBlocks = parseBlocks(contLines.map((l) => l.slice(deindentBy)).join("\n"));
|
|
2040
|
+
}
|
|
2041
|
+
items.push(item);
|
|
2042
|
+
}
|
|
2043
|
+
return {
|
|
2044
|
+
items,
|
|
2045
|
+
next: i
|
|
2046
|
+
};
|
|
2430
2047
|
}
|
|
2431
|
-
function
|
|
2432
|
-
|
|
2048
|
+
function leadingSpaces(s) {
|
|
2049
|
+
let n = 0;
|
|
2050
|
+
while (n < s.length && s[n] === " ") n++;
|
|
2051
|
+
return n;
|
|
2433
2052
|
}
|
|
2434
|
-
function
|
|
2435
|
-
|
|
2053
|
+
function matchMarker(s, kind) {
|
|
2054
|
+
if (kind === "task") {
|
|
2055
|
+
const m = s.match(TASK_RE);
|
|
2056
|
+
if (!m) return null;
|
|
2057
|
+
return {
|
|
2058
|
+
text: m[2],
|
|
2059
|
+
checked: m[1].toLowerCase() === "x"
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
if (kind === "bullet") {
|
|
2063
|
+
if (TASK_RE.test(s)) return null;
|
|
2064
|
+
const m = s.match(/^[-*+]\s+(.*)$/);
|
|
2065
|
+
if (!m) return null;
|
|
2066
|
+
return {
|
|
2067
|
+
text: m[1],
|
|
2068
|
+
checked: false
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
const m = s.match(/^\d+\.\s+(.*)$/);
|
|
2072
|
+
if (!m) return null;
|
|
2073
|
+
return {
|
|
2074
|
+
text: m[1],
|
|
2075
|
+
checked: false
|
|
2076
|
+
};
|
|
2436
2077
|
}
|
|
2437
|
-
function
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2078
|
+
function parseBlocks(markdown) {
|
|
2079
|
+
const rawLines = markdown.split("\n");
|
|
2080
|
+
let firstContentLine = 0;
|
|
2081
|
+
while (firstContentLine < rawLines.length) {
|
|
2082
|
+
const l = rawLines[firstContentLine];
|
|
2083
|
+
if (l.trim() === "" || /^import\s/.test(l) || /^export\s/.test(l)) firstContentLine++;
|
|
2084
|
+
else break;
|
|
2085
|
+
}
|
|
2086
|
+
const stripped = rawLines.slice(firstContentLine).join("\n");
|
|
2087
|
+
const blocks = [];
|
|
2088
|
+
const lines = stripped.split("\n");
|
|
2089
|
+
let i = 0;
|
|
2090
|
+
while (i < lines.length) {
|
|
2091
|
+
const line = lines[i];
|
|
2092
|
+
const fenceBlockMatch = line.match(/^(`{3,})(.*)$/);
|
|
2093
|
+
if (fenceBlockMatch) {
|
|
2094
|
+
const fence = fenceBlockMatch[1];
|
|
2095
|
+
const lang = fenceBlockMatch[2].trim().replace(/\{[^}]*\}$/, "").replace(/\s*\[.*\]$/, "").trim();
|
|
2096
|
+
const codeLines = [];
|
|
2097
|
+
i++;
|
|
2098
|
+
while (i < lines.length && !lines[i].startsWith(fence)) {
|
|
2099
|
+
codeLines.push(lines[i]);
|
|
2100
|
+
i++;
|
|
2101
|
+
}
|
|
2102
|
+
i++;
|
|
2103
|
+
const code = codeLines.join("\n");
|
|
2104
|
+
if (lang === "math") blocks.push({
|
|
2105
|
+
type: "mathBlock",
|
|
2106
|
+
expression: code
|
|
2107
|
+
});
|
|
2108
|
+
else blocks.push({
|
|
2109
|
+
type: "codeBlock",
|
|
2110
|
+
lang,
|
|
2111
|
+
code
|
|
2112
|
+
});
|
|
2445
2113
|
continue;
|
|
2446
2114
|
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2115
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
|
|
2116
|
+
if (headingMatch) {
|
|
2117
|
+
blocks.push({
|
|
2118
|
+
type: "heading",
|
|
2119
|
+
level: headingMatch[1].length,
|
|
2120
|
+
text: headingMatch[2].trim()
|
|
2121
|
+
});
|
|
2122
|
+
i++;
|
|
2453
2123
|
continue;
|
|
2454
2124
|
}
|
|
2455
|
-
if (
|
|
2456
|
-
|
|
2457
|
-
|
|
2125
|
+
if (/^[-*_]{3,}\s*$/.test(line)) {
|
|
2126
|
+
blocks.push({ type: "hr" });
|
|
2127
|
+
i++;
|
|
2458
2128
|
continue;
|
|
2459
2129
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2130
|
+
const embedMatch = line.match(/^!\[\[([0-9a-fA-F-]{36})(?:\|([^\]]+))?\]\](\{[^}]*\})?\s*$/);
|
|
2131
|
+
if (embedMatch) {
|
|
2132
|
+
const docId = embedMatch[1];
|
|
2133
|
+
const label = embedMatch[2] ?? "";
|
|
2134
|
+
const props = parseMdcProps(embedMatch[3]);
|
|
2135
|
+
blocks.push({
|
|
2136
|
+
type: "docEmbed",
|
|
2137
|
+
docId,
|
|
2138
|
+
label,
|
|
2139
|
+
props
|
|
2140
|
+
});
|
|
2141
|
+
i++;
|
|
2463
2142
|
continue;
|
|
2464
2143
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2144
|
+
const imgMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)(\{[^}]*\})?\s*$/);
|
|
2145
|
+
if (imgMatch) {
|
|
2146
|
+
const alt = imgMatch[1] ?? "";
|
|
2147
|
+
const src = imgMatch[2] ?? "";
|
|
2148
|
+
const attrs = parseMdcProps(imgMatch[3]);
|
|
2149
|
+
blocks.push({
|
|
2150
|
+
type: "image",
|
|
2151
|
+
src,
|
|
2152
|
+
alt,
|
|
2153
|
+
width: attrs["width"],
|
|
2154
|
+
height: attrs["height"]
|
|
2155
|
+
});
|
|
2156
|
+
i++;
|
|
2157
|
+
continue;
|
|
2471
2158
|
}
|
|
2472
|
-
if (
|
|
2473
|
-
const
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2159
|
+
if (line.startsWith(">")) {
|
|
2160
|
+
const bqLines = [];
|
|
2161
|
+
while (i < lines.length && lines[i].startsWith(">")) {
|
|
2162
|
+
bqLines.push(lines[i].replace(/^>\s?/, ""));
|
|
2163
|
+
i++;
|
|
2477
2164
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2165
|
+
blocks.push({
|
|
2166
|
+
type: "blockquote",
|
|
2167
|
+
lines: bqLines
|
|
2168
|
+
});
|
|
2482
2169
|
continue;
|
|
2483
2170
|
}
|
|
2484
|
-
if (
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
text = `[${text}](${href})`;
|
|
2490
|
-
}
|
|
2491
|
-
result += text;
|
|
2492
|
-
}
|
|
2493
|
-
return result;
|
|
2494
|
-
}
|
|
2495
|
-
function serializeInline(el) {
|
|
2496
|
-
const parts = [];
|
|
2497
|
-
for (const child of el.toArray()) if (isXText(child)) parts.push(serializeDelta(child.toDelta()));
|
|
2498
|
-
else if (isXElem(child)) if (child.nodeName === "docLink") {
|
|
2499
|
-
const docId = child.getAttribute("docId") ?? "";
|
|
2500
|
-
parts.push(`[[${docId}]]`);
|
|
2501
|
-
} else parts.push(serializeInline(child));
|
|
2502
|
-
return parts.join("");
|
|
2503
|
-
}
|
|
2504
|
-
function serializeBlock(el, indent = "") {
|
|
2505
|
-
if (isXText(el)) return serializeDelta(el.toDelta());
|
|
2506
|
-
switch (el.nodeName) {
|
|
2507
|
-
case "documentHeader":
|
|
2508
|
-
case "documentMeta": return "";
|
|
2509
|
-
case "heading": {
|
|
2510
|
-
const level = Number(el.getAttribute("level") ?? 2);
|
|
2511
|
-
return `${"#".repeat(level)} ${serializeInline(el)}`;
|
|
2512
|
-
}
|
|
2513
|
-
case "paragraph": return serializeInline(el);
|
|
2514
|
-
case "bulletList": return serializeListItems(el, "bullet", indent);
|
|
2515
|
-
case "orderedList": return serializeListItems(el, "ordered", indent);
|
|
2516
|
-
case "taskList": return serializeTaskList(el, indent);
|
|
2517
|
-
case "codeBlock": {
|
|
2518
|
-
const lang = el.getAttribute("language") ?? "";
|
|
2519
|
-
const code = getCodeBlockText(el);
|
|
2520
|
-
if (code === "") return `\`\`\`${lang}\n\`\`\``;
|
|
2521
|
-
return `\`\`\`${lang}\n${code}\n\`\`\``;
|
|
2522
|
-
}
|
|
2523
|
-
case "blockquote": {
|
|
2524
|
-
const lines = [];
|
|
2525
|
-
for (const child of el.toArray()) if (isXElem(child)) {
|
|
2526
|
-
const text = serializeBlock(child);
|
|
2527
|
-
for (const line of text.split("\n")) lines.push(`> ${line}`);
|
|
2171
|
+
if (/^\s*\|/.test(line)) {
|
|
2172
|
+
const tableLines = [];
|
|
2173
|
+
while (i < lines.length && /^\s*\|/.test(lines[i])) {
|
|
2174
|
+
tableLines.push(lines[i]);
|
|
2175
|
+
i++;
|
|
2528
2176
|
}
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
}
|
|
2543
|
-
case "docEmbed": {
|
|
2544
|
-
const docId = el.getAttribute("docId") ?? "";
|
|
2545
|
-
const collapsed = el.getAttribute("collapsed");
|
|
2546
|
-
const tall = el.getAttribute("tall");
|
|
2547
|
-
const seamless = el.getAttribute("seamless");
|
|
2548
|
-
const flags = [];
|
|
2549
|
-
if (collapsed === true || collapsed === "true") flags.push("collapsed");
|
|
2550
|
-
if (tall === true || tall === "true") flags.push("tall");
|
|
2551
|
-
if (seamless === true || seamless === "true") flags.push("seamless");
|
|
2552
|
-
return `![[${docId}]]${flags.length ? `{${flags.join(" ")}}` : ""}`;
|
|
2553
|
-
}
|
|
2554
|
-
case "mathBlock": return `\`\`\`math\n${el.getAttribute("expression") ?? ""}\n\`\`\``;
|
|
2555
|
-
case "fileBlock": {
|
|
2556
|
-
const uploadId = el.getAttribute("uploadId") ?? "";
|
|
2557
|
-
const filename = el.getAttribute("filename") ?? "";
|
|
2558
|
-
const mime = el.getAttribute("mime") ?? "";
|
|
2559
|
-
const src = el.getAttribute("src") ?? (uploadId && filename ? `.abracadabra/files/${uploadId}-${filename}` : "");
|
|
2560
|
-
const props = [];
|
|
2561
|
-
if (src) props.push(`src="${src}"`);
|
|
2562
|
-
if (mime) props.push(`mime="${mime}"`);
|
|
2563
|
-
if (uploadId) props.push(`upload-id="${uploadId}"`);
|
|
2564
|
-
if (filename) props.push(`filename="${filename}"`);
|
|
2565
|
-
return `:file{${props.join(" ")}}`;
|
|
2566
|
-
}
|
|
2567
|
-
case "callout": return `::${el.getAttribute("type") ?? "note"}\n${serializeChildren(el)}\n::`;
|
|
2568
|
-
case "collapsible": {
|
|
2569
|
-
const label = el.getAttribute("label") ?? "Details";
|
|
2570
|
-
const open = el.getAttribute("open");
|
|
2571
|
-
const props = [`label="${label}"`];
|
|
2572
|
-
if (open === true || open === "true") props.push("open=\"true\"");
|
|
2573
|
-
return `::collapsible{${props.join(" ")}}\n${serializeChildren(el)}\n::`;
|
|
2574
|
-
}
|
|
2575
|
-
case "steps": return `::steps\n${serializeChildren(el)}\n::`;
|
|
2576
|
-
case "card": {
|
|
2577
|
-
const props = [];
|
|
2578
|
-
const title = el.getAttribute("title");
|
|
2579
|
-
const icon = el.getAttribute("icon");
|
|
2580
|
-
const to = el.getAttribute("to");
|
|
2581
|
-
if (title) props.push(`title="${title}"`);
|
|
2582
|
-
if (icon) props.push(`icon="${icon}"`);
|
|
2583
|
-
if (to) props.push(`to="${to}"`);
|
|
2584
|
-
return `::card${props.length ? `{${props.join(" ")}}` : ""}\n${serializeChildren(el)}\n::`;
|
|
2585
|
-
}
|
|
2586
|
-
case "cardGroup": return `::card-group\n${el.toArray().filter((c) => isXElem(c)).map((c) => serializeBlock(c)).join("\n\n")}\n::`;
|
|
2587
|
-
case "codeCollapse": return `::code-collapse\n${el.toArray().filter((c) => isXElem(c) && c.nodeName === "codeBlock").map((c) => serializeBlock(c)).join("\n\n")}\n::`;
|
|
2588
|
-
case "codeGroup": return `::code-group\n${el.toArray().filter((c) => isXElem(c) && c.nodeName === "codeBlock").map((c) => serializeBlock(c)).join("\n\n")}\n::`;
|
|
2589
|
-
case "codePreview": {
|
|
2590
|
-
const children = el.toArray().filter((c) => isXElem(c));
|
|
2591
|
-
const nonCode = children.filter((c) => c.nodeName !== "codeBlock").map((c) => serializeBlock(c)).join("\n\n");
|
|
2592
|
-
const code = children.filter((c) => c.nodeName === "codeBlock").map((c) => serializeBlock(c)).join("\n\n");
|
|
2593
|
-
const parts = [nonCode];
|
|
2594
|
-
if (code) parts.push(`#code\n${code}`);
|
|
2595
|
-
return `::code-preview\n${parts.filter(Boolean).join("\n\n")}\n::`;
|
|
2596
|
-
}
|
|
2597
|
-
case "codeTree": return `::code-tree{files="${el.getAttribute("files") ?? "[]"}"}\n::`;
|
|
2598
|
-
case "accordion": return serializeSlottedContainer(el, "accordion", "accordionItem", "item");
|
|
2599
|
-
case "tabs": return serializeSlottedContainer(el, "tabs", "tabsItem", "tab");
|
|
2600
|
-
case "field": {
|
|
2601
|
-
const fieldName = el.getAttribute("name") ?? "";
|
|
2602
|
-
const fieldType = el.getAttribute("type") ?? "string";
|
|
2603
|
-
const required = el.getAttribute("required");
|
|
2604
|
-
const props = [`name="${fieldName}"`, `type="${fieldType}"`];
|
|
2605
|
-
if (required === true || required === "true") props.push("required=\"true\"");
|
|
2606
|
-
return `::field{${props.join(" ")}}\n${serializeChildren(el)}\n::`;
|
|
2607
|
-
}
|
|
2608
|
-
case "fieldGroup": return `::field-group\n${el.toArray().filter((c) => isXElem(c)).map((c) => serializeBlock(c)).join("\n\n")}\n::`;
|
|
2609
|
-
default: return serializeChildren(el);
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
function serializeChildren(el) {
|
|
2613
|
-
const blocks = [];
|
|
2614
|
-
for (const child of el.toArray()) if (isXElem(child)) {
|
|
2615
|
-
const text = serializeBlock(child);
|
|
2616
|
-
if (text) blocks.push(text);
|
|
2617
|
-
} else if (isXText(child)) {
|
|
2618
|
-
const text = serializeDelta(child.toDelta());
|
|
2619
|
-
if (text) blocks.push(text);
|
|
2620
|
-
}
|
|
2621
|
-
return blocks.join("\n\n");
|
|
2622
|
-
}
|
|
2623
|
-
function serializeListItems(el, type, indent) {
|
|
2624
|
-
const lines = [];
|
|
2625
|
-
let counter = 1;
|
|
2626
|
-
for (const child of el.toArray()) {
|
|
2627
|
-
if (!isXElem(child) || child.nodeName !== "listItem") continue;
|
|
2628
|
-
const prefix = type === "bullet" ? "- " : `${counter++}. `;
|
|
2629
|
-
const subParts = [];
|
|
2630
|
-
for (const sub of child.toArray()) {
|
|
2631
|
-
if (!isXElem(sub)) continue;
|
|
2632
|
-
if (sub.nodeName === "bulletList") subParts.push(serializeListItems(sub, "bullet", indent + " "));
|
|
2633
|
-
else if (sub.nodeName === "orderedList") subParts.push(serializeListItems(sub, "ordered", indent + " "));
|
|
2634
|
-
else subParts.push(serializeInline(sub));
|
|
2635
|
-
}
|
|
2636
|
-
if (subParts.length <= 1) lines.push(`${indent}${prefix}${subParts[0] ?? ""}`);
|
|
2637
|
-
else {
|
|
2638
|
-
lines.push(`${indent}${prefix}${subParts[0] ?? ""}`);
|
|
2639
|
-
for (let i = 1; i < subParts.length; i++) lines.push(subParts[i]);
|
|
2177
|
+
if (tableLines.length >= 2 && isTableSeparator(tableLines[1])) {
|
|
2178
|
+
const headerRow = parseTableRow(tableLines[0]);
|
|
2179
|
+
const dataRows = tableLines.slice(2).filter((l) => !isTableSeparator(l)).map(parseTableRow);
|
|
2180
|
+
blocks.push({
|
|
2181
|
+
type: "table",
|
|
2182
|
+
headerRow,
|
|
2183
|
+
dataRows
|
|
2184
|
+
});
|
|
2185
|
+
} else for (const l of tableLines) blocks.push({
|
|
2186
|
+
type: "paragraph",
|
|
2187
|
+
text: l
|
|
2188
|
+
});
|
|
2189
|
+
continue;
|
|
2640
2190
|
}
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
else if (sub.nodeName === "taskList") nestedParts.push(serializeTaskList(sub, indent + " "));
|
|
2658
|
-
else nestedParts.push(indent + " " + serializeBlock(sub, indent + " "));
|
|
2191
|
+
const atomMatch = line.match(/^:(\w[\w-]*)(\{[^}]*\})?\s*$/);
|
|
2192
|
+
if (atomMatch && atomMatch[1] === "file") {
|
|
2193
|
+
const props = parseMdcProps(atomMatch[2]);
|
|
2194
|
+
const uploadId = props["upload-id"] ?? props["uploadId"] ?? "";
|
|
2195
|
+
const filename = props["filename"] ?? "";
|
|
2196
|
+
const mime = props["mime"] ?? "";
|
|
2197
|
+
const src = props["src"] ?? (uploadId && filename ? `.abracadabra/files/${uploadId}-${filename}` : "");
|
|
2198
|
+
blocks.push({
|
|
2199
|
+
type: "fileBlock",
|
|
2200
|
+
src,
|
|
2201
|
+
mime,
|
|
2202
|
+
uploadId,
|
|
2203
|
+
filename
|
|
2204
|
+
});
|
|
2205
|
+
i++;
|
|
2206
|
+
continue;
|
|
2659
2207
|
}
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2208
|
+
const MDC_OPEN = /^\s*(:{2,})(\w[\w-]*)(\{[^}]*\})?\s*$/;
|
|
2209
|
+
if (MDC_OPEN.test(line)) {
|
|
2210
|
+
const colons = line.match(/^\s*(:+)/)?.[1]?.length ?? 2;
|
|
2211
|
+
const componentName = line.match(/^\s*:{2,}(\w[\w-]*)/)?.[1] ?? "";
|
|
2212
|
+
const innerLines = [];
|
|
2213
|
+
i++;
|
|
2214
|
+
while (i < lines.length) {
|
|
2215
|
+
const l = lines[i];
|
|
2216
|
+
if (new RegExp(`^\\s*:{${colons}}\\s*$`).test(l)) {
|
|
2217
|
+
i++;
|
|
2218
|
+
break;
|
|
2219
|
+
}
|
|
2220
|
+
const innerFence = l.match(/^(\s*`{3,})/);
|
|
2221
|
+
if (innerFence) {
|
|
2222
|
+
const fenceStr = innerFence[1].trimStart();
|
|
2223
|
+
innerLines.push(l);
|
|
2224
|
+
i++;
|
|
2225
|
+
while (i < lines.length && !lines[i].trimStart().startsWith(fenceStr)) {
|
|
2226
|
+
innerLines.push(lines[i]);
|
|
2227
|
+
i++;
|
|
2228
|
+
}
|
|
2229
|
+
if (i < lines.length) {
|
|
2230
|
+
innerLines.push(lines[i]);
|
|
2231
|
+
i++;
|
|
2232
|
+
}
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
innerLines.push(l);
|
|
2236
|
+
i++;
|
|
2237
|
+
}
|
|
2238
|
+
const nonBlank = innerLines.filter((l) => l.trim().length > 0);
|
|
2239
|
+
if (nonBlank.length) {
|
|
2240
|
+
const minIndent = Math.min(...nonBlank.map((l) => l.match(/^(\s*)/)?.[1]?.length ?? 0));
|
|
2241
|
+
if (minIndent > 0) for (let j = 0; j < innerLines.length; j++) innerLines[j] = innerLines[j].slice(Math.min(minIndent, innerLines[j].length));
|
|
2242
|
+
}
|
|
2243
|
+
let contentStart = 0;
|
|
2244
|
+
if (innerLines[0]?.trim() === "---") {
|
|
2245
|
+
const fmEnd = innerLines.findIndex((l, idx) => idx > 0 && l.trim() === "---");
|
|
2246
|
+
if (fmEnd !== -1) contentStart = fmEnd + 1;
|
|
2247
|
+
}
|
|
2248
|
+
const contentLines = innerLines.slice(contentStart);
|
|
2249
|
+
const defaultSlotLines = [];
|
|
2250
|
+
const codeSlotLines = [];
|
|
2251
|
+
let currentSlot = "default";
|
|
2252
|
+
for (const l of contentLines) {
|
|
2253
|
+
if (/^#code\s*$/.test(l)) {
|
|
2254
|
+
currentSlot = "code";
|
|
2255
|
+
continue;
|
|
2256
|
+
}
|
|
2257
|
+
if (/^#\w+/.test(l) && !/^#{2,}\s/.test(l)) {
|
|
2258
|
+
currentSlot = "other";
|
|
2259
|
+
continue;
|
|
2260
|
+
}
|
|
2261
|
+
if (currentSlot === "default") defaultSlotLines.push(l);
|
|
2262
|
+
else if (currentSlot === "code") codeSlotLines.push(l);
|
|
2263
|
+
}
|
|
2264
|
+
const innerBlocks = parseBlocks(defaultSlotLines.join("\n"));
|
|
2265
|
+
const codeBlocks = extractFencedCode(codeSlotLines);
|
|
2266
|
+
if (new Set([
|
|
2267
|
+
"tip",
|
|
2268
|
+
"note",
|
|
2269
|
+
"info",
|
|
2270
|
+
"warning",
|
|
2271
|
+
"caution",
|
|
2272
|
+
"danger",
|
|
2273
|
+
"callout",
|
|
2274
|
+
"alert"
|
|
2275
|
+
]).has(componentName.toLowerCase())) blocks.push({
|
|
2276
|
+
type: "callout",
|
|
2277
|
+
calloutType: componentName.toLowerCase(),
|
|
2278
|
+
innerBlocks
|
|
2279
|
+
});
|
|
2280
|
+
else {
|
|
2281
|
+
const mdcProps = parseMdcProps(line.match(MDC_OPEN)?.[3]);
|
|
2282
|
+
const lc = componentName.toLowerCase();
|
|
2283
|
+
if (lc === "collapsible") blocks.push({
|
|
2284
|
+
type: "collapsible",
|
|
2285
|
+
label: mdcProps["label"] || "Details",
|
|
2286
|
+
open: mdcProps["open"] === "true",
|
|
2287
|
+
innerBlocks
|
|
2288
|
+
});
|
|
2289
|
+
else if (lc === "steps") blocks.push({
|
|
2290
|
+
type: "steps",
|
|
2291
|
+
innerBlocks
|
|
2292
|
+
});
|
|
2293
|
+
else if (lc === "card") blocks.push({
|
|
2294
|
+
type: "card",
|
|
2295
|
+
title: mdcProps["title"] || "",
|
|
2296
|
+
icon: mdcProps["icon"] || "",
|
|
2297
|
+
to: mdcProps["to"] || "",
|
|
2298
|
+
innerBlocks
|
|
2299
|
+
});
|
|
2300
|
+
else if (lc === "card-group") {
|
|
2301
|
+
const cards = innerBlocks.filter((b) => b.type === "card");
|
|
2302
|
+
if (cards.length) blocks.push({
|
|
2303
|
+
type: "cardGroup",
|
|
2304
|
+
cards
|
|
2305
|
+
});
|
|
2306
|
+
else blocks.push(...innerBlocks);
|
|
2307
|
+
} else if (lc === "code-collapse") blocks.push({
|
|
2308
|
+
type: "codeCollapse",
|
|
2309
|
+
codeBlocks: codeBlocks.length ? codeBlocks : innerBlocks.filter((b) => b.type === "codeBlock")
|
|
2310
|
+
});
|
|
2311
|
+
else if (lc === "code-group") {
|
|
2312
|
+
const allCode = [...innerBlocks.filter((b) => b.type === "codeBlock"), ...codeBlocks];
|
|
2313
|
+
blocks.push({
|
|
2314
|
+
type: "codeGroup",
|
|
2315
|
+
codeBlocks: allCode
|
|
2316
|
+
});
|
|
2317
|
+
} else if (lc === "code-preview") blocks.push({
|
|
2318
|
+
type: "codePreview",
|
|
2319
|
+
innerBlocks,
|
|
2320
|
+
codeBlocks
|
|
2321
|
+
});
|
|
2322
|
+
else if (lc === "code-tree") blocks.push({
|
|
2323
|
+
type: "codeTree",
|
|
2324
|
+
files: mdcProps["files"] || "[]"
|
|
2325
|
+
});
|
|
2326
|
+
else if (lc === "accordion") {
|
|
2327
|
+
const items = parseMdcChildren(contentLines, "item");
|
|
2328
|
+
if (items.length) blocks.push({
|
|
2329
|
+
type: "accordion",
|
|
2330
|
+
items
|
|
2331
|
+
});
|
|
2332
|
+
else blocks.push({
|
|
2333
|
+
type: "accordion",
|
|
2334
|
+
items: [{
|
|
2335
|
+
label: "Item 1",
|
|
2336
|
+
icon: "",
|
|
2337
|
+
innerBlocks
|
|
2338
|
+
}]
|
|
2339
|
+
});
|
|
2340
|
+
} else if (lc === "tabs") {
|
|
2341
|
+
const items = parseMdcChildren(contentLines, "tab");
|
|
2342
|
+
if (items.length) blocks.push({
|
|
2343
|
+
type: "tabs",
|
|
2344
|
+
items
|
|
2345
|
+
});
|
|
2346
|
+
else blocks.push({
|
|
2347
|
+
type: "tabs",
|
|
2348
|
+
items: [{
|
|
2349
|
+
label: "Tab 1",
|
|
2350
|
+
icon: "",
|
|
2351
|
+
innerBlocks
|
|
2352
|
+
}]
|
|
2353
|
+
});
|
|
2354
|
+
} else if (lc === "field") blocks.push({
|
|
2355
|
+
type: "field",
|
|
2356
|
+
name: mdcProps["name"] || "",
|
|
2357
|
+
fieldType: mdcProps["type"] || "string",
|
|
2358
|
+
required: mdcProps["required"] === "true",
|
|
2359
|
+
innerBlocks
|
|
2360
|
+
});
|
|
2361
|
+
else if (lc === "field-group") {
|
|
2362
|
+
const fields = innerBlocks.filter((b) => b.type === "field");
|
|
2363
|
+
if (fields.length) blocks.push({
|
|
2364
|
+
type: "fieldGroup",
|
|
2365
|
+
fields
|
|
2366
|
+
});
|
|
2367
|
+
else blocks.push(...innerBlocks);
|
|
2368
|
+
} else {
|
|
2369
|
+
blocks.push(...innerBlocks);
|
|
2370
|
+
blocks.push(...codeBlocks);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2375
|
+
if (TASK_RE.test(line)) {
|
|
2376
|
+
const { items, next } = consumeList(lines, i, 0, "task");
|
|
2377
|
+
i = next;
|
|
2378
|
+
blocks.push({
|
|
2379
|
+
type: "taskList",
|
|
2380
|
+
items
|
|
2381
|
+
});
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
if (/^[-*+]\s+/.test(line)) {
|
|
2385
|
+
const { items, next } = consumeList(lines, i, 0, "bullet");
|
|
2386
|
+
if (items.length > 0) {
|
|
2387
|
+
i = next;
|
|
2388
|
+
blocks.push({
|
|
2389
|
+
type: "bulletList",
|
|
2390
|
+
items
|
|
2391
|
+
});
|
|
2778
2392
|
continue;
|
|
2779
2393
|
}
|
|
2780
|
-
meta[k] = v;
|
|
2781
2394
|
}
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
return {
|
|
2796
|
-
text: text ? text.toString() : "",
|
|
2797
|
-
source
|
|
2798
|
-
};
|
|
2799
|
-
}
|
|
2800
|
-
return { text: "" };
|
|
2801
|
-
}
|
|
2802
|
-
function collectBodyBlocks(fragment) {
|
|
2803
|
-
const out = [];
|
|
2804
|
-
for (const child of fragment.toArray()) {
|
|
2805
|
-
if (!isXElem(child)) continue;
|
|
2806
|
-
if (child.nodeName === "documentHeader" || child.nodeName === "documentMeta") continue;
|
|
2807
|
-
out.push(child);
|
|
2808
|
-
}
|
|
2809
|
-
return out;
|
|
2810
|
-
}
|
|
2811
|
-
function serializeBlocksClean(blocks) {
|
|
2812
|
-
const parts = [];
|
|
2813
|
-
for (const block of blocks) {
|
|
2814
|
-
if (block.nodeName === "paragraph" && block.length === 0) {
|
|
2815
|
-
parts.push("");
|
|
2395
|
+
if (/^\d+\.\s+/.test(line)) {
|
|
2396
|
+
const { items, next } = consumeList(lines, i, 0, "ordered");
|
|
2397
|
+
if (items.length > 0) {
|
|
2398
|
+
i = next;
|
|
2399
|
+
blocks.push({
|
|
2400
|
+
type: "orderedList",
|
|
2401
|
+
items
|
|
2402
|
+
});
|
|
2403
|
+
continue;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
if (line.trim() === "") {
|
|
2407
|
+
i++;
|
|
2816
2408
|
continue;
|
|
2817
2409
|
}
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
//#endregion
|
|
2836
|
-
//#region packages/convert/src/spec/nodes.ts
|
|
2837
|
-
const BOOL_FALSE_DEFAULT = {
|
|
2838
|
-
key: "",
|
|
2839
|
-
type: "boolean",
|
|
2840
|
-
default: false,
|
|
2841
|
-
optional: true
|
|
2842
|
-
};
|
|
2843
|
-
const bool = (key) => ({
|
|
2844
|
-
...BOOL_FALSE_DEFAULT,
|
|
2845
|
-
key
|
|
2846
|
-
});
|
|
2847
|
-
const str = (key, def) => ({
|
|
2848
|
-
key,
|
|
2849
|
-
type: "string",
|
|
2850
|
-
default: def,
|
|
2851
|
-
optional: true
|
|
2852
|
-
});
|
|
2853
|
-
const num = (key) => ({
|
|
2854
|
-
key,
|
|
2855
|
-
type: "number",
|
|
2856
|
-
optional: true
|
|
2857
|
-
});
|
|
2858
|
-
const int = (key) => ({
|
|
2859
|
-
key,
|
|
2860
|
-
type: "integer",
|
|
2861
|
-
optional: true
|
|
2862
|
-
});
|
|
2863
|
-
const VANILLA_BLOCKS = [
|
|
2864
|
-
{
|
|
2865
|
-
name: "documentHeader",
|
|
2866
|
-
group: "block",
|
|
2867
|
-
wire: "special",
|
|
2868
|
-
doc: "Holds the title; hoisted to frontmatter on serialise."
|
|
2869
|
-
},
|
|
2870
|
-
{
|
|
2871
|
-
name: "documentMeta",
|
|
2872
|
-
group: "block",
|
|
2873
|
-
wire: "special",
|
|
2874
|
-
doc: "Holds page-level meta; serialised into frontmatter."
|
|
2875
|
-
},
|
|
2876
|
-
{
|
|
2877
|
-
name: "paragraph",
|
|
2878
|
-
group: "block",
|
|
2879
|
-
wire: "vanilla",
|
|
2880
|
-
contentBearing: true
|
|
2881
|
-
},
|
|
2882
|
-
{
|
|
2883
|
-
name: "heading",
|
|
2884
|
-
group: "block",
|
|
2885
|
-
wire: "vanilla",
|
|
2886
|
-
attrs: [int("level")],
|
|
2887
|
-
contentBearing: true
|
|
2888
|
-
},
|
|
2889
|
-
{
|
|
2890
|
-
name: "blockquote",
|
|
2891
|
-
group: "block",
|
|
2892
|
-
wire: "vanilla",
|
|
2893
|
-
contentBearing: true
|
|
2894
|
-
},
|
|
2895
|
-
{
|
|
2896
|
-
name: "codeBlock",
|
|
2897
|
-
group: "block",
|
|
2898
|
-
wire: "fence",
|
|
2899
|
-
attrs: [str("language", "")]
|
|
2900
|
-
},
|
|
2901
|
-
{
|
|
2902
|
-
name: "bulletList",
|
|
2903
|
-
group: "block",
|
|
2904
|
-
wire: "vanilla"
|
|
2905
|
-
},
|
|
2906
|
-
{
|
|
2907
|
-
name: "orderedList",
|
|
2908
|
-
group: "block",
|
|
2909
|
-
wire: "vanilla"
|
|
2910
|
-
},
|
|
2911
|
-
{
|
|
2912
|
-
name: "listItem",
|
|
2913
|
-
group: "block",
|
|
2914
|
-
wire: "vanilla",
|
|
2915
|
-
contentBearing: true
|
|
2916
|
-
},
|
|
2917
|
-
{
|
|
2918
|
-
name: "taskList",
|
|
2919
|
-
group: "block",
|
|
2920
|
-
wire: "vanilla"
|
|
2921
|
-
},
|
|
2922
|
-
{
|
|
2923
|
-
name: "taskItem",
|
|
2924
|
-
group: "block",
|
|
2925
|
-
wire: "vanilla",
|
|
2926
|
-
attrs: [bool("checked")],
|
|
2927
|
-
contentBearing: true
|
|
2928
|
-
},
|
|
2929
|
-
{
|
|
2930
|
-
name: "table",
|
|
2931
|
-
group: "block",
|
|
2932
|
-
wire: "vanilla"
|
|
2933
|
-
},
|
|
2934
|
-
{
|
|
2935
|
-
name: "tableRow",
|
|
2936
|
-
group: "block",
|
|
2937
|
-
wire: "vanilla"
|
|
2938
|
-
},
|
|
2939
|
-
{
|
|
2940
|
-
name: "tableHeader",
|
|
2941
|
-
group: "block",
|
|
2942
|
-
wire: "vanilla",
|
|
2943
|
-
contentBearing: true
|
|
2944
|
-
},
|
|
2945
|
-
{
|
|
2946
|
-
name: "tableCell",
|
|
2947
|
-
group: "block",
|
|
2948
|
-
wire: "vanilla",
|
|
2949
|
-
contentBearing: true
|
|
2950
|
-
},
|
|
2951
|
-
{
|
|
2952
|
-
name: "horizontalRule",
|
|
2953
|
-
group: "block",
|
|
2954
|
-
wire: "vanilla"
|
|
2955
|
-
},
|
|
2956
|
-
{
|
|
2957
|
-
name: "image",
|
|
2958
|
-
group: "block",
|
|
2959
|
-
wire: "special",
|
|
2960
|
-
attrs: [
|
|
2961
|
-
str("src"),
|
|
2962
|
-
str("alt", ""),
|
|
2963
|
-
int("width"),
|
|
2964
|
-
int("height")
|
|
2965
|
-
]
|
|
2966
|
-
},
|
|
2967
|
-
{
|
|
2968
|
-
name: "hardBreak",
|
|
2969
|
-
group: "inline",
|
|
2970
|
-
wire: "vanilla"
|
|
2410
|
+
const paraLines = [];
|
|
2411
|
+
while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6}\s|[-*+]\s|\d+\.\s|>|`{3,}|\s*\||[-*_]{3,}\s*$|\s*:{2,}\w)/.test(lines[i])) {
|
|
2412
|
+
paraLines.push(lines[i]);
|
|
2413
|
+
i++;
|
|
2414
|
+
}
|
|
2415
|
+
if (paraLines.length) blocks.push({
|
|
2416
|
+
type: "paragraph",
|
|
2417
|
+
text: paraLines.join(" ")
|
|
2418
|
+
});
|
|
2419
|
+
else {
|
|
2420
|
+
blocks.push({
|
|
2421
|
+
type: "paragraph",
|
|
2422
|
+
text: line
|
|
2423
|
+
});
|
|
2424
|
+
i++;
|
|
2425
|
+
}
|
|
2971
2426
|
}
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
}
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
2427
|
+
return blocks;
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Insert formatted inline tokens into an already-attached Y.XmlElement.
|
|
2431
|
+
* Creates one Y.XmlText per token (attach first, fill second).
|
|
2432
|
+
*/
|
|
2433
|
+
function fillTextInto(el, tokens) {
|
|
2434
|
+
const filtered = tokens.filter((t) => t.text.length > 0);
|
|
2435
|
+
if (!filtered.length) return;
|
|
2436
|
+
const children = filtered.map((tok) => {
|
|
2437
|
+
return (tok.attrs?.docLink)?.docId ? new yjs.XmlElement("docLink") : new yjs.XmlText();
|
|
2438
|
+
});
|
|
2439
|
+
el.insert(0, children);
|
|
2440
|
+
filtered.forEach((tok, i) => {
|
|
2441
|
+
const node = children[i];
|
|
2442
|
+
if (node instanceof yjs.XmlElement) {
|
|
2443
|
+
const dl = tok.attrs.docLink;
|
|
2444
|
+
node.setAttribute("docId", dl.docId);
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
if (tok.attrs) node.insert(0, tok.text, tok.attrs);
|
|
2448
|
+
else node.insert(0, tok.text);
|
|
2449
|
+
});
|
|
2450
|
+
}
|
|
2451
|
+
function blockElName(b) {
|
|
2452
|
+
switch (b.type) {
|
|
2453
|
+
case "heading": return "heading";
|
|
2454
|
+
case "paragraph": return "paragraph";
|
|
2455
|
+
case "bulletList": return "bulletList";
|
|
2456
|
+
case "orderedList": return "orderedList";
|
|
2457
|
+
case "taskList": return "taskList";
|
|
2458
|
+
case "codeBlock": return "codeBlock";
|
|
2459
|
+
case "blockquote": return "blockquote";
|
|
2460
|
+
case "table": return "table";
|
|
2461
|
+
case "hr": return "horizontalRule";
|
|
2462
|
+
case "callout": return "callout";
|
|
2463
|
+
case "collapsible": return "collapsible";
|
|
2464
|
+
case "steps": return "steps";
|
|
2465
|
+
case "card": return "card";
|
|
2466
|
+
case "cardGroup": return "cardGroup";
|
|
2467
|
+
case "codeCollapse": return "codeCollapse";
|
|
2468
|
+
case "codeGroup": return "codeGroup";
|
|
2469
|
+
case "codePreview": return "codePreview";
|
|
2470
|
+
case "codeTree": return "codeTree";
|
|
2471
|
+
case "accordion": return "accordion";
|
|
2472
|
+
case "tabs": return "tabs";
|
|
2473
|
+
case "field": return "field";
|
|
2474
|
+
case "fieldGroup": return "fieldGroup";
|
|
2475
|
+
case "image": return "image";
|
|
2476
|
+
case "docEmbed": return "docEmbed";
|
|
2477
|
+
case "mathBlock": return "mathBlock";
|
|
2478
|
+
case "fileBlock": return "fileBlock";
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
function populateListItemChildren(itemEl, item, _itemKind) {
|
|
2482
|
+
const paraEl = new yjs.XmlElement("paragraph");
|
|
2483
|
+
itemEl.insert(itemEl.length, [paraEl]);
|
|
2484
|
+
fillTextInto(paraEl, parseInline(item.text));
|
|
2485
|
+
if (!item.innerBlocks?.length) return;
|
|
2486
|
+
const innerEls = item.innerBlocks.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2487
|
+
itemEl.insert(itemEl.length, innerEls);
|
|
2488
|
+
item.innerBlocks.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2489
|
+
}
|
|
2490
|
+
function fillBlock(el, block) {
|
|
2491
|
+
switch (block.type) {
|
|
2492
|
+
case "heading":
|
|
2493
|
+
el.setAttribute("level", block.level);
|
|
2494
|
+
fillTextInto(el, parseInline(block.text));
|
|
2495
|
+
break;
|
|
2496
|
+
case "paragraph":
|
|
2497
|
+
fillTextInto(el, parseInline(block.text));
|
|
2498
|
+
break;
|
|
2499
|
+
case "bulletList":
|
|
2500
|
+
case "orderedList": {
|
|
2501
|
+
const listItemEls = block.items.map(() => new yjs.XmlElement("listItem"));
|
|
2502
|
+
el.insert(0, listItemEls);
|
|
2503
|
+
block.items.forEach((item, i) => {
|
|
2504
|
+
populateListItemChildren(listItemEls[i], item, "listItem");
|
|
2505
|
+
});
|
|
2506
|
+
break;
|
|
2507
|
+
}
|
|
2508
|
+
case "taskList": {
|
|
2509
|
+
const taskItemEls = block.items.map(() => new yjs.XmlElement("taskItem"));
|
|
2510
|
+
el.insert(0, taskItemEls);
|
|
2511
|
+
block.items.forEach((item, i) => {
|
|
2512
|
+
taskItemEls[i].setAttribute("checked", !!item.checked);
|
|
2513
|
+
populateListItemChildren(taskItemEls[i], item, "taskItem");
|
|
2514
|
+
});
|
|
2515
|
+
break;
|
|
2516
|
+
}
|
|
2517
|
+
case "codeBlock": {
|
|
2518
|
+
if (block.lang) el.setAttribute("language", block.lang);
|
|
2519
|
+
const xt = new yjs.XmlText();
|
|
2520
|
+
el.insert(0, [xt]);
|
|
2521
|
+
xt.insert(0, block.code);
|
|
2522
|
+
break;
|
|
2523
|
+
}
|
|
2524
|
+
case "blockquote": {
|
|
2525
|
+
const paraEls = block.lines.map(() => new yjs.XmlElement("paragraph"));
|
|
2526
|
+
el.insert(0, paraEls);
|
|
2527
|
+
block.lines.forEach((line, i) => fillTextInto(paraEls[i], parseInline(line)));
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
2530
|
+
case "table": {
|
|
2531
|
+
const headerRowEl = new yjs.XmlElement("tableRow");
|
|
2532
|
+
const dataRowEls = block.dataRows.map(() => new yjs.XmlElement("tableRow"));
|
|
2533
|
+
el.insert(0, [headerRowEl, ...dataRowEls]);
|
|
2534
|
+
const headerCellEls = block.headerRow.map(() => new yjs.XmlElement("tableHeader"));
|
|
2535
|
+
headerRowEl.insert(0, headerCellEls);
|
|
2536
|
+
block.headerRow.forEach((cellText, i) => {
|
|
2537
|
+
const paraEl = new yjs.XmlElement("paragraph");
|
|
2538
|
+
headerCellEls[i].insert(0, [paraEl]);
|
|
2539
|
+
fillTextInto(paraEl, parseInline(cellText));
|
|
2540
|
+
});
|
|
2541
|
+
block.dataRows.forEach((row, ri) => {
|
|
2542
|
+
const cellEls = row.map(() => new yjs.XmlElement("tableCell"));
|
|
2543
|
+
dataRowEls[ri].insert(0, cellEls);
|
|
2544
|
+
row.forEach((cellText, ci) => {
|
|
2545
|
+
const paraEl = new yjs.XmlElement("paragraph");
|
|
2546
|
+
cellEls[ci].insert(0, [paraEl]);
|
|
2547
|
+
fillTextInto(paraEl, parseInline(cellText));
|
|
2548
|
+
});
|
|
2549
|
+
});
|
|
2550
|
+
break;
|
|
2551
|
+
}
|
|
2552
|
+
case "hr": break;
|
|
2553
|
+
case "callout": {
|
|
2554
|
+
el.setAttribute("type", block.calloutType);
|
|
2555
|
+
if (!block.innerBlocks.length) {
|
|
2556
|
+
const paraEl = new yjs.XmlElement("paragraph");
|
|
2557
|
+
el.insert(0, [paraEl]);
|
|
2558
|
+
break;
|
|
2559
|
+
}
|
|
2560
|
+
const innerEls = block.innerBlocks.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2561
|
+
el.insert(0, innerEls);
|
|
2562
|
+
block.innerBlocks.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2563
|
+
break;
|
|
2564
|
+
}
|
|
2565
|
+
case "collapsible": {
|
|
2566
|
+
el.setAttribute("label", block.label);
|
|
2567
|
+
el.setAttribute("open", block.open);
|
|
2568
|
+
const inner = block.innerBlocks.length ? block.innerBlocks : [{
|
|
2569
|
+
type: "paragraph",
|
|
2570
|
+
text: ""
|
|
2571
|
+
}];
|
|
2572
|
+
const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2573
|
+
el.insert(0, innerEls);
|
|
2574
|
+
inner.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2575
|
+
break;
|
|
2576
|
+
}
|
|
2577
|
+
case "steps": {
|
|
2578
|
+
const inner = block.innerBlocks.length ? block.innerBlocks : [{
|
|
2579
|
+
type: "paragraph",
|
|
2580
|
+
text: ""
|
|
2581
|
+
}];
|
|
2582
|
+
const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2583
|
+
el.insert(0, innerEls);
|
|
2584
|
+
inner.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2585
|
+
break;
|
|
2586
|
+
}
|
|
2587
|
+
case "card": {
|
|
2588
|
+
if (block.title) el.setAttribute("title", block.title);
|
|
2589
|
+
if (block.icon) el.setAttribute("icon", block.icon);
|
|
2590
|
+
if (block.to) el.setAttribute("to", block.to);
|
|
2591
|
+
const inner = block.innerBlocks.length ? block.innerBlocks : [{
|
|
2592
|
+
type: "paragraph",
|
|
2593
|
+
text: ""
|
|
2594
|
+
}];
|
|
2595
|
+
const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2596
|
+
el.insert(0, innerEls);
|
|
2597
|
+
inner.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2598
|
+
break;
|
|
2599
|
+
}
|
|
2600
|
+
case "cardGroup": {
|
|
2601
|
+
const cardEls = block.cards.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2602
|
+
el.insert(0, cardEls);
|
|
2603
|
+
block.cards.forEach((b, i) => fillBlock(cardEls[i], b));
|
|
2604
|
+
break;
|
|
2605
|
+
}
|
|
2606
|
+
case "codeCollapse": {
|
|
2607
|
+
const codes = block.codeBlocks.length ? block.codeBlocks : [{
|
|
2608
|
+
type: "codeBlock",
|
|
2609
|
+
lang: "",
|
|
2610
|
+
code: ""
|
|
2611
|
+
}];
|
|
2612
|
+
const codeEl = new yjs.XmlElement("codeBlock");
|
|
2613
|
+
el.insert(0, [codeEl]);
|
|
2614
|
+
fillBlock(codeEl, codes[0]);
|
|
2615
|
+
break;
|
|
2616
|
+
}
|
|
2617
|
+
case "codeGroup": {
|
|
2618
|
+
const codes = block.codeBlocks.length ? block.codeBlocks : [{
|
|
2619
|
+
type: "codeBlock",
|
|
2620
|
+
lang: "",
|
|
2621
|
+
code: ""
|
|
2622
|
+
}];
|
|
2623
|
+
const codeEls = codes.map(() => new yjs.XmlElement("codeBlock"));
|
|
2624
|
+
el.insert(0, codeEls);
|
|
2625
|
+
codes.forEach((b, i) => fillBlock(codeEls[i], b));
|
|
2626
|
+
break;
|
|
2627
|
+
}
|
|
2628
|
+
case "codePreview": {
|
|
2629
|
+
const all = [...block.innerBlocks, ...block.codeBlocks];
|
|
2630
|
+
const inner = all.length ? all : [{
|
|
2631
|
+
type: "paragraph",
|
|
2632
|
+
text: ""
|
|
2633
|
+
}];
|
|
2634
|
+
const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2635
|
+
el.insert(0, innerEls);
|
|
2636
|
+
inner.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2637
|
+
break;
|
|
2638
|
+
}
|
|
2639
|
+
case "codeTree":
|
|
2640
|
+
el.setAttribute("files", block.files);
|
|
2641
|
+
break;
|
|
2642
|
+
case "accordion": {
|
|
2643
|
+
const itemEls = block.items.map(() => new yjs.XmlElement("accordionItem"));
|
|
2644
|
+
el.insert(0, itemEls);
|
|
2645
|
+
block.items.forEach((item, i) => {
|
|
2646
|
+
itemEls[i].setAttribute("label", item.label);
|
|
2647
|
+
if (item.icon) itemEls[i].setAttribute("icon", item.icon);
|
|
2648
|
+
const inner = item.innerBlocks.length ? item.innerBlocks : [{
|
|
2649
|
+
type: "paragraph",
|
|
2650
|
+
text: ""
|
|
2651
|
+
}];
|
|
2652
|
+
const childEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2653
|
+
itemEls[i].insert(0, childEls);
|
|
2654
|
+
inner.forEach((b, ci) => fillBlock(childEls[ci], b));
|
|
2655
|
+
});
|
|
2656
|
+
break;
|
|
2657
|
+
}
|
|
2658
|
+
case "tabs": {
|
|
2659
|
+
const itemEls = block.items.map(() => new yjs.XmlElement("tabsItem"));
|
|
2660
|
+
el.insert(0, itemEls);
|
|
2661
|
+
block.items.forEach((item, i) => {
|
|
2662
|
+
itemEls[i].setAttribute("label", item.label);
|
|
2663
|
+
if (item.icon) itemEls[i].setAttribute("icon", item.icon);
|
|
2664
|
+
const inner = item.innerBlocks.length ? item.innerBlocks : [{
|
|
2665
|
+
type: "paragraph",
|
|
2666
|
+
text: ""
|
|
2667
|
+
}];
|
|
2668
|
+
const childEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2669
|
+
itemEls[i].insert(0, childEls);
|
|
2670
|
+
inner.forEach((b, ci) => fillBlock(childEls[ci], b));
|
|
2671
|
+
});
|
|
2672
|
+
break;
|
|
2673
|
+
}
|
|
2674
|
+
case "field": {
|
|
2675
|
+
if (block.name) el.setAttribute("name", block.name);
|
|
2676
|
+
el.setAttribute("type", block.fieldType);
|
|
2677
|
+
el.setAttribute("required", block.required);
|
|
2678
|
+
const inner = block.innerBlocks.length ? block.innerBlocks : [{
|
|
2679
|
+
type: "paragraph",
|
|
2680
|
+
text: ""
|
|
2681
|
+
}];
|
|
2682
|
+
const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2683
|
+
el.insert(0, innerEls);
|
|
2684
|
+
inner.forEach((b, i) => fillBlock(innerEls[i], b));
|
|
2685
|
+
break;
|
|
2686
|
+
}
|
|
2687
|
+
case "fieldGroup": {
|
|
2688
|
+
const fieldEls = block.fields.map((b) => new yjs.XmlElement(blockElName(b)));
|
|
2689
|
+
el.insert(0, fieldEls);
|
|
2690
|
+
block.fields.forEach((b, i) => fillBlock(fieldEls[i], b));
|
|
2691
|
+
break;
|
|
2692
|
+
}
|
|
2693
|
+
case "image":
|
|
2694
|
+
el.setAttribute("src", block.src);
|
|
2695
|
+
if (block.alt) el.setAttribute("alt", block.alt);
|
|
2696
|
+
if (block.width) el.setAttribute("width", block.width);
|
|
2697
|
+
if (block.height) el.setAttribute("height", block.height);
|
|
2698
|
+
break;
|
|
2699
|
+
case "docEmbed":
|
|
2700
|
+
el.setAttribute("docId", block.docId);
|
|
2701
|
+
for (const flag of [
|
|
2702
|
+
"collapsed",
|
|
2703
|
+
"tall",
|
|
2704
|
+
"seamless"
|
|
2705
|
+
]) if (block.props[flag] === "true" || block.props[flag] === "1") el.setAttribute(flag, true);
|
|
2706
|
+
break;
|
|
2707
|
+
case "mathBlock":
|
|
2708
|
+
el.setAttribute("expression", block.expression);
|
|
2709
|
+
break;
|
|
2710
|
+
case "fileBlock":
|
|
2711
|
+
if (block.src) el.setAttribute("src", block.src);
|
|
2712
|
+
if (block.mime) el.setAttribute("mime", block.mime);
|
|
2713
|
+
if (block.uploadId) el.setAttribute("uploadId", block.uploadId);
|
|
2714
|
+
if (block.filename) el.setAttribute("filename", block.filename);
|
|
2715
|
+
break;
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
/**
|
|
2719
|
+
* Parses markdown text and writes the result into a Y.XmlFragment that
|
|
2720
|
+
* TipTap's Collaboration extension can read.
|
|
2721
|
+
*
|
|
2722
|
+
* Requires `fragment.doc` to be set (i.e. the fragment must already be
|
|
2723
|
+
* obtained from a live Y.Doc via `ydoc.getXmlFragment('default')`).
|
|
2724
|
+
*
|
|
2725
|
+
* @param fragment The target `Y.Doc.getXmlFragment('default')`
|
|
2726
|
+
* @param markdown Raw markdown string
|
|
2727
|
+
* @param fallbackTitle Used as the title when the markdown has no H1
|
|
2728
|
+
*/
|
|
2729
|
+
function populateYDocFromMarkdown(fragment, markdown, fallbackTitle = "Untitled") {
|
|
2730
|
+
const ydoc = fragment.doc;
|
|
2731
|
+
if (!ydoc) {
|
|
2732
|
+
console.warn("[markdownToYjs] fragment has no doc — skipping population");
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
const fm = parseFrontmatter(markdown);
|
|
2736
|
+
const blocks = parseBlocks(fm.body);
|
|
2737
|
+
let title = fallbackTitle;
|
|
2738
|
+
let titleSource;
|
|
2739
|
+
if (fm.title !== void 0) {
|
|
2740
|
+
title = fm.title;
|
|
2741
|
+
titleSource = "frontmatter";
|
|
2742
|
+
}
|
|
2743
|
+
let contentBlocks = blocks;
|
|
2744
|
+
const h1 = blocks.findIndex((b) => b.type === "heading" && b.level === 1);
|
|
2745
|
+
if (h1 !== -1) {
|
|
2746
|
+
title = blocks[h1].text;
|
|
2747
|
+
contentBlocks = blocks.filter((_, i) => i !== h1);
|
|
2748
|
+
titleSource = "h1";
|
|
2749
|
+
}
|
|
2750
|
+
ydoc.transact(() => {
|
|
2751
|
+
const headerEl = new yjs.XmlElement("documentHeader");
|
|
2752
|
+
const metaEl = new yjs.XmlElement("documentMeta");
|
|
2753
|
+
const bodyEls = contentBlocks.map((b) => {
|
|
2754
|
+
switch (b.type) {
|
|
2755
|
+
case "heading": return new yjs.XmlElement("heading");
|
|
2756
|
+
case "paragraph": return new yjs.XmlElement("paragraph");
|
|
2757
|
+
case "bulletList": return new yjs.XmlElement("bulletList");
|
|
2758
|
+
case "orderedList": return new yjs.XmlElement("orderedList");
|
|
2759
|
+
case "taskList": return new yjs.XmlElement("taskList");
|
|
2760
|
+
case "codeBlock": return new yjs.XmlElement("codeBlock");
|
|
2761
|
+
case "blockquote": return new yjs.XmlElement("blockquote");
|
|
2762
|
+
case "table": return new yjs.XmlElement("table");
|
|
2763
|
+
case "hr": return new yjs.XmlElement("horizontalRule");
|
|
2764
|
+
case "callout": return new yjs.XmlElement("callout");
|
|
2765
|
+
case "collapsible": return new yjs.XmlElement("collapsible");
|
|
2766
|
+
case "steps": return new yjs.XmlElement("steps");
|
|
2767
|
+
case "card": return new yjs.XmlElement("card");
|
|
2768
|
+
case "cardGroup": return new yjs.XmlElement("cardGroup");
|
|
2769
|
+
case "codeCollapse": return new yjs.XmlElement("codeCollapse");
|
|
2770
|
+
case "codeGroup": return new yjs.XmlElement("codeGroup");
|
|
2771
|
+
case "codePreview": return new yjs.XmlElement("codePreview");
|
|
2772
|
+
case "codeTree": return new yjs.XmlElement("codeTree");
|
|
2773
|
+
case "accordion": return new yjs.XmlElement("accordion");
|
|
2774
|
+
case "tabs": return new yjs.XmlElement("tabs");
|
|
2775
|
+
case "field": return new yjs.XmlElement("field");
|
|
2776
|
+
case "fieldGroup": return new yjs.XmlElement("fieldGroup");
|
|
2777
|
+
case "image": return new yjs.XmlElement("image");
|
|
2778
|
+
case "docEmbed": return new yjs.XmlElement("docEmbed");
|
|
2779
|
+
case "mathBlock": return new yjs.XmlElement("mathBlock");
|
|
2780
|
+
case "fileBlock": return new yjs.XmlElement("fileBlock");
|
|
2781
|
+
}
|
|
2782
|
+
});
|
|
2783
|
+
fragment.insert(0, [
|
|
2784
|
+
headerEl,
|
|
2785
|
+
metaEl,
|
|
2786
|
+
...bodyEls
|
|
2787
|
+
]);
|
|
2788
|
+
if (titleSource) headerEl.setAttribute("titleSource", titleSource);
|
|
2789
|
+
const headerXt = new yjs.XmlText();
|
|
2790
|
+
headerEl.insert(0, [headerXt]);
|
|
2791
|
+
headerXt.insert(0, title);
|
|
2792
|
+
for (const k of Object.keys(fm.meta)) {
|
|
2793
|
+
const v = fm.meta[k];
|
|
2794
|
+
if (v === void 0 || v === null) continue;
|
|
2795
|
+
metaEl.setAttribute(k, v);
|
|
2796
|
+
}
|
|
2797
|
+
if (fm.type) metaEl.setAttribute("type", fm.type);
|
|
2798
|
+
contentBlocks.forEach((block, i) => fillBlock(bodyEls[i], block));
|
|
2799
|
+
});
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
//#endregion
|
|
2803
|
+
//#region packages/convert/src/yjs-to-markdown.ts
|
|
2804
|
+
function isXElem(n) {
|
|
2805
|
+
return !!n && typeof n.nodeName === "string";
|
|
2806
|
+
}
|
|
2807
|
+
function isXText(n) {
|
|
2808
|
+
return !!n && typeof n.nodeName !== "string" && typeof n.toDelta === "function";
|
|
2809
|
+
}
|
|
2810
|
+
function localizeFragment(fragment) {
|
|
2811
|
+
return fragment;
|
|
2812
|
+
}
|
|
2813
|
+
function serializeDelta(delta) {
|
|
2814
|
+
let result = "";
|
|
2815
|
+
for (const op of delta) {
|
|
2816
|
+
if (typeof op.insert !== "string") continue;
|
|
2817
|
+
let text = op.insert;
|
|
2818
|
+
const attrs = op.attributes ?? {};
|
|
2819
|
+
if (attrs.code) {
|
|
2820
|
+
result += `\`${text}\``;
|
|
2821
|
+
continue;
|
|
2822
|
+
}
|
|
2823
|
+
if (attrs.badge) {
|
|
2824
|
+
const b = attrs.badge;
|
|
2825
|
+
const props = [];
|
|
2826
|
+
if (b.color && b.color !== "neutral") props.push(`color="${b.color}"`);
|
|
2827
|
+
if (b.variant && b.variant !== "subtle") props.push(`variant="${b.variant}"`);
|
|
2828
|
+
result += `:badge[${b.label || text}]${props.length ? `{${props.join(" ")}}` : ""}`;
|
|
2829
|
+
continue;
|
|
2830
|
+
}
|
|
2831
|
+
if (attrs.proseIcon) {
|
|
2832
|
+
const icon = attrs.proseIcon.name || "i-lucide-star";
|
|
2833
|
+
result += `:icon{name="${icon}"}`;
|
|
2834
|
+
continue;
|
|
2835
|
+
}
|
|
2836
|
+
if (attrs.kbd) {
|
|
2837
|
+
const value = attrs.kbd.value || text;
|
|
2838
|
+
result += `:kbd{value="${value}"}`;
|
|
2839
|
+
continue;
|
|
2840
|
+
}
|
|
2841
|
+
if (attrs.docLink) {
|
|
2842
|
+
const docId = attrs.docLink.docId;
|
|
2843
|
+
if (docId) {
|
|
2844
|
+
result += text === docId ? `[[${docId}]]` : `[[${docId}|${text}]]`;
|
|
2845
|
+
continue;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
if (attrs.mention) {
|
|
2849
|
+
const { userId, label } = attrs.mention;
|
|
2850
|
+
if (userId) {
|
|
2851
|
+
result += `@[${label || text}](user:${userId})`;
|
|
2852
|
+
continue;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
if (attrs.mathInline) {
|
|
2856
|
+
const expr = attrs.mathInline.expression ?? text;
|
|
2857
|
+
result += `$${expr}$`;
|
|
2858
|
+
continue;
|
|
2859
|
+
}
|
|
2860
|
+
if (attrs.bold) text = `**${text}**`;
|
|
2861
|
+
if (attrs.italic) text = `*${text}*`;
|
|
2862
|
+
if (attrs.strike) text = `~~${text}~~`;
|
|
2863
|
+
if (attrs.link) {
|
|
2864
|
+
const href = attrs.link.href ?? "";
|
|
2865
|
+
text = `[${text}](${href})`;
|
|
2866
|
+
}
|
|
2867
|
+
result += text;
|
|
2868
|
+
}
|
|
2869
|
+
return result;
|
|
2870
|
+
}
|
|
2871
|
+
function serializeInline(el) {
|
|
2872
|
+
const parts = [];
|
|
2873
|
+
for (const child of el.toArray()) if (isXText(child)) parts.push(serializeDelta(child.toDelta()));
|
|
2874
|
+
else if (isXElem(child)) if (child.nodeName === "docLink") {
|
|
2875
|
+
const docId = child.getAttribute("docId") ?? "";
|
|
2876
|
+
parts.push(`[[${docId}]]`);
|
|
2877
|
+
} else parts.push(serializeInline(child));
|
|
2878
|
+
return parts.join("");
|
|
2879
|
+
}
|
|
2880
|
+
function serializeBlock(el, indent = "") {
|
|
2881
|
+
if (isXText(el)) return serializeDelta(el.toDelta());
|
|
2882
|
+
switch (el.nodeName) {
|
|
2883
|
+
case "documentHeader":
|
|
2884
|
+
case "documentMeta": return "";
|
|
2885
|
+
case "heading": {
|
|
2886
|
+
const level = Number(el.getAttribute("level") ?? 2);
|
|
2887
|
+
return `${"#".repeat(level)} ${serializeInline(el)}`;
|
|
2888
|
+
}
|
|
2889
|
+
case "paragraph": return serializeInline(el);
|
|
2890
|
+
case "bulletList": return serializeListItems(el, "bullet", indent);
|
|
2891
|
+
case "orderedList": return serializeListItems(el, "ordered", indent);
|
|
2892
|
+
case "taskList": return serializeTaskList(el, indent);
|
|
2893
|
+
case "codeBlock": {
|
|
2894
|
+
const lang = el.getAttribute("language") ?? "";
|
|
2895
|
+
const code = getCodeBlockText(el);
|
|
2896
|
+
if (code === "") return `\`\`\`${lang}\n\`\`\``;
|
|
2897
|
+
return `\`\`\`${lang}\n${code}\n\`\`\``;
|
|
2898
|
+
}
|
|
2899
|
+
case "blockquote": {
|
|
2900
|
+
const lines = [];
|
|
2901
|
+
for (const child of el.toArray()) if (isXElem(child)) {
|
|
2902
|
+
const text = serializeBlock(child);
|
|
2903
|
+
for (const line of text.split("\n")) lines.push(`> ${line}`);
|
|
2904
|
+
}
|
|
2905
|
+
return lines.join("\n");
|
|
2906
|
+
}
|
|
2907
|
+
case "table": return serializeTable(el);
|
|
2908
|
+
case "horizontalRule": return "---";
|
|
2909
|
+
case "image": {
|
|
2910
|
+
const src = el.getAttribute("src") ?? "";
|
|
2911
|
+
const alt = el.getAttribute("alt") ?? "";
|
|
2912
|
+
const width = el.getAttribute("width");
|
|
2913
|
+
const height = el.getAttribute("height");
|
|
2914
|
+
const attrs = [];
|
|
2915
|
+
if (width) attrs.push(`width=${width}`);
|
|
2916
|
+
if (height) attrs.push(`height=${height}`);
|
|
2917
|
+
return `${attrs.length ? `{${attrs.join(" ")}}` : ""}`;
|
|
2918
|
+
}
|
|
2919
|
+
case "docEmbed": {
|
|
2920
|
+
const docId = el.getAttribute("docId") ?? "";
|
|
2921
|
+
const collapsed = el.getAttribute("collapsed");
|
|
2922
|
+
const tall = el.getAttribute("tall");
|
|
2923
|
+
const seamless = el.getAttribute("seamless");
|
|
2924
|
+
const flags = [];
|
|
2925
|
+
if (collapsed === true || collapsed === "true") flags.push("collapsed");
|
|
2926
|
+
if (tall === true || tall === "true") flags.push("tall");
|
|
2927
|
+
if (seamless === true || seamless === "true") flags.push("seamless");
|
|
2928
|
+
return `![[${docId}]]${flags.length ? `{${flags.join(" ")}}` : ""}`;
|
|
2929
|
+
}
|
|
2930
|
+
case "mathBlock": return `\`\`\`math\n${el.getAttribute("expression") ?? ""}\n\`\`\``;
|
|
2931
|
+
case "fileBlock": {
|
|
2932
|
+
const uploadId = el.getAttribute("uploadId") ?? "";
|
|
2933
|
+
const filename = el.getAttribute("filename") ?? "";
|
|
2934
|
+
const mime = el.getAttribute("mime") ?? "";
|
|
2935
|
+
const src = el.getAttribute("src") ?? (uploadId && filename ? `.abracadabra/files/${uploadId}-${filename}` : "");
|
|
2936
|
+
const props = [];
|
|
2937
|
+
if (src) props.push(`src="${src}"`);
|
|
2938
|
+
if (mime) props.push(`mime="${mime}"`);
|
|
2939
|
+
if (uploadId) props.push(`upload-id="${uploadId}"`);
|
|
2940
|
+
if (filename) props.push(`filename="${filename}"`);
|
|
2941
|
+
return `:file{${props.join(" ")}}`;
|
|
2942
|
+
}
|
|
2943
|
+
case "callout": return `::${el.getAttribute("type") ?? "note"}\n${serializeChildren(el)}\n::`;
|
|
2944
|
+
case "collapsible": {
|
|
2945
|
+
const label = el.getAttribute("label") ?? "Details";
|
|
2946
|
+
const open = el.getAttribute("open");
|
|
2947
|
+
const props = [`label="${label}"`];
|
|
2948
|
+
if (open === true || open === "true") props.push("open=\"true\"");
|
|
2949
|
+
return `::collapsible{${props.join(" ")}}\n${serializeChildren(el)}\n::`;
|
|
2950
|
+
}
|
|
2951
|
+
case "steps": return `::steps\n${serializeChildren(el)}\n::`;
|
|
2952
|
+
case "card": {
|
|
2953
|
+
const props = [];
|
|
2954
|
+
const title = el.getAttribute("title");
|
|
2955
|
+
const icon = el.getAttribute("icon");
|
|
2956
|
+
const to = el.getAttribute("to");
|
|
2957
|
+
if (title) props.push(`title="${title}"`);
|
|
2958
|
+
if (icon) props.push(`icon="${icon}"`);
|
|
2959
|
+
if (to) props.push(`to="${to}"`);
|
|
2960
|
+
return `::card${props.length ? `{${props.join(" ")}}` : ""}\n${serializeChildren(el)}\n::`;
|
|
2961
|
+
}
|
|
2962
|
+
case "cardGroup": return `::card-group\n${el.toArray().filter((c) => isXElem(c)).map((c) => serializeBlock(c)).join("\n\n")}\n::`;
|
|
2963
|
+
case "codeCollapse": return `::code-collapse\n${el.toArray().filter((c) => isXElem(c) && c.nodeName === "codeBlock").map((c) => serializeBlock(c)).join("\n\n")}\n::`;
|
|
2964
|
+
case "codeGroup": return `::code-group\n${el.toArray().filter((c) => isXElem(c) && c.nodeName === "codeBlock").map((c) => serializeBlock(c)).join("\n\n")}\n::`;
|
|
2965
|
+
case "codePreview": {
|
|
2966
|
+
const children = el.toArray().filter((c) => isXElem(c));
|
|
2967
|
+
const nonCode = children.filter((c) => c.nodeName !== "codeBlock").map((c) => serializeBlock(c)).join("\n\n");
|
|
2968
|
+
const code = children.filter((c) => c.nodeName === "codeBlock").map((c) => serializeBlock(c)).join("\n\n");
|
|
2969
|
+
const parts = [nonCode];
|
|
2970
|
+
if (code) parts.push(`#code\n${code}`);
|
|
2971
|
+
return `::code-preview\n${parts.filter(Boolean).join("\n\n")}\n::`;
|
|
2972
|
+
}
|
|
2973
|
+
case "codeTree": return `::code-tree{files="${el.getAttribute("files") ?? "[]"}"}\n::`;
|
|
2974
|
+
case "accordion": return serializeSlottedContainer(el, "accordion", "accordionItem", "item");
|
|
2975
|
+
case "tabs": return serializeSlottedContainer(el, "tabs", "tabsItem", "tab");
|
|
2976
|
+
case "field": {
|
|
2977
|
+
const fieldName = el.getAttribute("name") ?? "";
|
|
2978
|
+
const fieldType = el.getAttribute("type") ?? "string";
|
|
2979
|
+
const required = el.getAttribute("required");
|
|
2980
|
+
const props = [`name="${fieldName}"`, `type="${fieldType}"`];
|
|
2981
|
+
if (required === true || required === "true") props.push("required=\"true\"");
|
|
2982
|
+
return `::field{${props.join(" ")}}\n${serializeChildren(el)}\n::`;
|
|
2983
|
+
}
|
|
2984
|
+
case "fieldGroup": return `::field-group\n${el.toArray().filter((c) => isXElem(c)).map((c) => serializeBlock(c)).join("\n\n")}\n::`;
|
|
2985
|
+
default: return serializeChildren(el);
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
function serializeChildren(el) {
|
|
2989
|
+
const blocks = [];
|
|
2990
|
+
for (const child of el.toArray()) if (isXElem(child)) {
|
|
2991
|
+
const text = serializeBlock(child);
|
|
2992
|
+
if (text) blocks.push(text);
|
|
2993
|
+
} else if (isXText(child)) {
|
|
2994
|
+
const text = serializeDelta(child.toDelta());
|
|
2995
|
+
if (text) blocks.push(text);
|
|
2996
|
+
}
|
|
2997
|
+
return blocks.join("\n\n");
|
|
2998
|
+
}
|
|
2999
|
+
function serializeListItems(el, type, indent) {
|
|
3000
|
+
const lines = [];
|
|
3001
|
+
let counter = 1;
|
|
3002
|
+
for (const child of el.toArray()) {
|
|
3003
|
+
if (!isXElem(child) || child.nodeName !== "listItem") continue;
|
|
3004
|
+
const prefix = type === "bullet" ? "- " : `${counter++}. `;
|
|
3005
|
+
const subParts = [];
|
|
3006
|
+
for (const sub of child.toArray()) {
|
|
3007
|
+
if (!isXElem(sub)) continue;
|
|
3008
|
+
if (sub.nodeName === "bulletList") subParts.push({
|
|
3009
|
+
text: serializeListItems(sub, "bullet", indent + " "),
|
|
3010
|
+
isList: true
|
|
3011
|
+
});
|
|
3012
|
+
else if (sub.nodeName === "orderedList") subParts.push({
|
|
3013
|
+
text: serializeListItems(sub, "ordered", indent + " "),
|
|
3014
|
+
isList: true
|
|
3015
|
+
});
|
|
3016
|
+
else subParts.push({
|
|
3017
|
+
text: serializeInline(sub),
|
|
3018
|
+
isList: false
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
lines.push(`${indent}${prefix}${subParts[0]?.text ?? ""}`);
|
|
3022
|
+
for (let i = 1; i < subParts.length; i++) {
|
|
3023
|
+
const part = subParts[i];
|
|
3024
|
+
lines.push(part.isList ? part.text : `${indent} ${part.text}`);
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
return lines.join("\n");
|
|
3028
|
+
}
|
|
3029
|
+
function serializeTaskList(el, indent) {
|
|
3030
|
+
const lines = [];
|
|
3031
|
+
for (const child of el.toArray()) {
|
|
3032
|
+
if (!isXElem(child) || child.nodeName !== "taskItem") continue;
|
|
3033
|
+
const checked = child.getAttribute("checked");
|
|
3034
|
+
const marker = checked === true || checked === "true" ? "[x]" : "[ ]";
|
|
3035
|
+
let header = "";
|
|
3036
|
+
const nestedParts = [];
|
|
3037
|
+
for (const sub of child.toArray()) {
|
|
3038
|
+
if (!isXElem(sub)) continue;
|
|
3039
|
+
if (sub.nodeName === "paragraph" && header === "") header = serializeInline(sub);
|
|
3040
|
+
else if (sub.nodeName === "bulletList") nestedParts.push(serializeListItems(sub, "bullet", indent + " "));
|
|
3041
|
+
else if (sub.nodeName === "orderedList") nestedParts.push(serializeListItems(sub, "ordered", indent + " "));
|
|
3042
|
+
else if (sub.nodeName === "taskList") nestedParts.push(serializeTaskList(sub, indent + " "));
|
|
3043
|
+
else nestedParts.push(indent + " " + serializeBlock(sub, indent + " "));
|
|
3044
|
+
}
|
|
3045
|
+
lines.push(`${indent}- ${marker} ${header}`);
|
|
3046
|
+
for (const part of nestedParts) lines.push(part);
|
|
3047
|
+
}
|
|
3048
|
+
return lines.join("\n");
|
|
3049
|
+
}
|
|
3050
|
+
function getCodeBlockText(el) {
|
|
3051
|
+
for (const child of el.toArray()) if (isXText(child)) return child.toString();
|
|
3052
|
+
return "";
|
|
3053
|
+
}
|
|
3054
|
+
function serializeTable(el) {
|
|
3055
|
+
const rows = el.toArray().filter((c) => isXElem(c));
|
|
3056
|
+
if (!rows.length) return "";
|
|
3057
|
+
const serializedRows = [];
|
|
3058
|
+
for (const row of rows) {
|
|
3059
|
+
const cells = row.toArray().filter((c) => isXElem(c)).map((cell) => {
|
|
3060
|
+
return cell.toArray().filter((c) => isXElem(c)).map((c) => serializeInline(c)).join(" ");
|
|
3061
|
+
});
|
|
3062
|
+
serializedRows.push(cells);
|
|
3063
|
+
}
|
|
3064
|
+
if (!serializedRows.length) return "";
|
|
3065
|
+
const colCount = Math.max(...serializedRows.map((r) => r.length));
|
|
3066
|
+
const headerRow = serializedRows[0];
|
|
3067
|
+
const separator = Array(colCount).fill("---");
|
|
3068
|
+
const dataRows = serializedRows.slice(1);
|
|
3069
|
+
const formatRow = (cells) => {
|
|
3070
|
+
return `| ${Array(colCount).fill("").map((_, i) => cells[i] ?? "").join(" | ")} |`;
|
|
3071
|
+
};
|
|
3072
|
+
return [
|
|
3073
|
+
formatRow(headerRow),
|
|
3074
|
+
formatRow(separator),
|
|
3075
|
+
...dataRows.map(formatRow)
|
|
3076
|
+
].join("\n");
|
|
3077
|
+
}
|
|
3078
|
+
function serializeSlottedContainer(el, containerName, childName, slotPrefix) {
|
|
3079
|
+
return `::${containerName}\n${el.toArray().filter((c) => isXElem(c) && c.nodeName === childName).map((item) => {
|
|
3080
|
+
const label = item.getAttribute("label") ?? "";
|
|
3081
|
+
const icon = item.getAttribute("icon") ?? "";
|
|
3082
|
+
const props = [];
|
|
3083
|
+
if (label) props.push(`label="${label}"`);
|
|
3084
|
+
if (icon) props.push(`icon="${icon}"`);
|
|
3085
|
+
const content = serializeChildren(item);
|
|
3086
|
+
return `#${slotPrefix}{${props.join(" ")}}\n${content}`;
|
|
3087
|
+
}).join("\n\n")}\n::`;
|
|
3088
|
+
}
|
|
3089
|
+
/**
|
|
3090
|
+
* Keys the hand-rolled canonical section below emits itself. The generic
|
|
3091
|
+
* spec-driven pass must skip these so each key is serialised exactly once,
|
|
3092
|
+
* in the canonical order existing fixtures depend on.
|
|
3093
|
+
*/
|
|
3094
|
+
const CANONICAL_FM_KEYS = new Set([
|
|
3095
|
+
"title",
|
|
3096
|
+
"type",
|
|
3097
|
+
"tags",
|
|
3098
|
+
"color",
|
|
3099
|
+
"icon",
|
|
3100
|
+
"status",
|
|
3101
|
+
"priority",
|
|
3102
|
+
"checked",
|
|
3103
|
+
"language",
|
|
3104
|
+
"fileExtension",
|
|
3105
|
+
"codeTheme",
|
|
3106
|
+
"dateStart",
|
|
3107
|
+
"dateEnd",
|
|
3108
|
+
"subtitle",
|
|
3109
|
+
"url",
|
|
3110
|
+
"rating"
|
|
3111
|
+
]);
|
|
3112
|
+
/** Render one meta value as a YAML line, or null when it can't be emitted. */
|
|
3113
|
+
function yamlLine(key, v, specType) {
|
|
3114
|
+
if (v === void 0 || v === null) return null;
|
|
3115
|
+
if (Array.isArray(v)) {
|
|
3116
|
+
if (v.length === 0) return null;
|
|
3117
|
+
return `${key}: [${v.map((x) => String(x)).join(", ")}]`;
|
|
3118
|
+
}
|
|
3119
|
+
if (typeof v === "number") return Number.isFinite(v) ? `${key}: ${v}` : null;
|
|
3120
|
+
if (typeof v === "boolean") return `${key}: ${v}`;
|
|
3121
|
+
if (typeof v === "string") return v === "" ? null : `${key}: ${yamlScalar(v)}`;
|
|
3122
|
+
if (specType === "members" || specType === "json" || typeof v === "object") try {
|
|
3123
|
+
const json = JSON.stringify(v);
|
|
3124
|
+
return json.includes("'") ? null : `${key}: '${json}'`;
|
|
3125
|
+
} catch {
|
|
3126
|
+
return null;
|
|
3127
|
+
}
|
|
3128
|
+
return null;
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* Generate the YAML frontmatter block. Returns '' when there is nothing to
|
|
3132
|
+
* emit, so callers can skip the block entirely — an empty `---\n\n---` shell
|
|
3133
|
+
* is never produced (docs whose meta holds only internal `_`-keys used to
|
|
3134
|
+
* export as exactly that junk).
|
|
3135
|
+
*
|
|
3136
|
+
* Emission order: the long-standing hand-rolled canonical keys first (byte
|
|
3137
|
+
* stability for existing files), then every remaining universal-meta key in
|
|
3138
|
+
* registry order, then custom keys alphabetically. Internal keys (leading
|
|
3139
|
+
* `_`, e.g. `_metaInitialized`) are never serialised.
|
|
3140
|
+
*/
|
|
3141
|
+
function generateFrontmatter(label, meta, type) {
|
|
3142
|
+
const lines = [];
|
|
3143
|
+
if (label !== void 0) lines.push(`title: "${escapeYaml(label)}"`);
|
|
3144
|
+
if (type && type !== "doc") lines.push(`type: ${type}`);
|
|
3145
|
+
if (meta) {
|
|
3146
|
+
if (meta.tags?.length) lines.push(`tags: [${meta.tags.join(", ")}]`);
|
|
3147
|
+
if (meta.color) lines.push(`color: ${yamlScalar(meta.color)}`);
|
|
3148
|
+
if (meta.icon) lines.push(`icon: ${yamlScalar(meta.icon)}`);
|
|
3149
|
+
if (meta.status) lines.push(`status: ${yamlScalar(meta.status)}`);
|
|
3150
|
+
if (meta.priority !== void 0 && meta.priority !== 0) lines.push(`priority: ${{
|
|
3151
|
+
1: "low",
|
|
3152
|
+
2: "medium",
|
|
3153
|
+
3: "high",
|
|
3154
|
+
4: "urgent"
|
|
3155
|
+
}[meta.priority] ?? meta.priority}`);
|
|
3156
|
+
if (meta.checked !== void 0) lines.push(`checked: ${meta.checked}`);
|
|
3157
|
+
if (meta.language) lines.push(`language: ${yamlScalar(meta.language)}`);
|
|
3158
|
+
if (meta.fileExtension) lines.push(`fileExtension: ${yamlScalar(meta.fileExtension)}`);
|
|
3159
|
+
if (meta.codeTheme) lines.push(`codeTheme: ${yamlScalar(meta.codeTheme)}`);
|
|
3160
|
+
if (meta.dateStart) lines.push(`dateStart: "${escapeYaml(meta.dateStart)}"`);
|
|
3161
|
+
if (meta.dateEnd) lines.push(`dateEnd: "${escapeYaml(meta.dateEnd)}"`);
|
|
3162
|
+
if (meta.subtitle) lines.push(`subtitle: "${escapeYaml(meta.subtitle)}"`);
|
|
3163
|
+
if (meta.url) lines.push(`url: ${meta.url}`);
|
|
3164
|
+
if (meta.rating !== void 0 && meta.rating !== 0) lines.push(`rating: ${meta.rating}`);
|
|
3165
|
+
const m = meta;
|
|
3166
|
+
for (const spec of UNIVERSAL_META_KEYS) {
|
|
3167
|
+
if (CANONICAL_FM_KEYS.has(spec.key)) continue;
|
|
3168
|
+
const line = yamlLine(spec.key, m[spec.key], spec.type);
|
|
3169
|
+
if (line) lines.push(line);
|
|
3170
|
+
}
|
|
3171
|
+
const customKeys = Object.keys(m).filter((k) => !k.startsWith("_") && !CANONICAL_FM_KEYS.has(k) && !UNIVERSAL_META_KEY_NAMES.has(k)).sort();
|
|
3172
|
+
for (const k of customKeys) {
|
|
3173
|
+
const line = yamlLine(k, m[k]);
|
|
3174
|
+
if (line) lines.push(line);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
if (lines.length === 0) return "";
|
|
3178
|
+
return `---\n${lines.join("\n")}\n---`;
|
|
3179
|
+
}
|
|
3180
|
+
/**
|
|
3181
|
+
* Render a YAML scalar — bare when safe, double-quoted when the value
|
|
3182
|
+
* needs escaping. YAML treats `#`, `:`, leading whitespace, and a few
|
|
3183
|
+
* other characters as syntactically significant, so anything starting
|
|
3184
|
+
* with one of those gets quoted to stay round-trip safe.
|
|
3185
|
+
*/
|
|
3186
|
+
function yamlScalar(s) {
|
|
3187
|
+
if (s === "") return "\"\"";
|
|
3188
|
+
if (/^[#&*!|>%@`]/.test(s)) return `"${escapeYaml(s)}"`;
|
|
3189
|
+
if (/[:"]/.test(s)) return `"${escapeYaml(s)}"`;
|
|
3190
|
+
if (/^\s|\s$/.test(s)) return `"${escapeYaml(s)}"`;
|
|
3191
|
+
return s;
|
|
3192
|
+
}
|
|
3193
|
+
function escapeYaml(s) {
|
|
3194
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
3195
|
+
}
|
|
3196
|
+
function yjsToMarkdown(fragment, label, meta, type) {
|
|
3197
|
+
fragment = localizeFragment(fragment);
|
|
3198
|
+
const { text: headerText, source: titleSource } = readDocumentHeader(fragment);
|
|
3199
|
+
const effectiveTitle = headerText || label;
|
|
3200
|
+
const docMeta = readDocumentMeta(fragment);
|
|
3201
|
+
const effectiveMeta = meta ?? docMeta.meta;
|
|
3202
|
+
const effectiveType = type ?? docMeta.type;
|
|
3203
|
+
const bodyBlocks = collectBodyBlocks(fragment);
|
|
3204
|
+
let body;
|
|
3205
|
+
if (titleSource === "h1" && effectiveTitle) {
|
|
3206
|
+
const tail = serializeBlocksClean(bodyBlocks);
|
|
3207
|
+
body = tail === "" ? `# ${effectiveTitle}` : `# ${effectiveTitle}\n\n${tail}`;
|
|
3208
|
+
} else body = serializeBlocksClean(bodyBlocks);
|
|
3209
|
+
const frontmatter = generateFrontmatter(titleSource === "frontmatter" ? effectiveTitle : void 0, effectiveMeta, effectiveType);
|
|
3210
|
+
if (frontmatter === "") return body === "" ? "" : `${body}\n`;
|
|
3211
|
+
if (body === "") return `${frontmatter}\n`;
|
|
3212
|
+
return `${frontmatter}\n\n${body}\n`;
|
|
3213
|
+
}
|
|
3214
|
+
function readDocumentMeta(fragment) {
|
|
3215
|
+
const meta = {};
|
|
3216
|
+
let type;
|
|
3217
|
+
for (const child of fragment.toArray()) {
|
|
3218
|
+
if (!isXElem(child) || child.nodeName !== "documentMeta") continue;
|
|
3219
|
+
const attrs = child.getAttributes();
|
|
3220
|
+
for (const k of Object.keys(attrs)) {
|
|
3221
|
+
const v = attrs[k];
|
|
3222
|
+
if (v === void 0 || v === null) continue;
|
|
3223
|
+
if (k === "type" && typeof v === "string") {
|
|
3224
|
+
type = v;
|
|
3225
|
+
continue;
|
|
3226
|
+
}
|
|
3227
|
+
meta[k] = v;
|
|
3228
|
+
}
|
|
3229
|
+
break;
|
|
3230
|
+
}
|
|
3231
|
+
return {
|
|
3232
|
+
meta,
|
|
3233
|
+
type
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
function readDocumentHeader(fragment) {
|
|
3237
|
+
for (const child of fragment.toArray()) {
|
|
3238
|
+
if (!isXElem(child) || child.nodeName !== "documentHeader") continue;
|
|
3239
|
+
const text = child.toArray().find((c) => isXText(c));
|
|
3240
|
+
const src = child.getAttribute("titleSource");
|
|
3241
|
+
const source = src === "h1" || src === "frontmatter" ? src : void 0;
|
|
3242
|
+
return {
|
|
3243
|
+
text: text ? text.toString() : "",
|
|
3244
|
+
source
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
return { text: "" };
|
|
3248
|
+
}
|
|
3249
|
+
function collectBodyBlocks(fragment) {
|
|
3250
|
+
const out = [];
|
|
3251
|
+
for (const child of fragment.toArray()) {
|
|
3252
|
+
if (!isXElem(child)) continue;
|
|
3253
|
+
if (child.nodeName === "documentHeader" || child.nodeName === "documentMeta") continue;
|
|
3254
|
+
out.push(child);
|
|
3255
|
+
}
|
|
3256
|
+
return out;
|
|
3257
|
+
}
|
|
3258
|
+
function serializeBlocksClean(blocks) {
|
|
3259
|
+
const parts = [];
|
|
3260
|
+
for (const block of blocks) {
|
|
3261
|
+
if (block.nodeName === "paragraph" && block.length === 0) {
|
|
3262
|
+
parts.push("");
|
|
3263
|
+
continue;
|
|
3264
|
+
}
|
|
3265
|
+
parts.push(serializeBlock(block));
|
|
3266
|
+
}
|
|
3267
|
+
while (parts.length && parts[parts.length - 1] === "") parts.pop();
|
|
3268
|
+
return parts.join("\n\n");
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
//#endregion
|
|
3272
|
+
//#region packages/convert/src/spec/nodes.ts
|
|
3273
|
+
const BOOL_FALSE_DEFAULT = {
|
|
3274
|
+
key: "",
|
|
3275
|
+
type: "boolean",
|
|
3276
|
+
default: false,
|
|
3277
|
+
optional: true
|
|
3278
|
+
};
|
|
3279
|
+
const bool = (key) => ({
|
|
3280
|
+
...BOOL_FALSE_DEFAULT,
|
|
3281
|
+
key
|
|
3282
|
+
});
|
|
3283
|
+
const str = (key, def) => ({
|
|
3284
|
+
key,
|
|
3285
|
+
type: "string",
|
|
3286
|
+
default: def,
|
|
3287
|
+
optional: true
|
|
3288
|
+
});
|
|
3289
|
+
const num = (key) => ({
|
|
3290
|
+
key,
|
|
3291
|
+
type: "number",
|
|
3292
|
+
optional: true
|
|
3293
|
+
});
|
|
3294
|
+
const int = (key) => ({
|
|
3295
|
+
key,
|
|
3296
|
+
type: "integer",
|
|
3297
|
+
optional: true
|
|
3298
|
+
});
|
|
3299
|
+
const VANILLA_BLOCKS = [
|
|
3199
3300
|
{
|
|
3200
|
-
name: "
|
|
3301
|
+
name: "documentHeader",
|
|
3201
3302
|
group: "block",
|
|
3202
|
-
wire: "
|
|
3203
|
-
|
|
3204
|
-
slotChild: "button"
|
|
3303
|
+
wire: "special",
|
|
3304
|
+
doc: "Holds the title; hoisted to frontmatter on serialise."
|
|
3205
3305
|
},
|
|
3206
3306
|
{
|
|
3207
|
-
name: "
|
|
3307
|
+
name: "documentMeta",
|
|
3208
3308
|
group: "block",
|
|
3209
|
-
wire: "
|
|
3210
|
-
|
|
3309
|
+
wire: "special",
|
|
3310
|
+
doc: "Holds page-level meta; serialised into frontmatter."
|
|
3211
3311
|
},
|
|
3212
3312
|
{
|
|
3213
|
-
name: "
|
|
3313
|
+
name: "paragraph",
|
|
3214
3314
|
group: "block",
|
|
3215
|
-
wire: "
|
|
3216
|
-
|
|
3217
|
-
attrs: [
|
|
3218
|
-
str("label"),
|
|
3219
|
-
str("icon"),
|
|
3220
|
-
str("date")
|
|
3221
|
-
]
|
|
3315
|
+
wire: "vanilla",
|
|
3316
|
+
contentBearing: true
|
|
3222
3317
|
},
|
|
3223
3318
|
{
|
|
3224
|
-
name: "
|
|
3319
|
+
name: "heading",
|
|
3225
3320
|
group: "block",
|
|
3226
|
-
wire: "
|
|
3227
|
-
attrs: [
|
|
3228
|
-
|
|
3229
|
-
type: "string"
|
|
3230
|
-
}]
|
|
3231
|
-
}
|
|
3232
|
-
];
|
|
3233
|
-
const INLINE_AND_SPECIAL = [
|
|
3234
|
-
{
|
|
3235
|
-
name: "docLink",
|
|
3236
|
-
group: "inline",
|
|
3237
|
-
wire: "special",
|
|
3238
|
-
attrs: [str("docId")],
|
|
3239
|
-
doc: "Wire form `[[uuid|label]]`; label regenerated on export."
|
|
3321
|
+
wire: "vanilla",
|
|
3322
|
+
attrs: [int("level")],
|
|
3323
|
+
contentBearing: true
|
|
3240
3324
|
},
|
|
3241
3325
|
{
|
|
3242
|
-
name: "
|
|
3326
|
+
name: "blockquote",
|
|
3243
3327
|
group: "block",
|
|
3244
|
-
wire: "
|
|
3245
|
-
|
|
3246
|
-
str("docId"),
|
|
3247
|
-
bool("collapsed"),
|
|
3248
|
-
bool("tall"),
|
|
3249
|
-
bool("seamless")
|
|
3250
|
-
],
|
|
3251
|
-
doc: "Wire form `![[uuid|label]]{collapsed tall seamless}`."
|
|
3252
|
-
},
|
|
3253
|
-
{
|
|
3254
|
-
name: "mention",
|
|
3255
|
-
group: "inline",
|
|
3256
|
-
wire: "special",
|
|
3257
|
-
attrs: [str("userId")],
|
|
3258
|
-
doc: "Wire form `@[label](user:uuid)`; label regenerated on export."
|
|
3259
|
-
},
|
|
3260
|
-
{
|
|
3261
|
-
name: "mathInline",
|
|
3262
|
-
group: "inline",
|
|
3263
|
-
wire: "special",
|
|
3264
|
-
attrs: [{
|
|
3265
|
-
key: "expression",
|
|
3266
|
-
type: "string"
|
|
3267
|
-
}],
|
|
3268
|
-
doc: "Wire form `$expression$`."
|
|
3328
|
+
wire: "vanilla",
|
|
3329
|
+
contentBearing: true
|
|
3269
3330
|
},
|
|
3270
3331
|
{
|
|
3271
|
-
name: "
|
|
3332
|
+
name: "codeBlock",
|
|
3272
3333
|
group: "block",
|
|
3273
3334
|
wire: "fence",
|
|
3274
|
-
attrs: [
|
|
3275
|
-
key: "expression",
|
|
3276
|
-
type: "string"
|
|
3277
|
-
}],
|
|
3278
|
-
mdcTag: "math",
|
|
3279
|
-
doc: "Wire form ``` ```math\\nexpression\\n``` ```."
|
|
3335
|
+
attrs: [str("language", "")]
|
|
3280
3336
|
},
|
|
3281
3337
|
{
|
|
3282
|
-
name: "
|
|
3338
|
+
name: "bulletList",
|
|
3283
3339
|
group: "block",
|
|
3284
|
-
wire: "
|
|
3285
|
-
mdcTag: "file",
|
|
3286
|
-
attrs: [
|
|
3287
|
-
str("src"),
|
|
3288
|
-
str("mime"),
|
|
3289
|
-
str("uploadId"),
|
|
3290
|
-
str("filename")
|
|
3291
|
-
],
|
|
3292
|
-
doc: "Wire form `:file{src=… mime=… upload-id=… filename=…}`; binary in sidecar."
|
|
3293
|
-
},
|
|
3294
|
-
{
|
|
3295
|
-
name: "badge",
|
|
3296
|
-
group: "inline",
|
|
3297
|
-
wire: "mdc-atom-inl",
|
|
3298
|
-
attrs: [
|
|
3299
|
-
str("label"),
|
|
3300
|
-
str("color"),
|
|
3301
|
-
str("variant", "subtle")
|
|
3302
|
-
],
|
|
3303
|
-
doc: "Wire form `:badge[Label]{color=… variant=…}`."
|
|
3304
|
-
},
|
|
3305
|
-
{
|
|
3306
|
-
name: "proseIcon",
|
|
3307
|
-
group: "inline",
|
|
3308
|
-
wire: "mdc-atom-inl",
|
|
3309
|
-
mdcTag: "icon",
|
|
3310
|
-
attrs: [str("name")],
|
|
3311
|
-
doc: "Wire form `:icon{name=…}`."
|
|
3312
|
-
},
|
|
3313
|
-
{
|
|
3314
|
-
name: "kbd",
|
|
3315
|
-
group: "inline",
|
|
3316
|
-
wire: "mdc-atom-inl",
|
|
3317
|
-
attrs: [str("value")],
|
|
3318
|
-
doc: "Wire form `:kbd{value=…}`."
|
|
3319
|
-
}
|
|
3320
|
-
];
|
|
3321
|
-
const NODE_SPECS = [
|
|
3322
|
-
...VANILLA_BLOCKS,
|
|
3323
|
-
...MDC_CONTAINERS,
|
|
3324
|
-
...INLINE_AND_SPECIAL
|
|
3325
|
-
];
|
|
3326
|
-
const NODE_SPEC_BY_NAME = new Map(NODE_SPECS.map((spec) => [spec.name, spec]));
|
|
3327
|
-
|
|
3328
|
-
//#endregion
|
|
3329
|
-
//#region packages/convert/src/spec/marks.ts
|
|
3330
|
-
const MARK_SPECS = [
|
|
3331
|
-
{
|
|
3332
|
-
name: "bold",
|
|
3333
|
-
wire: "delimited",
|
|
3334
|
-
delim: "**"
|
|
3335
|
-
},
|
|
3336
|
-
{
|
|
3337
|
-
name: "italic",
|
|
3338
|
-
wire: "delimited",
|
|
3339
|
-
delim: "*"
|
|
3340
|
-
},
|
|
3341
|
-
{
|
|
3342
|
-
name: "strike",
|
|
3343
|
-
wire: "delimited",
|
|
3344
|
-
delim: "~~"
|
|
3345
|
-
},
|
|
3346
|
-
{
|
|
3347
|
-
name: "code",
|
|
3348
|
-
wire: "delimited",
|
|
3349
|
-
delim: "`"
|
|
3350
|
-
},
|
|
3351
|
-
{
|
|
3352
|
-
name: "link",
|
|
3353
|
-
wire: "link",
|
|
3354
|
-
attrs: [{
|
|
3355
|
-
key: "href",
|
|
3356
|
-
type: "string"
|
|
3357
|
-
}, {
|
|
3358
|
-
key: "title",
|
|
3359
|
-
type: "string",
|
|
3360
|
-
optional: true
|
|
3361
|
-
}]
|
|
3362
|
-
},
|
|
3363
|
-
{
|
|
3364
|
-
name: "underline",
|
|
3365
|
-
wire: "delimited",
|
|
3366
|
-
delim: "__",
|
|
3367
|
-
doc: "Disambiguated from bold by delimiter character. Two underscores = underline; two asterisks = bold."
|
|
3368
|
-
},
|
|
3369
|
-
{
|
|
3370
|
-
name: "highlight",
|
|
3371
|
-
wire: "delimited",
|
|
3372
|
-
delim: "==",
|
|
3373
|
-
doc: "Pandoc-style."
|
|
3374
|
-
},
|
|
3375
|
-
{
|
|
3376
|
-
name: "subscript",
|
|
3377
|
-
wire: "delimited",
|
|
3378
|
-
delim: "~",
|
|
3379
|
-
doc: "Single tilde; double tilde is strike."
|
|
3340
|
+
wire: "vanilla"
|
|
3380
3341
|
},
|
|
3381
3342
|
{
|
|
3382
|
-
name: "
|
|
3383
|
-
|
|
3384
|
-
|
|
3343
|
+
name: "orderedList",
|
|
3344
|
+
group: "block",
|
|
3345
|
+
wire: "vanilla"
|
|
3385
3346
|
},
|
|
3386
3347
|
{
|
|
3387
|
-
name: "
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
key: "color",
|
|
3392
|
-
type: "string",
|
|
3393
|
-
optional: true
|
|
3394
|
-
},
|
|
3395
|
-
{
|
|
3396
|
-
key: "backgroundColor",
|
|
3397
|
-
type: "string",
|
|
3398
|
-
optional: true
|
|
3399
|
-
},
|
|
3400
|
-
{
|
|
3401
|
-
key: "fontSize",
|
|
3402
|
-
type: "string",
|
|
3403
|
-
optional: true
|
|
3404
|
-
},
|
|
3405
|
-
{
|
|
3406
|
-
key: "fontFamily",
|
|
3407
|
-
type: "string",
|
|
3408
|
-
optional: true
|
|
3409
|
-
}
|
|
3410
|
-
],
|
|
3411
|
-
doc: "Wire form `:span[text]{color=\"…\" font-size=\"…\"}`. Any of the attrs may be set."
|
|
3412
|
-
}
|
|
3413
|
-
];
|
|
3414
|
-
const MARK_SPEC_BY_NAME = new Map(MARK_SPECS.map((spec) => [spec.name, spec]));
|
|
3415
|
-
const MARK_SPEC_BY_DELIM = new Map(MARK_SPECS.filter((spec) => spec.wire === "delimited" && !!spec.delim).map((spec) => [spec.delim, spec]));
|
|
3416
|
-
|
|
3417
|
-
//#endregion
|
|
3418
|
-
//#region packages/convert/src/spec/universal-meta.ts
|
|
3419
|
-
const UNIVERSAL_META_KEYS = [
|
|
3420
|
-
{
|
|
3421
|
-
key: "title",
|
|
3422
|
-
type: "string",
|
|
3423
|
-
doc: "Display title; the first H1 is hoisted into this field on import."
|
|
3348
|
+
name: "listItem",
|
|
3349
|
+
group: "block",
|
|
3350
|
+
wire: "vanilla",
|
|
3351
|
+
contentBearing: true
|
|
3424
3352
|
},
|
|
3425
3353
|
{
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3354
|
+
name: "taskList",
|
|
3355
|
+
group: "block",
|
|
3356
|
+
wire: "vanilla"
|
|
3429
3357
|
},
|
|
3430
3358
|
{
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3359
|
+
name: "taskItem",
|
|
3360
|
+
group: "block",
|
|
3361
|
+
wire: "vanilla",
|
|
3362
|
+
attrs: [bool("checked")],
|
|
3363
|
+
contentBearing: true
|
|
3434
3364
|
},
|
|
3435
3365
|
{
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3366
|
+
name: "table",
|
|
3367
|
+
group: "block",
|
|
3368
|
+
wire: "vanilla"
|
|
3439
3369
|
},
|
|
3440
3370
|
{
|
|
3441
|
-
|
|
3442
|
-
|
|
3371
|
+
name: "tableRow",
|
|
3372
|
+
group: "block",
|
|
3373
|
+
wire: "vanilla"
|
|
3443
3374
|
},
|
|
3444
3375
|
{
|
|
3445
|
-
|
|
3446
|
-
|
|
3376
|
+
name: "tableHeader",
|
|
3377
|
+
group: "block",
|
|
3378
|
+
wire: "vanilla",
|
|
3379
|
+
contentBearing: true
|
|
3447
3380
|
},
|
|
3448
3381
|
{
|
|
3449
|
-
|
|
3450
|
-
|
|
3382
|
+
name: "tableCell",
|
|
3383
|
+
group: "block",
|
|
3384
|
+
wire: "vanilla",
|
|
3385
|
+
contentBearing: true
|
|
3451
3386
|
},
|
|
3452
3387
|
{
|
|
3453
|
-
|
|
3454
|
-
|
|
3388
|
+
name: "horizontalRule",
|
|
3389
|
+
group: "block",
|
|
3390
|
+
wire: "vanilla"
|
|
3455
3391
|
},
|
|
3456
3392
|
{
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3393
|
+
name: "image",
|
|
3394
|
+
group: "block",
|
|
3395
|
+
wire: "special",
|
|
3396
|
+
attrs: [
|
|
3397
|
+
str("src"),
|
|
3398
|
+
str("alt", ""),
|
|
3399
|
+
int("width"),
|
|
3400
|
+
int("height")
|
|
3401
|
+
]
|
|
3460
3402
|
},
|
|
3461
3403
|
{
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
}
|
|
3404
|
+
name: "hardBreak",
|
|
3405
|
+
group: "inline",
|
|
3406
|
+
wire: "vanilla"
|
|
3407
|
+
}
|
|
3408
|
+
];
|
|
3409
|
+
const MDC_CONTAINERS = [
|
|
3466
3410
|
{
|
|
3467
|
-
|
|
3468
|
-
|
|
3411
|
+
name: "callout",
|
|
3412
|
+
group: "block",
|
|
3413
|
+
wire: "mdc-container",
|
|
3414
|
+
attrs: [
|
|
3415
|
+
{
|
|
3416
|
+
key: "type",
|
|
3417
|
+
type: "string",
|
|
3418
|
+
default: "note",
|
|
3419
|
+
optional: true,
|
|
3420
|
+
values: [
|
|
3421
|
+
"note",
|
|
3422
|
+
"tip",
|
|
3423
|
+
"warning",
|
|
3424
|
+
"danger",
|
|
3425
|
+
"info",
|
|
3426
|
+
"caution",
|
|
3427
|
+
"alert",
|
|
3428
|
+
"success",
|
|
3429
|
+
"error"
|
|
3430
|
+
]
|
|
3431
|
+
},
|
|
3432
|
+
str("title"),
|
|
3433
|
+
str("icon")
|
|
3434
|
+
]
|
|
3469
3435
|
},
|
|
3470
3436
|
{
|
|
3471
|
-
|
|
3472
|
-
|
|
3437
|
+
name: "collapsible",
|
|
3438
|
+
group: "block",
|
|
3439
|
+
wire: "mdc-container",
|
|
3440
|
+
attrs: [str("label", "Details"), bool("open")]
|
|
3473
3441
|
},
|
|
3474
3442
|
{
|
|
3475
|
-
|
|
3476
|
-
|
|
3443
|
+
name: "accordion",
|
|
3444
|
+
group: "block",
|
|
3445
|
+
wire: "mdc-slotted",
|
|
3446
|
+
slotChild: "accordionItem"
|
|
3477
3447
|
},
|
|
3478
3448
|
{
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3449
|
+
name: "accordionItem",
|
|
3450
|
+
group: "block",
|
|
3451
|
+
wire: "mdc-container",
|
|
3452
|
+
mdcTag: "accordion-item",
|
|
3453
|
+
attrs: [str("label", "Item"), str("icon")]
|
|
3482
3454
|
},
|
|
3483
3455
|
{
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
doc: "Numeric or named (low/medium/high/urgent → 1/2/3/4)."
|
|
3456
|
+
name: "tabs",
|
|
3457
|
+
group: "block",
|
|
3458
|
+
wire: "mdc-slotted",
|
|
3459
|
+
slotChild: "tabsItem"
|
|
3489
3460
|
},
|
|
3490
3461
|
{
|
|
3491
|
-
|
|
3492
|
-
|
|
3462
|
+
name: "tabsItem",
|
|
3463
|
+
group: "block",
|
|
3464
|
+
wire: "mdc-container",
|
|
3465
|
+
mdcTag: "tabs-item",
|
|
3466
|
+
attrs: [str("label"), str("icon")]
|
|
3493
3467
|
},
|
|
3494
3468
|
{
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
max: 5
|
|
3469
|
+
name: "steps",
|
|
3470
|
+
group: "block",
|
|
3471
|
+
wire: "mdc-container"
|
|
3499
3472
|
},
|
|
3500
3473
|
{
|
|
3501
|
-
|
|
3502
|
-
|
|
3474
|
+
name: "card",
|
|
3475
|
+
group: "block",
|
|
3476
|
+
wire: "mdc-container",
|
|
3477
|
+
attrs: [
|
|
3478
|
+
str("title"),
|
|
3479
|
+
str("icon"),
|
|
3480
|
+
str("to")
|
|
3481
|
+
]
|
|
3503
3482
|
},
|
|
3504
3483
|
{
|
|
3505
|
-
|
|
3506
|
-
|
|
3484
|
+
name: "cardGroup",
|
|
3485
|
+
group: "block",
|
|
3486
|
+
wire: "mdc-slotted",
|
|
3487
|
+
mdcTag: "card-group",
|
|
3488
|
+
slotChild: "card"
|
|
3507
3489
|
},
|
|
3508
3490
|
{
|
|
3509
|
-
|
|
3510
|
-
|
|
3491
|
+
name: "field",
|
|
3492
|
+
group: "block",
|
|
3493
|
+
wire: "mdc-container",
|
|
3494
|
+
attrs: [
|
|
3495
|
+
str("name"),
|
|
3496
|
+
str("type", "string"),
|
|
3497
|
+
bool("required")
|
|
3498
|
+
]
|
|
3511
3499
|
},
|
|
3512
3500
|
{
|
|
3513
|
-
|
|
3514
|
-
|
|
3501
|
+
name: "fieldGroup",
|
|
3502
|
+
group: "block",
|
|
3503
|
+
wire: "mdc-slotted",
|
|
3504
|
+
mdcTag: "field-group",
|
|
3505
|
+
slotChild: "field"
|
|
3515
3506
|
},
|
|
3516
3507
|
{
|
|
3517
|
-
|
|
3518
|
-
|
|
3508
|
+
name: "codeGroup",
|
|
3509
|
+
group: "block",
|
|
3510
|
+
wire: "mdc-slotted",
|
|
3511
|
+
mdcTag: "code-group",
|
|
3512
|
+
slotChild: "codeBlock"
|
|
3519
3513
|
},
|
|
3520
3514
|
{
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3515
|
+
name: "codeCollapse",
|
|
3516
|
+
group: "block",
|
|
3517
|
+
wire: "mdc-container",
|
|
3518
|
+
mdcTag: "code-collapse"
|
|
3524
3519
|
},
|
|
3525
3520
|
{
|
|
3526
|
-
|
|
3527
|
-
|
|
3521
|
+
name: "codePreview",
|
|
3522
|
+
group: "block",
|
|
3523
|
+
wire: "mdc-container",
|
|
3524
|
+
mdcTag: "code-preview"
|
|
3528
3525
|
},
|
|
3529
3526
|
{
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3527
|
+
name: "codeTree",
|
|
3528
|
+
group: "block",
|
|
3529
|
+
wire: "mdc-atom-block",
|
|
3530
|
+
mdcTag: "code-tree",
|
|
3531
|
+
attrs: [{
|
|
3532
|
+
key: "files",
|
|
3533
|
+
type: "json"
|
|
3534
|
+
}]
|
|
3534
3535
|
},
|
|
3535
3536
|
{
|
|
3536
|
-
|
|
3537
|
-
|
|
3537
|
+
name: "figure",
|
|
3538
|
+
group: "block",
|
|
3539
|
+
wire: "mdc-container",
|
|
3540
|
+
attrs: [
|
|
3541
|
+
str("src"),
|
|
3542
|
+
str("alt", ""),
|
|
3543
|
+
str("caption")
|
|
3544
|
+
]
|
|
3538
3545
|
},
|
|
3539
3546
|
{
|
|
3540
|
-
|
|
3541
|
-
|
|
3547
|
+
name: "video",
|
|
3548
|
+
group: "block",
|
|
3549
|
+
wire: "mdc-atom-block",
|
|
3550
|
+
attrs: [
|
|
3551
|
+
str("src"),
|
|
3552
|
+
str("poster"),
|
|
3553
|
+
bool("autoplay"),
|
|
3554
|
+
bool("loop"),
|
|
3555
|
+
bool("controls")
|
|
3556
|
+
]
|
|
3542
3557
|
},
|
|
3543
3558
|
{
|
|
3544
|
-
|
|
3545
|
-
|
|
3559
|
+
name: "embed",
|
|
3560
|
+
group: "block",
|
|
3561
|
+
wire: "mdc-atom-block",
|
|
3562
|
+
attrs: [str("src"), str("title")]
|
|
3546
3563
|
},
|
|
3547
3564
|
{
|
|
3548
|
-
|
|
3549
|
-
|
|
3565
|
+
name: "svgEmbed",
|
|
3566
|
+
group: "block",
|
|
3567
|
+
wire: "fence",
|
|
3568
|
+
attrs: [str("title")],
|
|
3569
|
+
mdcTag: "svg",
|
|
3570
|
+
doc: "Serialised as a ```svg fenced block; the SVG markup is the body."
|
|
3550
3571
|
},
|
|
3551
3572
|
{
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
"line",
|
|
3557
|
-
"measure"
|
|
3558
|
-
]
|
|
3573
|
+
name: "divider",
|
|
3574
|
+
group: "block",
|
|
3575
|
+
wire: "mdc-atom-block",
|
|
3576
|
+
attrs: [str("label"), str("icon")]
|
|
3559
3577
|
},
|
|
3560
3578
|
{
|
|
3561
|
-
|
|
3562
|
-
|
|
3579
|
+
name: "quote",
|
|
3580
|
+
group: "block",
|
|
3581
|
+
wire: "mdc-container",
|
|
3582
|
+
attrs: [str("cite")]
|
|
3563
3583
|
},
|
|
3564
3584
|
{
|
|
3565
|
-
|
|
3566
|
-
|
|
3585
|
+
name: "progress",
|
|
3586
|
+
group: "block",
|
|
3587
|
+
wire: "mdc-atom-block",
|
|
3588
|
+
attrs: [
|
|
3589
|
+
num("value"),
|
|
3590
|
+
num("max"),
|
|
3591
|
+
str("label")
|
|
3592
|
+
]
|
|
3567
3593
|
},
|
|
3568
3594
|
{
|
|
3569
|
-
|
|
3570
|
-
|
|
3595
|
+
name: "spoiler",
|
|
3596
|
+
group: "block",
|
|
3597
|
+
wire: "mdc-container",
|
|
3598
|
+
attrs: [str("label")]
|
|
3571
3599
|
},
|
|
3572
3600
|
{
|
|
3573
|
-
|
|
3574
|
-
|
|
3601
|
+
name: "colorSwatch",
|
|
3602
|
+
group: "block",
|
|
3603
|
+
wire: "mdc-atom-block",
|
|
3604
|
+
mdcTag: "color-swatch",
|
|
3605
|
+
attrs: [str("color"), str("label")]
|
|
3575
3606
|
},
|
|
3576
3607
|
{
|
|
3577
|
-
|
|
3578
|
-
|
|
3608
|
+
name: "stat",
|
|
3609
|
+
group: "block",
|
|
3610
|
+
wire: "mdc-container",
|
|
3611
|
+
attrs: [
|
|
3612
|
+
str("label"),
|
|
3613
|
+
str("value"),
|
|
3614
|
+
str("icon")
|
|
3615
|
+
]
|
|
3579
3616
|
},
|
|
3580
3617
|
{
|
|
3581
|
-
|
|
3582
|
-
|
|
3618
|
+
name: "statGroup",
|
|
3619
|
+
group: "block",
|
|
3620
|
+
wire: "mdc-slotted",
|
|
3621
|
+
mdcTag: "stat-group",
|
|
3622
|
+
slotChild: "stat"
|
|
3583
3623
|
},
|
|
3584
3624
|
{
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
"
|
|
3590
|
-
"
|
|
3625
|
+
name: "button",
|
|
3626
|
+
group: "block",
|
|
3627
|
+
wire: "mdc-atom-block",
|
|
3628
|
+
attrs: [
|
|
3629
|
+
str("label"),
|
|
3630
|
+
str("to"),
|
|
3631
|
+
str("icon"),
|
|
3632
|
+
str("variant")
|
|
3591
3633
|
]
|
|
3592
3634
|
},
|
|
3593
3635
|
{
|
|
3594
|
-
|
|
3595
|
-
|
|
3636
|
+
name: "buttonGroup",
|
|
3637
|
+
group: "block",
|
|
3638
|
+
wire: "mdc-slotted",
|
|
3639
|
+
mdcTag: "button-group",
|
|
3640
|
+
slotChild: "button"
|
|
3596
3641
|
},
|
|
3597
3642
|
{
|
|
3598
|
-
|
|
3599
|
-
|
|
3643
|
+
name: "timeline",
|
|
3644
|
+
group: "block",
|
|
3645
|
+
wire: "mdc-slotted",
|
|
3646
|
+
slotChild: "timelineItem"
|
|
3600
3647
|
},
|
|
3601
3648
|
{
|
|
3602
|
-
|
|
3603
|
-
|
|
3649
|
+
name: "timelineItem",
|
|
3650
|
+
group: "block",
|
|
3651
|
+
wire: "mdc-container",
|
|
3652
|
+
mdcTag: "timeline-item",
|
|
3653
|
+
attrs: [
|
|
3654
|
+
str("label"),
|
|
3655
|
+
str("icon"),
|
|
3656
|
+
str("date")
|
|
3657
|
+
]
|
|
3604
3658
|
},
|
|
3605
3659
|
{
|
|
3606
|
-
|
|
3607
|
-
|
|
3660
|
+
name: "diff",
|
|
3661
|
+
group: "block",
|
|
3662
|
+
wire: "mdc-atom-block",
|
|
3663
|
+
attrs: [str("language", ""), {
|
|
3664
|
+
key: "value",
|
|
3665
|
+
type: "string"
|
|
3666
|
+
}]
|
|
3667
|
+
}
|
|
3668
|
+
];
|
|
3669
|
+
const INLINE_AND_SPECIAL = [
|
|
3670
|
+
{
|
|
3671
|
+
name: "docLink",
|
|
3672
|
+
group: "inline",
|
|
3673
|
+
wire: "special",
|
|
3674
|
+
attrs: [str("docId")],
|
|
3675
|
+
doc: "Wire form `[[uuid|label]]`; label regenerated on export."
|
|
3608
3676
|
},
|
|
3609
3677
|
{
|
|
3610
|
-
|
|
3611
|
-
|
|
3678
|
+
name: "docEmbed",
|
|
3679
|
+
group: "block",
|
|
3680
|
+
wire: "special",
|
|
3681
|
+
attrs: [
|
|
3682
|
+
str("docId"),
|
|
3683
|
+
bool("collapsed"),
|
|
3684
|
+
bool("tall"),
|
|
3685
|
+
bool("seamless")
|
|
3686
|
+
],
|
|
3687
|
+
doc: "Wire form `![[uuid|label]]{collapsed tall seamless}`."
|
|
3612
3688
|
},
|
|
3613
3689
|
{
|
|
3614
|
-
|
|
3615
|
-
|
|
3690
|
+
name: "mention",
|
|
3691
|
+
group: "inline",
|
|
3692
|
+
wire: "special",
|
|
3693
|
+
attrs: [str("userId")],
|
|
3694
|
+
doc: "Wire form `@[label](user:uuid)`; label regenerated on export."
|
|
3616
3695
|
},
|
|
3617
3696
|
{
|
|
3618
|
-
|
|
3619
|
-
|
|
3697
|
+
name: "mathInline",
|
|
3698
|
+
group: "inline",
|
|
3699
|
+
wire: "special",
|
|
3700
|
+
attrs: [{
|
|
3701
|
+
key: "expression",
|
|
3702
|
+
type: "string"
|
|
3703
|
+
}],
|
|
3704
|
+
doc: "Wire form `$expression$`."
|
|
3620
3705
|
},
|
|
3621
3706
|
{
|
|
3622
|
-
|
|
3623
|
-
|
|
3707
|
+
name: "mathBlock",
|
|
3708
|
+
group: "block",
|
|
3709
|
+
wire: "fence",
|
|
3710
|
+
attrs: [{
|
|
3711
|
+
key: "expression",
|
|
3712
|
+
type: "string"
|
|
3713
|
+
}],
|
|
3714
|
+
mdcTag: "math",
|
|
3715
|
+
doc: "Wire form ``` ```math\\nexpression\\n``` ```."
|
|
3624
3716
|
},
|
|
3625
3717
|
{
|
|
3626
|
-
|
|
3627
|
-
|
|
3718
|
+
name: "fileBlock",
|
|
3719
|
+
group: "block",
|
|
3720
|
+
wire: "mdc-atom-block",
|
|
3721
|
+
mdcTag: "file",
|
|
3722
|
+
attrs: [
|
|
3723
|
+
str("src"),
|
|
3724
|
+
str("mime"),
|
|
3725
|
+
str("uploadId"),
|
|
3726
|
+
str("filename")
|
|
3727
|
+
],
|
|
3728
|
+
doc: "Wire form `:file{src=… mime=… upload-id=… filename=…}`; binary in sidecar."
|
|
3628
3729
|
},
|
|
3629
3730
|
{
|
|
3630
|
-
|
|
3631
|
-
|
|
3731
|
+
name: "badge",
|
|
3732
|
+
group: "inline",
|
|
3733
|
+
wire: "mdc-atom-inl",
|
|
3734
|
+
attrs: [
|
|
3735
|
+
str("label"),
|
|
3736
|
+
str("color"),
|
|
3737
|
+
str("variant", "subtle")
|
|
3738
|
+
],
|
|
3739
|
+
doc: "Wire form `:badge[Label]{color=… variant=…}`."
|
|
3632
3740
|
},
|
|
3633
3741
|
{
|
|
3634
|
-
|
|
3635
|
-
|
|
3742
|
+
name: "proseIcon",
|
|
3743
|
+
group: "inline",
|
|
3744
|
+
wire: "mdc-atom-inl",
|
|
3745
|
+
mdcTag: "icon",
|
|
3746
|
+
attrs: [str("name")],
|
|
3747
|
+
doc: "Wire form `:icon{name=…}`."
|
|
3636
3748
|
},
|
|
3637
3749
|
{
|
|
3638
|
-
|
|
3639
|
-
|
|
3750
|
+
name: "kbd",
|
|
3751
|
+
group: "inline",
|
|
3752
|
+
wire: "mdc-atom-inl",
|
|
3753
|
+
attrs: [str("value")],
|
|
3754
|
+
doc: "Wire form `:kbd{value=…}`."
|
|
3755
|
+
}
|
|
3756
|
+
];
|
|
3757
|
+
const NODE_SPECS = [
|
|
3758
|
+
...VANILLA_BLOCKS,
|
|
3759
|
+
...MDC_CONTAINERS,
|
|
3760
|
+
...INLINE_AND_SPECIAL
|
|
3761
|
+
];
|
|
3762
|
+
const NODE_SPEC_BY_NAME = new Map(NODE_SPECS.map((spec) => [spec.name, spec]));
|
|
3763
|
+
|
|
3764
|
+
//#endregion
|
|
3765
|
+
//#region packages/convert/src/spec/marks.ts
|
|
3766
|
+
const MARK_SPECS = [
|
|
3767
|
+
{
|
|
3768
|
+
name: "bold",
|
|
3769
|
+
wire: "delimited",
|
|
3770
|
+
delim: "**"
|
|
3640
3771
|
},
|
|
3641
3772
|
{
|
|
3642
|
-
|
|
3643
|
-
|
|
3773
|
+
name: "italic",
|
|
3774
|
+
wire: "delimited",
|
|
3775
|
+
delim: "*"
|
|
3644
3776
|
},
|
|
3645
3777
|
{
|
|
3646
|
-
|
|
3647
|
-
|
|
3778
|
+
name: "strike",
|
|
3779
|
+
wire: "delimited",
|
|
3780
|
+
delim: "~~"
|
|
3648
3781
|
},
|
|
3649
3782
|
{
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
"box",
|
|
3654
|
-
"sphere",
|
|
3655
|
-
"cylinder",
|
|
3656
|
-
"cone",
|
|
3657
|
-
"plane",
|
|
3658
|
-
"torus",
|
|
3659
|
-
"glb"
|
|
3660
|
-
]
|
|
3783
|
+
name: "code",
|
|
3784
|
+
wire: "delimited",
|
|
3785
|
+
delim: "`"
|
|
3661
3786
|
},
|
|
3662
3787
|
{
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3788
|
+
name: "link",
|
|
3789
|
+
wire: "link",
|
|
3790
|
+
attrs: [{
|
|
3791
|
+
key: "href",
|
|
3792
|
+
type: "string"
|
|
3793
|
+
}, {
|
|
3794
|
+
key: "title",
|
|
3795
|
+
type: "string",
|
|
3796
|
+
optional: true
|
|
3797
|
+
}]
|
|
3667
3798
|
},
|
|
3668
3799
|
{
|
|
3669
|
-
|
|
3670
|
-
|
|
3800
|
+
name: "underline",
|
|
3801
|
+
wire: "delimited",
|
|
3802
|
+
delim: "__",
|
|
3803
|
+
doc: "Disambiguated from bold by delimiter character. Two underscores = underline; two asterisks = bold."
|
|
3671
3804
|
},
|
|
3672
3805
|
{
|
|
3673
|
-
|
|
3674
|
-
|
|
3806
|
+
name: "highlight",
|
|
3807
|
+
wire: "delimited",
|
|
3808
|
+
delim: "==",
|
|
3809
|
+
doc: "Pandoc-style."
|
|
3675
3810
|
},
|
|
3676
3811
|
{
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
"fade",
|
|
3682
|
-
"slide"
|
|
3683
|
-
]
|
|
3812
|
+
name: "subscript",
|
|
3813
|
+
wire: "delimited",
|
|
3814
|
+
delim: "~",
|
|
3815
|
+
doc: "Single tilde; double tilde is strike."
|
|
3684
3816
|
},
|
|
3685
3817
|
{
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3818
|
+
name: "superscript",
|
|
3819
|
+
wire: "delimited",
|
|
3820
|
+
delim: "^"
|
|
3689
3821
|
},
|
|
3690
3822
|
{
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3823
|
+
name: "textStyle",
|
|
3824
|
+
wire: "mdc-span",
|
|
3825
|
+
attrs: [
|
|
3826
|
+
{
|
|
3827
|
+
key: "color",
|
|
3828
|
+
type: "string",
|
|
3829
|
+
optional: true
|
|
3830
|
+
},
|
|
3831
|
+
{
|
|
3832
|
+
key: "backgroundColor",
|
|
3833
|
+
type: "string",
|
|
3834
|
+
optional: true
|
|
3835
|
+
},
|
|
3836
|
+
{
|
|
3837
|
+
key: "fontSize",
|
|
3838
|
+
type: "string",
|
|
3839
|
+
optional: true
|
|
3840
|
+
},
|
|
3841
|
+
{
|
|
3842
|
+
key: "fontFamily",
|
|
3843
|
+
type: "string",
|
|
3844
|
+
optional: true
|
|
3845
|
+
}
|
|
3846
|
+
],
|
|
3847
|
+
doc: "Wire form `:span[text]{color=\"…\" font-size=\"…\"}`. Any of the attrs may be set."
|
|
3694
3848
|
}
|
|
3695
3849
|
];
|
|
3696
|
-
const
|
|
3850
|
+
const MARK_SPEC_BY_NAME = new Map(MARK_SPECS.map((spec) => [spec.name, spec]));
|
|
3851
|
+
const MARK_SPEC_BY_DELIM = new Map(MARK_SPECS.filter((spec) => spec.wire === "delimited" && !!spec.delim).map((spec) => [spec.delim, spec]));
|
|
3697
3852
|
|
|
3698
3853
|
//#endregion
|
|
3699
3854
|
//#region packages/cli/src/commands/documents.ts
|