@zettelgeist/cli 0.1.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/LICENSE +202 -0
- package/README.md +64 -0
- package/dist/bin.js +2211 -0
- package/dist/bin.js.map +7 -0
- package/dist/templates/export.html +27 -0
- package/dist/templates/skill/SKILL.md +220 -0
- package/dist/viewer-bundle/architecture-YZFGNWBL-W2K3EQOM.js +14 -0
- package/dist/viewer-bundle/architecture-YZFGNWBL-W2K3EQOM.js.map +7 -0
- package/dist/viewer-bundle/architectureDiagram-Q4EWVU46-LCGRUEWT.js +8884 -0
- package/dist/viewer-bundle/architectureDiagram-Q4EWVU46-LCGRUEWT.js.map +7 -0
- package/dist/viewer-bundle/base.css +248 -0
- package/dist/viewer-bundle/blockDiagram-DXYQGD6D-KAM7BOAP.js +3777 -0
- package/dist/viewer-bundle/blockDiagram-DXYQGD6D-KAM7BOAP.js.map +7 -0
- package/dist/viewer-bundle/board.css +370 -0
- package/dist/viewer-bundle/c4Diagram-AHTNJAMY-C3FIQYXA.js +2596 -0
- package/dist/viewer-bundle/c4Diagram-AHTNJAMY-C3FIQYXA.js.map +7 -0
- package/dist/viewer-bundle/chunk-2QXGXAO5.js +68 -0
- package/dist/viewer-bundle/chunk-2QXGXAO5.js.map +7 -0
- package/dist/viewer-bundle/chunk-5PZT7VUU.js +67 -0
- package/dist/viewer-bundle/chunk-5PZT7VUU.js.map +7 -0
- package/dist/viewer-bundle/chunk-5YJCJF2C.js +112 -0
- package/dist/viewer-bundle/chunk-5YJCJF2C.js.map +7 -0
- package/dist/viewer-bundle/chunk-6LYV7PBV.js +1011 -0
- package/dist/viewer-bundle/chunk-6LYV7PBV.js.map +7 -0
- package/dist/viewer-bundle/chunk-6VW7D5WX.js +48 -0
- package/dist/viewer-bundle/chunk-6VW7D5WX.js.map +7 -0
- package/dist/viewer-bundle/chunk-76C5OSD4.js +2048 -0
- package/dist/viewer-bundle/chunk-76C5OSD4.js.map +7 -0
- package/dist/viewer-bundle/chunk-7NZMPQDX.js +6957 -0
- package/dist/viewer-bundle/chunk-7NZMPQDX.js.map +7 -0
- package/dist/viewer-bundle/chunk-A634GTZN.js +122 -0
- package/dist/viewer-bundle/chunk-A634GTZN.js.map +7 -0
- package/dist/viewer-bundle/chunk-AJQJUKMU.js +133 -0
- package/dist/viewer-bundle/chunk-AJQJUKMU.js.map +7 -0
- package/dist/viewer-bundle/chunk-BM2KPNFW.js +5556 -0
- package/dist/viewer-bundle/chunk-BM2KPNFW.js.map +7 -0
- package/dist/viewer-bundle/chunk-CIDUOCCG.js +25 -0
- package/dist/viewer-bundle/chunk-CIDUOCCG.js.map +7 -0
- package/dist/viewer-bundle/chunk-CZHJHAOR.js +6397 -0
- package/dist/viewer-bundle/chunk-CZHJHAOR.js.map +7 -0
- package/dist/viewer-bundle/chunk-D5RLIWY4.js +125 -0
- package/dist/viewer-bundle/chunk-D5RLIWY4.js.map +7 -0
- package/dist/viewer-bundle/chunk-DI52DQAC.js +44 -0
- package/dist/viewer-bundle/chunk-DI52DQAC.js.map +7 -0
- package/dist/viewer-bundle/chunk-EXJQLTIV.js +51 -0
- package/dist/viewer-bundle/chunk-EXJQLTIV.js.map +7 -0
- package/dist/viewer-bundle/chunk-G3PPZWPW.js +96 -0
- package/dist/viewer-bundle/chunk-G3PPZWPW.js.map +7 -0
- package/dist/viewer-bundle/chunk-GTW4IDD4.js +30297 -0
- package/dist/viewer-bundle/chunk-GTW4IDD4.js.map +7 -0
- package/dist/viewer-bundle/chunk-GVE7OA3Z.js +59 -0
- package/dist/viewer-bundle/chunk-GVE7OA3Z.js.map +7 -0
- package/dist/viewer-bundle/chunk-JBUVKVPY.js +2042 -0
- package/dist/viewer-bundle/chunk-JBUVKVPY.js.map +7 -0
- package/dist/viewer-bundle/chunk-JQLVOAQB.js +20 -0
- package/dist/viewer-bundle/chunk-JQLVOAQB.js.map +7 -0
- package/dist/viewer-bundle/chunk-LQMQSYLO.js +101 -0
- package/dist/viewer-bundle/chunk-LQMQSYLO.js.map +7 -0
- package/dist/viewer-bundle/chunk-MBFAQ3IK.js +34 -0
- package/dist/viewer-bundle/chunk-MBFAQ3IK.js.map +7 -0
- package/dist/viewer-bundle/chunk-N7G7IIKG.js +25 -0
- package/dist/viewer-bundle/chunk-N7G7IIKG.js.map +7 -0
- package/dist/viewer-bundle/chunk-NW4YG3NS.js +171 -0
- package/dist/viewer-bundle/chunk-NW4YG3NS.js.map +7 -0
- package/dist/viewer-bundle/chunk-ODEP5TKB.js +61 -0
- package/dist/viewer-bundle/chunk-ODEP5TKB.js.map +7 -0
- package/dist/viewer-bundle/chunk-OGKINV23.js +1050 -0
- package/dist/viewer-bundle/chunk-OGKINV23.js.map +7 -0
- package/dist/viewer-bundle/chunk-OGMSNDVH.js +1994 -0
- package/dist/viewer-bundle/chunk-OGMSNDVH.js.map +7 -0
- package/dist/viewer-bundle/chunk-QJVSDNAW.js +25 -0
- package/dist/viewer-bundle/chunk-QJVSDNAW.js.map +7 -0
- package/dist/viewer-bundle/chunk-RBTT26R4.js +2721 -0
- package/dist/viewer-bundle/chunk-RBTT26R4.js.map +7 -0
- package/dist/viewer-bundle/chunk-RQIPIIE2.js +48 -0
- package/dist/viewer-bundle/chunk-RQIPIIE2.js.map +7 -0
- package/dist/viewer-bundle/chunk-SRTYTXTX.js +22 -0
- package/dist/viewer-bundle/chunk-SRTYTXTX.js.map +7 -0
- package/dist/viewer-bundle/chunk-TRL7YIZG.js +1663 -0
- package/dist/viewer-bundle/chunk-TRL7YIZG.js.map +7 -0
- package/dist/viewer-bundle/chunk-U5T7X4BV.js +172 -0
- package/dist/viewer-bundle/chunk-U5T7X4BV.js.map +7 -0
- package/dist/viewer-bundle/chunk-UCAW6C6C.js +48 -0
- package/dist/viewer-bundle/chunk-UCAW6C6C.js.map +7 -0
- package/dist/viewer-bundle/chunk-UEAG4BJQ.js +93 -0
- package/dist/viewer-bundle/chunk-UEAG4BJQ.js.map +7 -0
- package/dist/viewer-bundle/chunk-UVRE3R6A.js +1039 -0
- package/dist/viewer-bundle/chunk-UVRE3R6A.js.map +7 -0
- package/dist/viewer-bundle/chunk-VODO7SV4.js +25029 -0
- package/dist/viewer-bundle/chunk-VODO7SV4.js.map +7 -0
- package/dist/viewer-bundle/chunk-YEU62MVS.js +682 -0
- package/dist/viewer-bundle/chunk-YEU62MVS.js.map +7 -0
- package/dist/viewer-bundle/chunk-YFQT7PPW.js +987 -0
- package/dist/viewer-bundle/chunk-YFQT7PPW.js.map +7 -0
- package/dist/viewer-bundle/chunk-Z4G7FG27.js +48 -0
- package/dist/viewer-bundle/chunk-Z4G7FG27.js.map +7 -0
- package/dist/viewer-bundle/chunk-ZW4Y7DIF.js +2044 -0
- package/dist/viewer-bundle/chunk-ZW4Y7DIF.js.map +7 -0
- package/dist/viewer-bundle/classDiagram-6PBFFD2Q-7VKYXLUX.js +46 -0
- package/dist/viewer-bundle/classDiagram-6PBFFD2Q-7VKYXLUX.js.map +7 -0
- package/dist/viewer-bundle/classDiagram-v2-HSJHXN6E-ACCNN7EN.js +46 -0
- package/dist/viewer-bundle/classDiagram-v2-HSJHXN6E-ACCNN7EN.js.map +7 -0
- package/dist/viewer-bundle/cose-bilkent-S5V4N54A-MUJHAA34.js +5009 -0
- package/dist/viewer-bundle/cose-bilkent-S5V4N54A-MUJHAA34.js.map +7 -0
- package/dist/viewer-bundle/dagre-KV5264BT-YC5VV3WF.js +739 -0
- package/dist/viewer-bundle/dagre-KV5264BT-YC5VV3WF.js.map +7 -0
- package/dist/viewer-bundle/dark.css +13 -0
- package/dist/viewer-bundle/detail.css +539 -0
- package/dist/viewer-bundle/diagram-5BDNPKRD-RXFPVFYK.js +214 -0
- package/dist/viewer-bundle/diagram-5BDNPKRD-RXFPVFYK.js.map +7 -0
- package/dist/viewer-bundle/diagram-G4DWMVQ6-KN7CBNBQ.js +578 -0
- package/dist/viewer-bundle/diagram-G4DWMVQ6-KN7CBNBQ.js.map +7 -0
- package/dist/viewer-bundle/diagram-MMDJMWI5-ZN6TQ7ZC.js +345 -0
- package/dist/viewer-bundle/diagram-MMDJMWI5-ZN6TQ7ZC.js.map +7 -0
- package/dist/viewer-bundle/diagram-TYMM5635-MMTUJ4KA.js +255 -0
- package/dist/viewer-bundle/diagram-TYMM5635-MMTUJ4KA.js.map +7 -0
- package/dist/viewer-bundle/docs.css +88 -0
- package/dist/viewer-bundle/edit-modal-BEGC2AO6.js +176 -0
- package/dist/viewer-bundle/edit-modal-BEGC2AO6.js.map +7 -0
- package/dist/viewer-bundle/erDiagram-SMLLAGMA-TBHMLD2E.js +1349 -0
- package/dist/viewer-bundle/erDiagram-SMLLAGMA-TBHMLD2E.js.map +7 -0
- package/dist/viewer-bundle/flowDiagram-DWJPFMVM-BZHLK6QB.js +2501 -0
- package/dist/viewer-bundle/flowDiagram-DWJPFMVM-BZHLK6QB.js.map +7 -0
- package/dist/viewer-bundle/ganttDiagram-T4ZO3ILL-YBARPTQR.js +2654 -0
- package/dist/viewer-bundle/ganttDiagram-T4ZO3ILL-YBARPTQR.js.map +7 -0
- package/dist/viewer-bundle/gitGraph-7Q5UKJZL-HENKIQDX.js +14 -0
- package/dist/viewer-bundle/gitGraph-7Q5UKJZL-HENKIQDX.js.map +7 -0
- package/dist/viewer-bundle/gitGraphDiagram-UUTBAWPF-M4VV3YVA.js +1946 -0
- package/dist/viewer-bundle/gitGraphDiagram-UUTBAWPF-M4VV3YVA.js.map +7 -0
- package/dist/viewer-bundle/index.html +28 -0
- package/dist/viewer-bundle/info-OMHHGYJF-E773USRS.js +14 -0
- package/dist/viewer-bundle/info-OMHHGYJF-E773USRS.js.map +7 -0
- package/dist/viewer-bundle/infoDiagram-42DDH7IO-C7JGUXKK.js +59 -0
- package/dist/viewer-bundle/infoDiagram-42DDH7IO-C7JGUXKK.js.map +7 -0
- package/dist/viewer-bundle/ishikawaDiagram-UXIWVN3A-YBC4X4VB.js +1012 -0
- package/dist/viewer-bundle/ishikawaDiagram-UXIWVN3A-YBC4X4VB.js.map +7 -0
- package/dist/viewer-bundle/journeyDiagram-VCZTEJTY-6WKVEOOO.js +1303 -0
- package/dist/viewer-bundle/journeyDiagram-VCZTEJTY-6WKVEOOO.js.map +7 -0
- package/dist/viewer-bundle/kanban-definition-6JOO6SKY-URTTHHO4.js +1131 -0
- package/dist/viewer-bundle/kanban-definition-6JOO6SKY-URTTHHO4.js.map +7 -0
- package/dist/viewer-bundle/katex-QN5266ZE.js +14318 -0
- package/dist/viewer-bundle/katex-QN5266ZE.js.map +7 -0
- package/dist/viewer-bundle/light.css +15 -0
- package/dist/viewer-bundle/main.js +4816 -0
- package/dist/viewer-bundle/main.js.map +7 -0
- package/dist/viewer-bundle/mermaid.core-AEBXU2JK.js +1708 -0
- package/dist/viewer-bundle/mermaid.core-AEBXU2JK.js.map +7 -0
- package/dist/viewer-bundle/mindmap-definition-QFDTVHPH-KUMAMRSF.js +1277 -0
- package/dist/viewer-bundle/mindmap-definition-QFDTVHPH-KUMAMRSF.js.map +7 -0
- package/dist/viewer-bundle/packet-4T2RLAQJ-IRYWWA66.js +14 -0
- package/dist/viewer-bundle/packet-4T2RLAQJ-IRYWWA66.js.map +7 -0
- package/dist/viewer-bundle/pico.classless.min.css +4 -0
- package/dist/viewer-bundle/pie-ZZUOXDRM-JYO4VL5N.js +14 -0
- package/dist/viewer-bundle/pie-ZZUOXDRM-JYO4VL5N.js.map +7 -0
- package/dist/viewer-bundle/pieDiagram-DEJITSTG-QOEHQN3N.js +238 -0
- package/dist/viewer-bundle/pieDiagram-DEJITSTG-QOEHQN3N.js.map +7 -0
- package/dist/viewer-bundle/prompt-modal-C4LHI7BS.js +12 -0
- package/dist/viewer-bundle/prompt-modal-C4LHI7BS.js.map +7 -0
- package/dist/viewer-bundle/quadrantDiagram-34T5L4WZ-SJNPUU5N.js +1409 -0
- package/dist/viewer-bundle/quadrantDiagram-34T5L4WZ-SJNPUU5N.js.map +7 -0
- package/dist/viewer-bundle/radar-PYXPWWZC-45BRYQSB.js +14 -0
- package/dist/viewer-bundle/radar-PYXPWWZC-45BRYQSB.js.map +7 -0
- package/dist/viewer-bundle/reason-modal-MK34MQ73.js +68 -0
- package/dist/viewer-bundle/reason-modal-MK34MQ73.js.map +7 -0
- package/dist/viewer-bundle/requirementDiagram-MS252O5E-UOMT3FCC.js +1311 -0
- package/dist/viewer-bundle/requirementDiagram-MS252O5E-UOMT3FCC.js.map +7 -0
- package/dist/viewer-bundle/sankeyDiagram-XADWPNL6-LAVJ5C6A.js +1263 -0
- package/dist/viewer-bundle/sankeyDiagram-XADWPNL6-LAVJ5C6A.js.map +7 -0
- package/dist/viewer-bundle/sequenceDiagram-FGHM5R23-3IWTOUNQ.js +4655 -0
- package/dist/viewer-bundle/sequenceDiagram-FGHM5R23-3IWTOUNQ.js.map +7 -0
- package/dist/viewer-bundle/stateDiagram-FHFEXIEX-S2OVQQON.js +495 -0
- package/dist/viewer-bundle/stateDiagram-FHFEXIEX-S2OVQQON.js.map +7 -0
- package/dist/viewer-bundle/stateDiagram-v2-QKLJ7IA2-XNZ3XXSV.js +44 -0
- package/dist/viewer-bundle/stateDiagram-v2-QKLJ7IA2-XNZ3XXSV.js.map +7 -0
- package/dist/viewer-bundle/timeline-definition-GMOUNBTQ-FHVZ7MHE.js +1646 -0
- package/dist/viewer-bundle/timeline-definition-GMOUNBTQ-FHVZ7MHE.js.map +7 -0
- package/dist/viewer-bundle/treeView-SZITEDCU-RXZXNYAM.js +14 -0
- package/dist/viewer-bundle/treeView-SZITEDCU-RXZXNYAM.js.map +7 -0
- package/dist/viewer-bundle/treemap-W4RFUUIX-2IGOFSJM.js +14 -0
- package/dist/viewer-bundle/treemap-W4RFUUIX-2IGOFSJM.js.map +7 -0
- package/dist/viewer-bundle/vennDiagram-DHZGUBPP-HEAOEXEZ.js +2544 -0
- package/dist/viewer-bundle/vennDiagram-DHZGUBPP-HEAOEXEZ.js.map +7 -0
- package/dist/viewer-bundle/wardley-RL74JXVD-VSPCLOX2.js +14 -0
- package/dist/viewer-bundle/wardley-RL74JXVD-VSPCLOX2.js.map +7 -0
- package/dist/viewer-bundle/wardleyDiagram-NUSXRM2D-EBY4FG3X.js +938 -0
- package/dist/viewer-bundle/wardleyDiagram-NUSXRM2D-EBY4FG3X.js.map +7 -0
- package/dist/viewer-bundle/xychartDiagram-5P7HB3ND-SSMUQEXK.js +1952 -0
- package/dist/viewer-bundle/xychartDiagram-5P7HB3ND-SSMUQEXK.js.map +7 -0
- package/package.json +51 -0
|
@@ -0,0 +1,4816 @@
|
|
|
1
|
+
import {
|
|
2
|
+
showAlert,
|
|
3
|
+
showConfirmModal,
|
|
4
|
+
showInputModal
|
|
5
|
+
} from "./chunk-U5T7X4BV.js";
|
|
6
|
+
import "./chunk-DI52DQAC.js";
|
|
7
|
+
|
|
8
|
+
// src/router.ts
|
|
9
|
+
var Router = class {
|
|
10
|
+
routes = [];
|
|
11
|
+
notFoundHandler = async () => {
|
|
12
|
+
document.getElementById("app").innerHTML = "<p>Not found.</p>";
|
|
13
|
+
};
|
|
14
|
+
add(pattern, handler) {
|
|
15
|
+
const paramNames = [];
|
|
16
|
+
const regexStr = pattern.replace(/\//g, "\\/").replace(/:([a-zA-Z]+)|\*([a-zA-Z]+)/g, (_, name1, name2) => {
|
|
17
|
+
const name = name1 ?? name2;
|
|
18
|
+
paramNames.push(name);
|
|
19
|
+
return name1 ? "([^/]+)" : "(.+)";
|
|
20
|
+
});
|
|
21
|
+
this.routes.push({ pattern: new RegExp("^" + regexStr + "$"), paramNames, handler });
|
|
22
|
+
}
|
|
23
|
+
setNotFound(handler) {
|
|
24
|
+
this.notFoundHandler = handler;
|
|
25
|
+
}
|
|
26
|
+
async navigate() {
|
|
27
|
+
const hash = window.location.hash.slice(1) || "/";
|
|
28
|
+
if (!hash.startsWith("/spec/")) {
|
|
29
|
+
try {
|
|
30
|
+
sessionStorage.setItem("zg:prev-route", `#${hash}`);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const route of this.routes) {
|
|
35
|
+
const match = hash.match(route.pattern);
|
|
36
|
+
if (match) {
|
|
37
|
+
const params = {};
|
|
38
|
+
route.paramNames.forEach((name, i) => {
|
|
39
|
+
params[name] = decodeURIComponent(match[i + 1] ?? "");
|
|
40
|
+
});
|
|
41
|
+
await route.handler(params);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
await this.notFoundHandler({});
|
|
46
|
+
}
|
|
47
|
+
start() {
|
|
48
|
+
window.addEventListener("hashchange", () => {
|
|
49
|
+
void this.navigate();
|
|
50
|
+
});
|
|
51
|
+
if (document.readyState === "loading") {
|
|
52
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
53
|
+
void this.navigate();
|
|
54
|
+
}, { once: true });
|
|
55
|
+
} else {
|
|
56
|
+
void this.navigate();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/components/card.ts
|
|
62
|
+
function renderCard(spec) {
|
|
63
|
+
const card = document.createElement("article");
|
|
64
|
+
card.className = "zg-card";
|
|
65
|
+
card.dataset.spec = spec.name;
|
|
66
|
+
card.dataset.status = spec.status;
|
|
67
|
+
card.draggable = true;
|
|
68
|
+
const header = document.createElement("div");
|
|
69
|
+
header.className = "zg-card-header";
|
|
70
|
+
const name = document.createElement("h4");
|
|
71
|
+
name.className = "zg-card-name";
|
|
72
|
+
name.textContent = spec.name;
|
|
73
|
+
const editBtn = document.createElement("button");
|
|
74
|
+
editBtn.type = "button";
|
|
75
|
+
editBtn.className = "zg-card-edit";
|
|
76
|
+
editBtn.title = "Edit";
|
|
77
|
+
editBtn.setAttribute("aria-label", `Edit ${spec.name}`);
|
|
78
|
+
editBtn.textContent = "\u270E";
|
|
79
|
+
editBtn.addEventListener("click", async (e) => {
|
|
80
|
+
e.stopPropagation();
|
|
81
|
+
const { showEditModal } = await import("./edit-modal-BEGC2AO6.js");
|
|
82
|
+
const saved = await showEditModal({ spec });
|
|
83
|
+
if (saved) window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
84
|
+
});
|
|
85
|
+
const delBtn = document.createElement("button");
|
|
86
|
+
delBtn.type = "button";
|
|
87
|
+
delBtn.className = "zg-card-delete";
|
|
88
|
+
delBtn.title = "Delete spec";
|
|
89
|
+
delBtn.setAttribute("aria-label", `Delete ${spec.name}`);
|
|
90
|
+
delBtn.textContent = "\u{1F5D1}";
|
|
91
|
+
delBtn.addEventListener("click", async (e) => {
|
|
92
|
+
e.stopPropagation();
|
|
93
|
+
const { showConfirmModal: showConfirmModal2, showAlert: showAlert2 } = await import("./prompt-modal-C4LHI7BS.js");
|
|
94
|
+
const ok = await showConfirmModal2({
|
|
95
|
+
title: `Delete "${spec.name}"?`,
|
|
96
|
+
message: "This removes the entire spec folder from disk and commits the deletion. You can still recover it from git history, but no in-app undo.",
|
|
97
|
+
confirmLabel: "Delete spec",
|
|
98
|
+
destructive: true
|
|
99
|
+
});
|
|
100
|
+
if (!ok) return;
|
|
101
|
+
try {
|
|
102
|
+
await window.zettelgeistBackend.deleteSpec(spec.name);
|
|
103
|
+
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
104
|
+
} catch (err) {
|
|
105
|
+
void showAlert2("Delete failed", err.message);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
header.appendChild(name);
|
|
109
|
+
header.appendChild(editBtn);
|
|
110
|
+
header.appendChild(delBtn);
|
|
111
|
+
const meta = document.createElement("div");
|
|
112
|
+
meta.className = "zg-card-meta";
|
|
113
|
+
const progressLabel = document.createElement("span");
|
|
114
|
+
progressLabel.textContent = spec.progress;
|
|
115
|
+
meta.appendChild(progressLabel);
|
|
116
|
+
const { done, total } = parseProgress(spec.progress);
|
|
117
|
+
if (total > 0) {
|
|
118
|
+
const bar = document.createElement("div");
|
|
119
|
+
bar.className = "zg-card-progress";
|
|
120
|
+
const fill = document.createElement("div");
|
|
121
|
+
fill.className = "zg-card-progress-bar";
|
|
122
|
+
fill.style.width = `${Math.round(done / total * 100)}%`;
|
|
123
|
+
bar.appendChild(fill);
|
|
124
|
+
meta.appendChild(bar);
|
|
125
|
+
}
|
|
126
|
+
card.appendChild(header);
|
|
127
|
+
card.appendChild(meta);
|
|
128
|
+
if (spec.pr || spec.branch) {
|
|
129
|
+
const badges = document.createElement("div");
|
|
130
|
+
badges.className = "zg-card-badges";
|
|
131
|
+
if (spec.pr) {
|
|
132
|
+
const a = document.createElement("a");
|
|
133
|
+
a.className = "zg-badge zg-badge-pr";
|
|
134
|
+
a.href = spec.pr;
|
|
135
|
+
a.target = "_blank";
|
|
136
|
+
a.rel = "noopener";
|
|
137
|
+
a.title = spec.pr;
|
|
138
|
+
a.textContent = prLabel(spec.pr);
|
|
139
|
+
a.addEventListener("click", (e) => e.stopPropagation());
|
|
140
|
+
badges.appendChild(a);
|
|
141
|
+
}
|
|
142
|
+
if (spec.branch) {
|
|
143
|
+
const span = document.createElement("span");
|
|
144
|
+
span.className = "zg-badge zg-badge-branch";
|
|
145
|
+
span.title = spec.branch;
|
|
146
|
+
span.textContent = spec.branch;
|
|
147
|
+
badges.appendChild(span);
|
|
148
|
+
}
|
|
149
|
+
card.appendChild(badges);
|
|
150
|
+
}
|
|
151
|
+
if (spec.blockedBy) {
|
|
152
|
+
const blocked = document.createElement("small");
|
|
153
|
+
blocked.className = "zg-card-blocked";
|
|
154
|
+
blocked.title = spec.blockedBy;
|
|
155
|
+
blocked.textContent = `blocked: ${spec.blockedBy}`;
|
|
156
|
+
card.appendChild(blocked);
|
|
157
|
+
}
|
|
158
|
+
card.addEventListener("click", () => {
|
|
159
|
+
window.location.hash = `#/spec/${encodeURIComponent(spec.name)}`;
|
|
160
|
+
});
|
|
161
|
+
card.addEventListener("dragstart", (e) => {
|
|
162
|
+
if (!e.dataTransfer) return;
|
|
163
|
+
e.dataTransfer.setData("text/plain", spec.name);
|
|
164
|
+
e.dataTransfer.setData("application/x-zg-status", spec.status);
|
|
165
|
+
e.dataTransfer.effectAllowed = "move";
|
|
166
|
+
});
|
|
167
|
+
return card;
|
|
168
|
+
}
|
|
169
|
+
function parseProgress(s) {
|
|
170
|
+
const m = /^(\d+)\s*\/\s*(\d+)$/.exec(s);
|
|
171
|
+
if (!m) return { done: 0, total: 0 };
|
|
172
|
+
return { done: Number(m[1]), total: Number(m[2]) };
|
|
173
|
+
}
|
|
174
|
+
function prLabel(url) {
|
|
175
|
+
const gh = /\/pull\/(\d+)/.exec(url);
|
|
176
|
+
if (gh) return `PR #${gh[1]}`;
|
|
177
|
+
return "PR";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ../../node_modules/.pnpm/dompurify@3.2.0/node_modules/dompurify/dist/purify.es.mjs
|
|
181
|
+
var {
|
|
182
|
+
entries,
|
|
183
|
+
setPrototypeOf,
|
|
184
|
+
isFrozen,
|
|
185
|
+
getPrototypeOf,
|
|
186
|
+
getOwnPropertyDescriptor
|
|
187
|
+
} = Object;
|
|
188
|
+
var {
|
|
189
|
+
freeze,
|
|
190
|
+
seal,
|
|
191
|
+
create
|
|
192
|
+
} = Object;
|
|
193
|
+
var {
|
|
194
|
+
apply,
|
|
195
|
+
construct
|
|
196
|
+
} = typeof Reflect !== "undefined" && Reflect;
|
|
197
|
+
if (!freeze) {
|
|
198
|
+
freeze = function freeze2(x) {
|
|
199
|
+
return x;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (!seal) {
|
|
203
|
+
seal = function seal2(x) {
|
|
204
|
+
return x;
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (!apply) {
|
|
208
|
+
apply = function apply2(fun, thisValue, args) {
|
|
209
|
+
return fun.apply(thisValue, args);
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (!construct) {
|
|
213
|
+
construct = function construct2(Func, args) {
|
|
214
|
+
return new Func(...args);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
var arrayForEach = unapply(Array.prototype.forEach);
|
|
218
|
+
var arrayPop = unapply(Array.prototype.pop);
|
|
219
|
+
var arrayPush = unapply(Array.prototype.push);
|
|
220
|
+
var stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
221
|
+
var stringToString = unapply(String.prototype.toString);
|
|
222
|
+
var stringMatch = unapply(String.prototype.match);
|
|
223
|
+
var stringReplace = unapply(String.prototype.replace);
|
|
224
|
+
var stringIndexOf = unapply(String.prototype.indexOf);
|
|
225
|
+
var stringTrim = unapply(String.prototype.trim);
|
|
226
|
+
var objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
227
|
+
var regExpTest = unapply(RegExp.prototype.test);
|
|
228
|
+
var typeErrorCreate = unconstruct(TypeError);
|
|
229
|
+
function unapply(func) {
|
|
230
|
+
return function(thisArg) {
|
|
231
|
+
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
232
|
+
args[_key - 1] = arguments[_key];
|
|
233
|
+
}
|
|
234
|
+
return apply(func, thisArg, args);
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function unconstruct(func) {
|
|
238
|
+
return function() {
|
|
239
|
+
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
|
240
|
+
args[_key2] = arguments[_key2];
|
|
241
|
+
}
|
|
242
|
+
return construct(func, args);
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function addToSet(set, array) {
|
|
246
|
+
let transformCaseFunc = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : stringToLowerCase;
|
|
247
|
+
if (setPrototypeOf) {
|
|
248
|
+
setPrototypeOf(set, null);
|
|
249
|
+
}
|
|
250
|
+
let l = array.length;
|
|
251
|
+
while (l--) {
|
|
252
|
+
let element = array[l];
|
|
253
|
+
if (typeof element === "string") {
|
|
254
|
+
const lcElement = transformCaseFunc(element);
|
|
255
|
+
if (lcElement !== element) {
|
|
256
|
+
if (!isFrozen(array)) {
|
|
257
|
+
array[l] = lcElement;
|
|
258
|
+
}
|
|
259
|
+
element = lcElement;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
set[element] = true;
|
|
263
|
+
}
|
|
264
|
+
return set;
|
|
265
|
+
}
|
|
266
|
+
function cleanArray(array) {
|
|
267
|
+
for (let index = 0; index < array.length; index++) {
|
|
268
|
+
const isPropertyExist = objectHasOwnProperty(array, index);
|
|
269
|
+
if (!isPropertyExist) {
|
|
270
|
+
array[index] = null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return array;
|
|
274
|
+
}
|
|
275
|
+
function clone(object) {
|
|
276
|
+
const newObject = create(null);
|
|
277
|
+
for (const [property, value] of entries(object)) {
|
|
278
|
+
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
279
|
+
if (isPropertyExist) {
|
|
280
|
+
if (Array.isArray(value)) {
|
|
281
|
+
newObject[property] = cleanArray(value);
|
|
282
|
+
} else if (value && typeof value === "object" && value.constructor === Object) {
|
|
283
|
+
newObject[property] = clone(value);
|
|
284
|
+
} else {
|
|
285
|
+
newObject[property] = value;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return newObject;
|
|
290
|
+
}
|
|
291
|
+
function lookupGetter(object, prop) {
|
|
292
|
+
while (object !== null) {
|
|
293
|
+
const desc = getOwnPropertyDescriptor(object, prop);
|
|
294
|
+
if (desc) {
|
|
295
|
+
if (desc.get) {
|
|
296
|
+
return unapply(desc.get);
|
|
297
|
+
}
|
|
298
|
+
if (typeof desc.value === "function") {
|
|
299
|
+
return unapply(desc.value);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
object = getPrototypeOf(object);
|
|
303
|
+
}
|
|
304
|
+
function fallbackValue() {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
return fallbackValue;
|
|
308
|
+
}
|
|
309
|
+
var html$1 = freeze(["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "section", "select", "shadow", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]);
|
|
310
|
+
var svg$1 = freeze(["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"]);
|
|
311
|
+
var svgFilters = freeze(["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"]);
|
|
312
|
+
var svgDisallowed = freeze(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]);
|
|
313
|
+
var mathMl$1 = freeze(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]);
|
|
314
|
+
var mathMlDisallowed = freeze(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]);
|
|
315
|
+
var text = freeze(["#text"]);
|
|
316
|
+
var html = freeze(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns", "slot"]);
|
|
317
|
+
var svg = freeze(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]);
|
|
318
|
+
var mathMl = freeze(["accent", "accentunder", "align", "bevelled", "close", "columnsalign", "columnlines", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lspace", "lquote", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]);
|
|
319
|
+
var xml = freeze(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]);
|
|
320
|
+
var MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm);
|
|
321
|
+
var ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
|
|
322
|
+
var TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
|
|
323
|
+
var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/);
|
|
324
|
+
var ARIA_ATTR = seal(/^aria-[\-\w]+$/);
|
|
325
|
+
var IS_ALLOWED_URI = seal(
|
|
326
|
+
/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i
|
|
327
|
+
// eslint-disable-line no-useless-escape
|
|
328
|
+
);
|
|
329
|
+
var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
|
|
330
|
+
var ATTR_WHITESPACE = seal(
|
|
331
|
+
/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g
|
|
332
|
+
// eslint-disable-line no-control-regex
|
|
333
|
+
);
|
|
334
|
+
var DOCTYPE_NAME = seal(/^html$/i);
|
|
335
|
+
var CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
336
|
+
var EXPRESSIONS = /* @__PURE__ */ Object.freeze({
|
|
337
|
+
__proto__: null,
|
|
338
|
+
ARIA_ATTR,
|
|
339
|
+
ATTR_WHITESPACE,
|
|
340
|
+
CUSTOM_ELEMENT,
|
|
341
|
+
DATA_ATTR,
|
|
342
|
+
DOCTYPE_NAME,
|
|
343
|
+
ERB_EXPR,
|
|
344
|
+
IS_ALLOWED_URI,
|
|
345
|
+
IS_SCRIPT_OR_DATA,
|
|
346
|
+
MUSTACHE_EXPR,
|
|
347
|
+
TMPLIT_EXPR
|
|
348
|
+
});
|
|
349
|
+
var NODE_TYPE = {
|
|
350
|
+
element: 1,
|
|
351
|
+
attribute: 2,
|
|
352
|
+
text: 3,
|
|
353
|
+
cdataSection: 4,
|
|
354
|
+
entityReference: 5,
|
|
355
|
+
// Deprecated
|
|
356
|
+
entityNode: 6,
|
|
357
|
+
// Deprecated
|
|
358
|
+
progressingInstruction: 7,
|
|
359
|
+
comment: 8,
|
|
360
|
+
document: 9,
|
|
361
|
+
documentType: 10,
|
|
362
|
+
documentFragment: 11,
|
|
363
|
+
notation: 12
|
|
364
|
+
// Deprecated
|
|
365
|
+
};
|
|
366
|
+
var getGlobal = function getGlobal2() {
|
|
367
|
+
return typeof window === "undefined" ? null : window;
|
|
368
|
+
};
|
|
369
|
+
var _createTrustedTypesPolicy = function _createTrustedTypesPolicy2(trustedTypes, purifyHostElement) {
|
|
370
|
+
if (typeof trustedTypes !== "object" || typeof trustedTypes.createPolicy !== "function") {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
let suffix = null;
|
|
374
|
+
const ATTR_NAME = "data-tt-policy-suffix";
|
|
375
|
+
if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
|
|
376
|
+
suffix = purifyHostElement.getAttribute(ATTR_NAME);
|
|
377
|
+
}
|
|
378
|
+
const policyName = "dompurify" + (suffix ? "#" + suffix : "");
|
|
379
|
+
try {
|
|
380
|
+
return trustedTypes.createPolicy(policyName, {
|
|
381
|
+
createHTML(html3) {
|
|
382
|
+
return html3;
|
|
383
|
+
},
|
|
384
|
+
createScriptURL(scriptUrl) {
|
|
385
|
+
return scriptUrl;
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
} catch (_) {
|
|
389
|
+
console.warn("TrustedTypes policy " + policyName + " could not be created.");
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
function createDOMPurify() {
|
|
394
|
+
let window2 = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
|
|
395
|
+
const DOMPurify = (root) => createDOMPurify(root);
|
|
396
|
+
DOMPurify.version = "3.2.0";
|
|
397
|
+
DOMPurify.removed = [];
|
|
398
|
+
if (!window2 || !window2.document || window2.document.nodeType !== NODE_TYPE.document) {
|
|
399
|
+
DOMPurify.isSupported = false;
|
|
400
|
+
return DOMPurify;
|
|
401
|
+
}
|
|
402
|
+
let {
|
|
403
|
+
document: document2
|
|
404
|
+
} = window2;
|
|
405
|
+
const originalDocument = document2;
|
|
406
|
+
const currentScript = originalDocument.currentScript;
|
|
407
|
+
const {
|
|
408
|
+
DocumentFragment,
|
|
409
|
+
HTMLTemplateElement,
|
|
410
|
+
Node,
|
|
411
|
+
Element,
|
|
412
|
+
NodeFilter,
|
|
413
|
+
NamedNodeMap = window2.NamedNodeMap || window2.MozNamedAttrMap,
|
|
414
|
+
HTMLFormElement,
|
|
415
|
+
DOMParser,
|
|
416
|
+
trustedTypes
|
|
417
|
+
} = window2;
|
|
418
|
+
const ElementPrototype = Element.prototype;
|
|
419
|
+
const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
|
|
420
|
+
const remove = lookupGetter(ElementPrototype, "remove");
|
|
421
|
+
const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
|
|
422
|
+
const getChildNodes = lookupGetter(ElementPrototype, "childNodes");
|
|
423
|
+
const getParentNode = lookupGetter(ElementPrototype, "parentNode");
|
|
424
|
+
if (typeof HTMLTemplateElement === "function") {
|
|
425
|
+
const template = document2.createElement("template");
|
|
426
|
+
if (template.content && template.content.ownerDocument) {
|
|
427
|
+
document2 = template.content.ownerDocument;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
let trustedTypesPolicy;
|
|
431
|
+
let emptyHTML = "";
|
|
432
|
+
const {
|
|
433
|
+
implementation,
|
|
434
|
+
createNodeIterator,
|
|
435
|
+
createDocumentFragment,
|
|
436
|
+
getElementsByTagName
|
|
437
|
+
} = document2;
|
|
438
|
+
const {
|
|
439
|
+
importNode
|
|
440
|
+
} = originalDocument;
|
|
441
|
+
let hooks = {};
|
|
442
|
+
DOMPurify.isSupported = typeof entries === "function" && typeof getParentNode === "function" && implementation && implementation.createHTMLDocument !== void 0;
|
|
443
|
+
const {
|
|
444
|
+
MUSTACHE_EXPR: MUSTACHE_EXPR2,
|
|
445
|
+
ERB_EXPR: ERB_EXPR2,
|
|
446
|
+
TMPLIT_EXPR: TMPLIT_EXPR2,
|
|
447
|
+
DATA_ATTR: DATA_ATTR2,
|
|
448
|
+
ARIA_ATTR: ARIA_ATTR2,
|
|
449
|
+
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA2,
|
|
450
|
+
ATTR_WHITESPACE: ATTR_WHITESPACE2,
|
|
451
|
+
CUSTOM_ELEMENT: CUSTOM_ELEMENT2
|
|
452
|
+
} = EXPRESSIONS;
|
|
453
|
+
let {
|
|
454
|
+
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
455
|
+
} = EXPRESSIONS;
|
|
456
|
+
let ALLOWED_TAGS = null;
|
|
457
|
+
const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
|
|
458
|
+
let ALLOWED_ATTR = null;
|
|
459
|
+
const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
|
|
460
|
+
let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
|
|
461
|
+
tagNameCheck: {
|
|
462
|
+
writable: true,
|
|
463
|
+
configurable: false,
|
|
464
|
+
enumerable: true,
|
|
465
|
+
value: null
|
|
466
|
+
},
|
|
467
|
+
attributeNameCheck: {
|
|
468
|
+
writable: true,
|
|
469
|
+
configurable: false,
|
|
470
|
+
enumerable: true,
|
|
471
|
+
value: null
|
|
472
|
+
},
|
|
473
|
+
allowCustomizedBuiltInElements: {
|
|
474
|
+
writable: true,
|
|
475
|
+
configurable: false,
|
|
476
|
+
enumerable: true,
|
|
477
|
+
value: false
|
|
478
|
+
}
|
|
479
|
+
}));
|
|
480
|
+
let FORBID_TAGS = null;
|
|
481
|
+
let FORBID_ATTR = null;
|
|
482
|
+
let ALLOW_ARIA_ATTR = true;
|
|
483
|
+
let ALLOW_DATA_ATTR = true;
|
|
484
|
+
let ALLOW_UNKNOWN_PROTOCOLS = false;
|
|
485
|
+
let ALLOW_SELF_CLOSE_IN_ATTR = true;
|
|
486
|
+
let SAFE_FOR_TEMPLATES = false;
|
|
487
|
+
let SAFE_FOR_XML = true;
|
|
488
|
+
let WHOLE_DOCUMENT = false;
|
|
489
|
+
let SET_CONFIG = false;
|
|
490
|
+
let FORCE_BODY = false;
|
|
491
|
+
let RETURN_DOM = false;
|
|
492
|
+
let RETURN_DOM_FRAGMENT = false;
|
|
493
|
+
let RETURN_TRUSTED_TYPE = false;
|
|
494
|
+
let SANITIZE_DOM = true;
|
|
495
|
+
let SANITIZE_NAMED_PROPS = false;
|
|
496
|
+
const SANITIZE_NAMED_PROPS_PREFIX = "user-content-";
|
|
497
|
+
let KEEP_CONTENT = true;
|
|
498
|
+
let IN_PLACE = false;
|
|
499
|
+
let USE_PROFILES = {};
|
|
500
|
+
let FORBID_CONTENTS = null;
|
|
501
|
+
const DEFAULT_FORBID_CONTENTS = addToSet({}, ["annotation-xml", "audio", "colgroup", "desc", "foreignobject", "head", "iframe", "math", "mi", "mn", "mo", "ms", "mtext", "noembed", "noframes", "noscript", "plaintext", "script", "style", "svg", "template", "thead", "title", "video", "xmp"]);
|
|
502
|
+
let DATA_URI_TAGS = null;
|
|
503
|
+
const DEFAULT_DATA_URI_TAGS = addToSet({}, ["audio", "video", "img", "source", "image", "track"]);
|
|
504
|
+
let URI_SAFE_ATTRIBUTES = null;
|
|
505
|
+
const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]);
|
|
506
|
+
const MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
|
|
507
|
+
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
508
|
+
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
|
509
|
+
let NAMESPACE = HTML_NAMESPACE;
|
|
510
|
+
let IS_EMPTY_INPUT = false;
|
|
511
|
+
let ALLOWED_NAMESPACES = null;
|
|
512
|
+
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
513
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ["mi", "mo", "mn", "ms", "mtext"]);
|
|
514
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, ["annotation-xml"]);
|
|
515
|
+
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ["title", "style", "font", "a", "script"]);
|
|
516
|
+
let PARSER_MEDIA_TYPE = null;
|
|
517
|
+
const SUPPORTED_PARSER_MEDIA_TYPES = ["application/xhtml+xml", "text/html"];
|
|
518
|
+
const DEFAULT_PARSER_MEDIA_TYPE = "text/html";
|
|
519
|
+
let transformCaseFunc = null;
|
|
520
|
+
let CONFIG = null;
|
|
521
|
+
const formElement = document2.createElement("form");
|
|
522
|
+
const isRegexOrFunction = function isRegexOrFunction2(testValue) {
|
|
523
|
+
return testValue instanceof RegExp || testValue instanceof Function;
|
|
524
|
+
};
|
|
525
|
+
const _parseConfig = function _parseConfig2() {
|
|
526
|
+
let cfg = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
|
|
527
|
+
if (CONFIG && CONFIG === cfg) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (!cfg || typeof cfg !== "object") {
|
|
531
|
+
cfg = {};
|
|
532
|
+
}
|
|
533
|
+
cfg = clone(cfg);
|
|
534
|
+
PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes
|
|
535
|
+
SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
|
|
536
|
+
transformCaseFunc = PARSER_MEDIA_TYPE === "application/xhtml+xml" ? stringToString : stringToLowerCase;
|
|
537
|
+
ALLOWED_TAGS = objectHasOwnProperty(cfg, "ALLOWED_TAGS") ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
|
|
538
|
+
ALLOWED_ATTR = objectHasOwnProperty(cfg, "ALLOWED_ATTR") ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
|
|
539
|
+
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, "ALLOWED_NAMESPACES") ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
|
|
540
|
+
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR") ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
|
|
541
|
+
DATA_URI_TAGS = objectHasOwnProperty(cfg, "ADD_DATA_URI_TAGS") ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
|
|
542
|
+
FORBID_CONTENTS = objectHasOwnProperty(cfg, "FORBID_CONTENTS") ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
|
|
543
|
+
FORBID_TAGS = objectHasOwnProperty(cfg, "FORBID_TAGS") ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
|
|
544
|
+
FORBID_ATTR = objectHasOwnProperty(cfg, "FORBID_ATTR") ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
|
|
545
|
+
USE_PROFILES = objectHasOwnProperty(cfg, "USE_PROFILES") ? cfg.USE_PROFILES : false;
|
|
546
|
+
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false;
|
|
547
|
+
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false;
|
|
548
|
+
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false;
|
|
549
|
+
ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false;
|
|
550
|
+
SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false;
|
|
551
|
+
SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false;
|
|
552
|
+
WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false;
|
|
553
|
+
RETURN_DOM = cfg.RETURN_DOM || false;
|
|
554
|
+
RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false;
|
|
555
|
+
RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false;
|
|
556
|
+
FORCE_BODY = cfg.FORCE_BODY || false;
|
|
557
|
+
SANITIZE_DOM = cfg.SANITIZE_DOM !== false;
|
|
558
|
+
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false;
|
|
559
|
+
KEEP_CONTENT = cfg.KEEP_CONTENT !== false;
|
|
560
|
+
IN_PLACE = cfg.IN_PLACE || false;
|
|
561
|
+
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
|
|
562
|
+
NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
|
|
563
|
+
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
|
|
564
|
+
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
|
|
565
|
+
CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
|
|
566
|
+
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
|
|
567
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
|
|
568
|
+
}
|
|
569
|
+
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
|
|
570
|
+
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
|
|
571
|
+
}
|
|
572
|
+
if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === "boolean") {
|
|
573
|
+
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
|
|
574
|
+
}
|
|
575
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
576
|
+
ALLOW_DATA_ATTR = false;
|
|
577
|
+
}
|
|
578
|
+
if (RETURN_DOM_FRAGMENT) {
|
|
579
|
+
RETURN_DOM = true;
|
|
580
|
+
}
|
|
581
|
+
if (USE_PROFILES) {
|
|
582
|
+
ALLOWED_TAGS = addToSet({}, text);
|
|
583
|
+
ALLOWED_ATTR = [];
|
|
584
|
+
if (USE_PROFILES.html === true) {
|
|
585
|
+
addToSet(ALLOWED_TAGS, html$1);
|
|
586
|
+
addToSet(ALLOWED_ATTR, html);
|
|
587
|
+
}
|
|
588
|
+
if (USE_PROFILES.svg === true) {
|
|
589
|
+
addToSet(ALLOWED_TAGS, svg$1);
|
|
590
|
+
addToSet(ALLOWED_ATTR, svg);
|
|
591
|
+
addToSet(ALLOWED_ATTR, xml);
|
|
592
|
+
}
|
|
593
|
+
if (USE_PROFILES.svgFilters === true) {
|
|
594
|
+
addToSet(ALLOWED_TAGS, svgFilters);
|
|
595
|
+
addToSet(ALLOWED_ATTR, svg);
|
|
596
|
+
addToSet(ALLOWED_ATTR, xml);
|
|
597
|
+
}
|
|
598
|
+
if (USE_PROFILES.mathMl === true) {
|
|
599
|
+
addToSet(ALLOWED_TAGS, mathMl$1);
|
|
600
|
+
addToSet(ALLOWED_ATTR, mathMl);
|
|
601
|
+
addToSet(ALLOWED_ATTR, xml);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (cfg.ADD_TAGS) {
|
|
605
|
+
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
606
|
+
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
607
|
+
}
|
|
608
|
+
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
609
|
+
}
|
|
610
|
+
if (cfg.ADD_ATTR) {
|
|
611
|
+
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
612
|
+
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
613
|
+
}
|
|
614
|
+
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
615
|
+
}
|
|
616
|
+
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
617
|
+
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
618
|
+
}
|
|
619
|
+
if (cfg.FORBID_CONTENTS) {
|
|
620
|
+
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
621
|
+
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
622
|
+
}
|
|
623
|
+
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
624
|
+
}
|
|
625
|
+
if (KEEP_CONTENT) {
|
|
626
|
+
ALLOWED_TAGS["#text"] = true;
|
|
627
|
+
}
|
|
628
|
+
if (WHOLE_DOCUMENT) {
|
|
629
|
+
addToSet(ALLOWED_TAGS, ["html", "head", "body"]);
|
|
630
|
+
}
|
|
631
|
+
if (ALLOWED_TAGS.table) {
|
|
632
|
+
addToSet(ALLOWED_TAGS, ["tbody"]);
|
|
633
|
+
delete FORBID_TAGS.tbody;
|
|
634
|
+
}
|
|
635
|
+
if (cfg.TRUSTED_TYPES_POLICY) {
|
|
636
|
+
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== "function") {
|
|
637
|
+
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
|
|
638
|
+
}
|
|
639
|
+
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== "function") {
|
|
640
|
+
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
641
|
+
}
|
|
642
|
+
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
643
|
+
emptyHTML = trustedTypesPolicy.createHTML("");
|
|
644
|
+
} else {
|
|
645
|
+
if (trustedTypesPolicy === void 0) {
|
|
646
|
+
trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
647
|
+
}
|
|
648
|
+
if (trustedTypesPolicy !== null && typeof emptyHTML === "string") {
|
|
649
|
+
emptyHTML = trustedTypesPolicy.createHTML("");
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (freeze) {
|
|
653
|
+
freeze(cfg);
|
|
654
|
+
}
|
|
655
|
+
CONFIG = cfg;
|
|
656
|
+
};
|
|
657
|
+
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
658
|
+
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
659
|
+
const _checkValidNamespace = function _checkValidNamespace2(element) {
|
|
660
|
+
let parent = getParentNode(element);
|
|
661
|
+
if (!parent || !parent.tagName) {
|
|
662
|
+
parent = {
|
|
663
|
+
namespaceURI: NAMESPACE,
|
|
664
|
+
tagName: "template"
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
const tagName = stringToLowerCase(element.tagName);
|
|
668
|
+
const parentTagName = stringToLowerCase(parent.tagName);
|
|
669
|
+
if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
673
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
674
|
+
return tagName === "svg";
|
|
675
|
+
}
|
|
676
|
+
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
677
|
+
return tagName === "svg" && (parentTagName === "annotation-xml" || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
678
|
+
}
|
|
679
|
+
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
680
|
+
}
|
|
681
|
+
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
682
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
683
|
+
return tagName === "math";
|
|
684
|
+
}
|
|
685
|
+
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
686
|
+
return tagName === "math" && HTML_INTEGRATION_POINTS[parentTagName];
|
|
687
|
+
}
|
|
688
|
+
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
689
|
+
}
|
|
690
|
+
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
691
|
+
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
698
|
+
}
|
|
699
|
+
if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
return false;
|
|
703
|
+
};
|
|
704
|
+
const _forceRemove = function _forceRemove2(node) {
|
|
705
|
+
arrayPush(DOMPurify.removed, {
|
|
706
|
+
element: node
|
|
707
|
+
});
|
|
708
|
+
try {
|
|
709
|
+
getParentNode(node).removeChild(node);
|
|
710
|
+
} catch (_) {
|
|
711
|
+
remove(node);
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
const _removeAttribute = function _removeAttribute2(name, element) {
|
|
715
|
+
try {
|
|
716
|
+
arrayPush(DOMPurify.removed, {
|
|
717
|
+
attribute: element.getAttributeNode(name),
|
|
718
|
+
from: element
|
|
719
|
+
});
|
|
720
|
+
} catch (_) {
|
|
721
|
+
arrayPush(DOMPurify.removed, {
|
|
722
|
+
attribute: null,
|
|
723
|
+
from: element
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
element.removeAttribute(name);
|
|
727
|
+
if (name === "is" && !ALLOWED_ATTR[name]) {
|
|
728
|
+
if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
|
|
729
|
+
try {
|
|
730
|
+
_forceRemove(element);
|
|
731
|
+
} catch (_) {
|
|
732
|
+
}
|
|
733
|
+
} else {
|
|
734
|
+
try {
|
|
735
|
+
element.setAttribute(name, "");
|
|
736
|
+
} catch (_) {
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
const _initDocument = function _initDocument2(dirty) {
|
|
742
|
+
let doc = null;
|
|
743
|
+
let leadingWhitespace = null;
|
|
744
|
+
if (FORCE_BODY) {
|
|
745
|
+
dirty = "<remove></remove>" + dirty;
|
|
746
|
+
} else {
|
|
747
|
+
const matches = stringMatch(dirty, /^[\r\n\t ]+/);
|
|
748
|
+
leadingWhitespace = matches && matches[0];
|
|
749
|
+
}
|
|
750
|
+
if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && NAMESPACE === HTML_NAMESPACE) {
|
|
751
|
+
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + "</body></html>";
|
|
752
|
+
}
|
|
753
|
+
const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
754
|
+
if (NAMESPACE === HTML_NAMESPACE) {
|
|
755
|
+
try {
|
|
756
|
+
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
|
|
757
|
+
} catch (_) {
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (!doc || !doc.documentElement) {
|
|
761
|
+
doc = implementation.createDocument(NAMESPACE, "template", null);
|
|
762
|
+
try {
|
|
763
|
+
doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
|
|
764
|
+
} catch (_) {
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
const body = doc.body || doc.documentElement;
|
|
768
|
+
if (dirty && leadingWhitespace) {
|
|
769
|
+
body.insertBefore(document2.createTextNode(leadingWhitespace), body.childNodes[0] || null);
|
|
770
|
+
}
|
|
771
|
+
if (NAMESPACE === HTML_NAMESPACE) {
|
|
772
|
+
return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? "html" : "body")[0];
|
|
773
|
+
}
|
|
774
|
+
return WHOLE_DOCUMENT ? doc.documentElement : body;
|
|
775
|
+
};
|
|
776
|
+
const _createNodeIterator = function _createNodeIterator2(root) {
|
|
777
|
+
return createNodeIterator.call(
|
|
778
|
+
root.ownerDocument || root,
|
|
779
|
+
root,
|
|
780
|
+
// eslint-disable-next-line no-bitwise
|
|
781
|
+
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION,
|
|
782
|
+
null
|
|
783
|
+
);
|
|
784
|
+
};
|
|
785
|
+
const _isClobbered = function _isClobbered2(element) {
|
|
786
|
+
return element instanceof HTMLFormElement && (typeof element.nodeName !== "string" || typeof element.textContent !== "string" || typeof element.removeChild !== "function" || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== "function" || typeof element.setAttribute !== "function" || typeof element.namespaceURI !== "string" || typeof element.insertBefore !== "function" || typeof element.hasChildNodes !== "function");
|
|
787
|
+
};
|
|
788
|
+
const _isNode = function _isNode2(value) {
|
|
789
|
+
return typeof Node === "function" && value instanceof Node;
|
|
790
|
+
};
|
|
791
|
+
function _executeHook(entryPoint, currentNode, data) {
|
|
792
|
+
if (!hooks[entryPoint]) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
arrayForEach(hooks[entryPoint], (hook) => {
|
|
796
|
+
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
const _sanitizeElements = function _sanitizeElements2(currentNode) {
|
|
800
|
+
let content = null;
|
|
801
|
+
_executeHook("beforeSanitizeElements", currentNode, null);
|
|
802
|
+
if (_isClobbered(currentNode)) {
|
|
803
|
+
_forceRemove(currentNode);
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
807
|
+
_executeHook("uponSanitizeElement", currentNode, {
|
|
808
|
+
tagName,
|
|
809
|
+
allowedTags: ALLOWED_TAGS
|
|
810
|
+
});
|
|
811
|
+
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
|
|
812
|
+
_forceRemove(currentNode);
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
|
|
816
|
+
_forceRemove(currentNode);
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
|
|
820
|
+
_forceRemove(currentNode);
|
|
821
|
+
return true;
|
|
822
|
+
}
|
|
823
|
+
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
824
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
825
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
833
|
+
const parentNode = getParentNode(currentNode) || currentNode.parentNode;
|
|
834
|
+
const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
|
|
835
|
+
if (childNodes && parentNode) {
|
|
836
|
+
const childCount = childNodes.length;
|
|
837
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
838
|
+
const childClone = cloneNode(childNodes[i], true);
|
|
839
|
+
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
|
|
840
|
+
parentNode.insertBefore(childClone, getNextSibling(currentNode));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
_forceRemove(currentNode);
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
|
|
848
|
+
_forceRemove(currentNode);
|
|
849
|
+
return true;
|
|
850
|
+
}
|
|
851
|
+
if ((tagName === "noscript" || tagName === "noembed" || tagName === "noframes") && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
|
|
852
|
+
_forceRemove(currentNode);
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
856
|
+
content = currentNode.textContent;
|
|
857
|
+
arrayForEach([MUSTACHE_EXPR2, ERB_EXPR2, TMPLIT_EXPR2], (expr) => {
|
|
858
|
+
content = stringReplace(content, expr, " ");
|
|
859
|
+
});
|
|
860
|
+
if (currentNode.textContent !== content) {
|
|
861
|
+
arrayPush(DOMPurify.removed, {
|
|
862
|
+
element: currentNode.cloneNode()
|
|
863
|
+
});
|
|
864
|
+
currentNode.textContent = content;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
_executeHook("afterSanitizeElements", currentNode, null);
|
|
868
|
+
return false;
|
|
869
|
+
};
|
|
870
|
+
const _isValidAttribute = function _isValidAttribute2(lcTag, lcName, value) {
|
|
871
|
+
if (SANITIZE_DOM && (lcName === "id" || lcName === "name") && (value in document2 || value in formElement)) {
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
874
|
+
if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR2, lcName)) ;
|
|
875
|
+
else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR2, lcName)) ;
|
|
876
|
+
else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
|
|
877
|
+
if (
|
|
878
|
+
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
879
|
+
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
880
|
+
// and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
|
|
881
|
+
_isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || // Alternative, second condition checks if it's an `is`-attribute, AND
|
|
882
|
+
// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
883
|
+
lcName === "is" && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))
|
|
884
|
+
) ;
|
|
885
|
+
else {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
} else if (URI_SAFE_ATTRIBUTES[lcName]) ;
|
|
889
|
+
else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE2, ""))) ;
|
|
890
|
+
else if ((lcName === "src" || lcName === "xlink:href" || lcName === "href") && lcTag !== "script" && stringIndexOf(value, "data:") === 0 && DATA_URI_TAGS[lcTag]) ;
|
|
891
|
+
else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA2, stringReplace(value, ATTR_WHITESPACE2, ""))) ;
|
|
892
|
+
else if (value) {
|
|
893
|
+
return false;
|
|
894
|
+
} else ;
|
|
895
|
+
return true;
|
|
896
|
+
};
|
|
897
|
+
const _isBasicCustomElement = function _isBasicCustomElement2(tagName) {
|
|
898
|
+
return tagName !== "annotation-xml" && stringMatch(tagName, CUSTOM_ELEMENT2);
|
|
899
|
+
};
|
|
900
|
+
const _sanitizeAttributes = function _sanitizeAttributes2(currentNode) {
|
|
901
|
+
_executeHook("beforeSanitizeAttributes", currentNode, null);
|
|
902
|
+
const {
|
|
903
|
+
attributes
|
|
904
|
+
} = currentNode;
|
|
905
|
+
if (!attributes) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const hookEvent = {
|
|
909
|
+
attrName: "",
|
|
910
|
+
attrValue: "",
|
|
911
|
+
keepAttr: true,
|
|
912
|
+
allowedAttributes: ALLOWED_ATTR,
|
|
913
|
+
forceKeepAttr: void 0
|
|
914
|
+
};
|
|
915
|
+
let l = attributes.length;
|
|
916
|
+
while (l--) {
|
|
917
|
+
const attr = attributes[l];
|
|
918
|
+
const {
|
|
919
|
+
name,
|
|
920
|
+
namespaceURI,
|
|
921
|
+
value: attrValue
|
|
922
|
+
} = attr;
|
|
923
|
+
const lcName = transformCaseFunc(name);
|
|
924
|
+
let value = name === "value" ? attrValue : stringTrim(attrValue);
|
|
925
|
+
hookEvent.attrName = lcName;
|
|
926
|
+
hookEvent.attrValue = value;
|
|
927
|
+
hookEvent.keepAttr = true;
|
|
928
|
+
hookEvent.forceKeepAttr = void 0;
|
|
929
|
+
_executeHook("uponSanitizeAttribute", currentNode, hookEvent);
|
|
930
|
+
value = hookEvent.attrValue;
|
|
931
|
+
if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name")) {
|
|
932
|
+
_removeAttribute(name, currentNode);
|
|
933
|
+
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
934
|
+
}
|
|
935
|
+
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
|
|
936
|
+
_removeAttribute(name, currentNode);
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (hookEvent.forceKeepAttr) {
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
_removeAttribute(name, currentNode);
|
|
943
|
+
if (!hookEvent.keepAttr) {
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
|
|
947
|
+
_removeAttribute(name, currentNode);
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
951
|
+
arrayForEach([MUSTACHE_EXPR2, ERB_EXPR2, TMPLIT_EXPR2], (expr) => {
|
|
952
|
+
value = stringReplace(value, expr, " ");
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
956
|
+
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (trustedTypesPolicy && typeof trustedTypes === "object" && typeof trustedTypes.getAttributeType === "function") {
|
|
960
|
+
if (namespaceURI) ;
|
|
961
|
+
else {
|
|
962
|
+
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
963
|
+
case "TrustedHTML": {
|
|
964
|
+
value = trustedTypesPolicy.createHTML(value);
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
967
|
+
case "TrustedScriptURL": {
|
|
968
|
+
value = trustedTypesPolicy.createScriptURL(value);
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
try {
|
|
975
|
+
if (namespaceURI) {
|
|
976
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
977
|
+
} else {
|
|
978
|
+
currentNode.setAttribute(name, value);
|
|
979
|
+
}
|
|
980
|
+
if (_isClobbered(currentNode)) {
|
|
981
|
+
_forceRemove(currentNode);
|
|
982
|
+
} else {
|
|
983
|
+
arrayPop(DOMPurify.removed);
|
|
984
|
+
}
|
|
985
|
+
} catch (_) {
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
_executeHook("afterSanitizeAttributes", currentNode, null);
|
|
989
|
+
};
|
|
990
|
+
const _sanitizeShadowDOM = function _sanitizeShadowDOM2(fragment) {
|
|
991
|
+
let shadowNode = null;
|
|
992
|
+
const shadowIterator = _createNodeIterator(fragment);
|
|
993
|
+
_executeHook("beforeSanitizeShadowDOM", fragment, null);
|
|
994
|
+
while (shadowNode = shadowIterator.nextNode()) {
|
|
995
|
+
_executeHook("uponSanitizeShadowNode", shadowNode, null);
|
|
996
|
+
if (_sanitizeElements(shadowNode)) {
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (shadowNode.content instanceof DocumentFragment) {
|
|
1000
|
+
_sanitizeShadowDOM2(shadowNode.content);
|
|
1001
|
+
}
|
|
1002
|
+
_sanitizeAttributes(shadowNode);
|
|
1003
|
+
}
|
|
1004
|
+
_executeHook("afterSanitizeShadowDOM", fragment, null);
|
|
1005
|
+
};
|
|
1006
|
+
DOMPurify.sanitize = function(dirty) {
|
|
1007
|
+
let cfg = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
|
|
1008
|
+
let body = null;
|
|
1009
|
+
let importedNode = null;
|
|
1010
|
+
let currentNode = null;
|
|
1011
|
+
let returnNode = null;
|
|
1012
|
+
IS_EMPTY_INPUT = !dirty;
|
|
1013
|
+
if (IS_EMPTY_INPUT) {
|
|
1014
|
+
dirty = "<!-->";
|
|
1015
|
+
}
|
|
1016
|
+
if (typeof dirty !== "string" && !_isNode(dirty)) {
|
|
1017
|
+
if (typeof dirty.toString === "function") {
|
|
1018
|
+
dirty = dirty.toString();
|
|
1019
|
+
if (typeof dirty !== "string") {
|
|
1020
|
+
throw typeErrorCreate("dirty is not a string, aborting");
|
|
1021
|
+
}
|
|
1022
|
+
} else {
|
|
1023
|
+
throw typeErrorCreate("toString is not a function");
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (!DOMPurify.isSupported) {
|
|
1027
|
+
return dirty;
|
|
1028
|
+
}
|
|
1029
|
+
if (!SET_CONFIG) {
|
|
1030
|
+
_parseConfig(cfg);
|
|
1031
|
+
}
|
|
1032
|
+
DOMPurify.removed = [];
|
|
1033
|
+
if (typeof dirty === "string") {
|
|
1034
|
+
IN_PLACE = false;
|
|
1035
|
+
}
|
|
1036
|
+
if (IN_PLACE) {
|
|
1037
|
+
if (dirty.nodeName) {
|
|
1038
|
+
const tagName = transformCaseFunc(dirty.nodeName);
|
|
1039
|
+
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
1040
|
+
throw typeErrorCreate("root node is forbidden and cannot be sanitized in-place");
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
} else if (dirty instanceof Node) {
|
|
1044
|
+
body = _initDocument("<!---->");
|
|
1045
|
+
importedNode = body.ownerDocument.importNode(dirty, true);
|
|
1046
|
+
if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === "BODY") {
|
|
1047
|
+
body = importedNode;
|
|
1048
|
+
} else if (importedNode.nodeName === "HTML") {
|
|
1049
|
+
body = importedNode;
|
|
1050
|
+
} else {
|
|
1051
|
+
body.appendChild(importedNode);
|
|
1052
|
+
}
|
|
1053
|
+
} else {
|
|
1054
|
+
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && // eslint-disable-next-line unicorn/prefer-includes
|
|
1055
|
+
dirty.indexOf("<") === -1) {
|
|
1056
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
1057
|
+
}
|
|
1058
|
+
body = _initDocument(dirty);
|
|
1059
|
+
if (!body) {
|
|
1060
|
+
return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : "";
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (body && FORCE_BODY) {
|
|
1064
|
+
_forceRemove(body.firstChild);
|
|
1065
|
+
}
|
|
1066
|
+
const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
|
|
1067
|
+
while (currentNode = nodeIterator.nextNode()) {
|
|
1068
|
+
if (_sanitizeElements(currentNode)) {
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
if (currentNode.content instanceof DocumentFragment) {
|
|
1072
|
+
_sanitizeShadowDOM(currentNode.content);
|
|
1073
|
+
}
|
|
1074
|
+
_sanitizeAttributes(currentNode);
|
|
1075
|
+
}
|
|
1076
|
+
if (IN_PLACE) {
|
|
1077
|
+
return dirty;
|
|
1078
|
+
}
|
|
1079
|
+
if (RETURN_DOM) {
|
|
1080
|
+
if (RETURN_DOM_FRAGMENT) {
|
|
1081
|
+
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
1082
|
+
while (body.firstChild) {
|
|
1083
|
+
returnNode.appendChild(body.firstChild);
|
|
1084
|
+
}
|
|
1085
|
+
} else {
|
|
1086
|
+
returnNode = body;
|
|
1087
|
+
}
|
|
1088
|
+
if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
|
|
1089
|
+
returnNode = importNode.call(originalDocument, returnNode, true);
|
|
1090
|
+
}
|
|
1091
|
+
return returnNode;
|
|
1092
|
+
}
|
|
1093
|
+
let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
|
|
1094
|
+
if (WHOLE_DOCUMENT && ALLOWED_TAGS["!doctype"] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
|
|
1095
|
+
serializedHTML = "<!DOCTYPE " + body.ownerDocument.doctype.name + ">\n" + serializedHTML;
|
|
1096
|
+
}
|
|
1097
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
1098
|
+
arrayForEach([MUSTACHE_EXPR2, ERB_EXPR2, TMPLIT_EXPR2], (expr) => {
|
|
1099
|
+
serializedHTML = stringReplace(serializedHTML, expr, " ");
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
|
|
1103
|
+
};
|
|
1104
|
+
DOMPurify.setConfig = function() {
|
|
1105
|
+
let cfg = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
|
|
1106
|
+
_parseConfig(cfg);
|
|
1107
|
+
SET_CONFIG = true;
|
|
1108
|
+
};
|
|
1109
|
+
DOMPurify.clearConfig = function() {
|
|
1110
|
+
CONFIG = null;
|
|
1111
|
+
SET_CONFIG = false;
|
|
1112
|
+
};
|
|
1113
|
+
DOMPurify.isValidAttribute = function(tag2, attr, value) {
|
|
1114
|
+
if (!CONFIG) {
|
|
1115
|
+
_parseConfig({});
|
|
1116
|
+
}
|
|
1117
|
+
const lcTag = transformCaseFunc(tag2);
|
|
1118
|
+
const lcName = transformCaseFunc(attr);
|
|
1119
|
+
return _isValidAttribute(lcTag, lcName, value);
|
|
1120
|
+
};
|
|
1121
|
+
DOMPurify.addHook = function(entryPoint, hookFunction) {
|
|
1122
|
+
if (typeof hookFunction !== "function") {
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
hooks[entryPoint] = hooks[entryPoint] || [];
|
|
1126
|
+
arrayPush(hooks[entryPoint], hookFunction);
|
|
1127
|
+
};
|
|
1128
|
+
DOMPurify.removeHook = function(entryPoint) {
|
|
1129
|
+
if (hooks[entryPoint]) {
|
|
1130
|
+
return arrayPop(hooks[entryPoint]);
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
DOMPurify.removeHooks = function(entryPoint) {
|
|
1134
|
+
if (hooks[entryPoint]) {
|
|
1135
|
+
hooks[entryPoint] = [];
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
DOMPurify.removeAllHooks = function() {
|
|
1139
|
+
hooks = {};
|
|
1140
|
+
};
|
|
1141
|
+
return DOMPurify;
|
|
1142
|
+
}
|
|
1143
|
+
var purify = createDOMPurify();
|
|
1144
|
+
|
|
1145
|
+
// src/util/sanitize.ts
|
|
1146
|
+
var PURIFY_CONFIG = {
|
|
1147
|
+
USE_PROFILES: { html: true, svg: true, svgFilters: true },
|
|
1148
|
+
// Allow <input type="checkbox" disabled> for GFM task-list rendering and
|
|
1149
|
+
// the `task-list-item` / `contains-task-list` classes marked emits.
|
|
1150
|
+
ADD_TAGS: ["input"],
|
|
1151
|
+
ADD_ATTR: ["data-spec", "data-status", "type", "checked", "disabled", "class"],
|
|
1152
|
+
ALLOW_DATA_ATTR: false,
|
|
1153
|
+
KEEP_CONTENT: true
|
|
1154
|
+
};
|
|
1155
|
+
function sanitizeHtml(html3) {
|
|
1156
|
+
return purify.sanitize(html3, PURIFY_CONFIG);
|
|
1157
|
+
}
|
|
1158
|
+
function escapeHtml(s) {
|
|
1159
|
+
return s.replace(/[&<>"']/g, (c) => ({
|
|
1160
|
+
"&": "&",
|
|
1161
|
+
"<": "<",
|
|
1162
|
+
">": ">",
|
|
1163
|
+
'"': """,
|
|
1164
|
+
"'": "'"
|
|
1165
|
+
})[c] ?? c);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/components/validation-banner.ts
|
|
1169
|
+
function renderValidationBanner(errors) {
|
|
1170
|
+
if (errors.length === 0) return null;
|
|
1171
|
+
const banner = document.createElement("details");
|
|
1172
|
+
banner.className = "zg-validation-banner";
|
|
1173
|
+
banner.open = errors.length <= 3;
|
|
1174
|
+
const summary = document.createElement("summary");
|
|
1175
|
+
summary.className = "zg-validation-summary";
|
|
1176
|
+
const icon = document.createElement("span");
|
|
1177
|
+
icon.className = "zg-validation-icon";
|
|
1178
|
+
icon.textContent = "\u26A0";
|
|
1179
|
+
summary.appendChild(icon);
|
|
1180
|
+
const label = document.createElement("span");
|
|
1181
|
+
label.textContent = errors.length === 1 ? "1 spec validation error" : `${errors.length} spec validation errors`;
|
|
1182
|
+
summary.appendChild(label);
|
|
1183
|
+
banner.appendChild(summary);
|
|
1184
|
+
const list2 = document.createElement("ul");
|
|
1185
|
+
list2.className = "zg-validation-list";
|
|
1186
|
+
for (const err of errors) {
|
|
1187
|
+
const li = document.createElement("li");
|
|
1188
|
+
li.className = `zg-validation-item zg-validation-${err.code.toLowerCase()}`;
|
|
1189
|
+
li.innerHTML = `<code>${escapeHtml(err.code)}</code> <strong>${escapeHtml(formatPath(err.path))}</strong>` + (err.detail ? ` \u2014 ${escapeHtml(err.detail)}` : "");
|
|
1190
|
+
list2.appendChild(li);
|
|
1191
|
+
}
|
|
1192
|
+
banner.appendChild(list2);
|
|
1193
|
+
return banner;
|
|
1194
|
+
}
|
|
1195
|
+
function formatPath(path) {
|
|
1196
|
+
if (typeof path === "string") return path;
|
|
1197
|
+
return path.join(" \u2192 ");
|
|
1198
|
+
}
|
|
1199
|
+
async function fetchAndRenderValidationBanner() {
|
|
1200
|
+
try {
|
|
1201
|
+
const { errors } = await window.zettelgeistBackend.validateRepo();
|
|
1202
|
+
return renderValidationBanner(errors);
|
|
1203
|
+
} catch {
|
|
1204
|
+
return null;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/views/board.ts
|
|
1209
|
+
var COLUMN_ORDER = [
|
|
1210
|
+
"draft",
|
|
1211
|
+
"planned",
|
|
1212
|
+
"in-progress",
|
|
1213
|
+
"in-review",
|
|
1214
|
+
"done",
|
|
1215
|
+
"blocked",
|
|
1216
|
+
"cancelled"
|
|
1217
|
+
];
|
|
1218
|
+
var COLUMN_LABELS = {
|
|
1219
|
+
"draft": "Draft",
|
|
1220
|
+
"planned": "Planned",
|
|
1221
|
+
"in-progress": "In Progress",
|
|
1222
|
+
"in-review": "In Review",
|
|
1223
|
+
"done": "Done",
|
|
1224
|
+
"blocked": "Blocked",
|
|
1225
|
+
"cancelled": "Cancelled"
|
|
1226
|
+
};
|
|
1227
|
+
async function renderBoard() {
|
|
1228
|
+
const app = document.getElementById("app");
|
|
1229
|
+
app.innerHTML = "<p>Loading specs\u2026</p>";
|
|
1230
|
+
const backend = window.zettelgeistBackend;
|
|
1231
|
+
let specs;
|
|
1232
|
+
try {
|
|
1233
|
+
specs = await backend.listSpecs();
|
|
1234
|
+
} catch (err) {
|
|
1235
|
+
app.innerHTML = `<p class="zg-error">Failed to load specs: ${escapeHtml(err.message)}</p>`;
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
app.innerHTML = "";
|
|
1239
|
+
const banner = await fetchAndRenderValidationBanner();
|
|
1240
|
+
if (banner) app.appendChild(banner);
|
|
1241
|
+
if (specs.length === 0) {
|
|
1242
|
+
app.appendChild(renderEmptyState());
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
const byStatus = {
|
|
1246
|
+
"draft": [],
|
|
1247
|
+
"planned": [],
|
|
1248
|
+
"in-progress": [],
|
|
1249
|
+
"in-review": [],
|
|
1250
|
+
"done": [],
|
|
1251
|
+
"blocked": [],
|
|
1252
|
+
"cancelled": []
|
|
1253
|
+
};
|
|
1254
|
+
for (const s of specs) byStatus[s.status].push(s);
|
|
1255
|
+
const board = document.createElement("div");
|
|
1256
|
+
board.className = "zg-board";
|
|
1257
|
+
for (const status of COLUMN_ORDER) {
|
|
1258
|
+
const column = document.createElement("section");
|
|
1259
|
+
column.className = "zg-column";
|
|
1260
|
+
column.dataset.status = status;
|
|
1261
|
+
const header = document.createElement("header");
|
|
1262
|
+
header.className = "zg-column-header";
|
|
1263
|
+
const title = document.createElement("h3");
|
|
1264
|
+
title.textContent = COLUMN_LABELS[status];
|
|
1265
|
+
const count = document.createElement("span");
|
|
1266
|
+
count.className = "zg-column-count";
|
|
1267
|
+
count.textContent = String(byStatus[status].length);
|
|
1268
|
+
const addBtn = document.createElement("button");
|
|
1269
|
+
addBtn.type = "button";
|
|
1270
|
+
addBtn.className = "zg-column-add";
|
|
1271
|
+
addBtn.title = `New spec in ${COLUMN_LABELS[status]}`;
|
|
1272
|
+
addBtn.setAttribute("aria-label", `New spec in ${COLUMN_LABELS[status]}`);
|
|
1273
|
+
addBtn.textContent = "+";
|
|
1274
|
+
addBtn.addEventListener("click", () => void createSpecInColumn(status, specs));
|
|
1275
|
+
header.appendChild(title);
|
|
1276
|
+
header.appendChild(count);
|
|
1277
|
+
header.appendChild(addBtn);
|
|
1278
|
+
const cards = document.createElement("div");
|
|
1279
|
+
cards.className = "zg-column-cards";
|
|
1280
|
+
for (const spec of byStatus[status]) {
|
|
1281
|
+
cards.appendChild(renderCard(spec));
|
|
1282
|
+
}
|
|
1283
|
+
column.appendChild(header);
|
|
1284
|
+
column.appendChild(cards);
|
|
1285
|
+
attachDropHandlers(column, status);
|
|
1286
|
+
board.appendChild(column);
|
|
1287
|
+
}
|
|
1288
|
+
app.appendChild(board);
|
|
1289
|
+
}
|
|
1290
|
+
function templateFor(name, status, blockedBy) {
|
|
1291
|
+
const fmLines = ["---"];
|
|
1292
|
+
fmLines.push(`status: ${status}`);
|
|
1293
|
+
if (status === "blocked" && blockedBy) fmLines.push(`blocked_by: ${JSON.stringify(blockedBy)}`);
|
|
1294
|
+
fmLines.push("depends_on: []");
|
|
1295
|
+
fmLines.push("---");
|
|
1296
|
+
const requirements = `${fmLines.join("\n")}
|
|
1297
|
+
# ${name}
|
|
1298
|
+
|
|
1299
|
+
## Why
|
|
1300
|
+
|
|
1301
|
+
<!-- Why does this spec exist? -->
|
|
1302
|
+
|
|
1303
|
+
## Acceptance criteria
|
|
1304
|
+
|
|
1305
|
+
- [ ] WHEN <trigger>
|
|
1306
|
+
- [ ] THE SYSTEM SHALL <observable behavior>
|
|
1307
|
+
|
|
1308
|
+
## Out of scope
|
|
1309
|
+
|
|
1310
|
+
-
|
|
1311
|
+
|
|
1312
|
+
## References
|
|
1313
|
+
|
|
1314
|
+
-
|
|
1315
|
+
`;
|
|
1316
|
+
const tasks = `- [ ] 1.
|
|
1317
|
+
`;
|
|
1318
|
+
return { requirements, tasks };
|
|
1319
|
+
}
|
|
1320
|
+
async function createSpecInColumn(status, existing) {
|
|
1321
|
+
const raw = await showInputModal({
|
|
1322
|
+
title: `New spec in "${status}"`,
|
|
1323
|
+
message: 'Lowercase letters, numbers, and dashes. Example: "user-auth".',
|
|
1324
|
+
placeholder: "my-new-spec",
|
|
1325
|
+
confirmLabel: "Create",
|
|
1326
|
+
validate: (v) => {
|
|
1327
|
+
const t = v.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1328
|
+
if (!t) return "Name is required.";
|
|
1329
|
+
if (existing.some((s) => s.name === t)) return `A spec named "${t}" already exists.`;
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
if (raw === null) return;
|
|
1334
|
+
const name = raw.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1335
|
+
if (!name) return;
|
|
1336
|
+
let blockedBy;
|
|
1337
|
+
if (status === "blocked") {
|
|
1338
|
+
const reason = await showInputModal({
|
|
1339
|
+
title: `Why is "${name}" blocked?`,
|
|
1340
|
+
message: "A reason is required for blocked specs.",
|
|
1341
|
+
confirmLabel: "Create",
|
|
1342
|
+
validate: (v) => v.trim() ? null : "Reason is required."
|
|
1343
|
+
});
|
|
1344
|
+
if (reason === null) return;
|
|
1345
|
+
blockedBy = reason.trim();
|
|
1346
|
+
}
|
|
1347
|
+
const { requirements, tasks } = templateFor(name, status, blockedBy);
|
|
1348
|
+
try {
|
|
1349
|
+
const backend = window.zettelgeistBackend;
|
|
1350
|
+
await backend.writeSpecFile(name, "requirements.md", requirements);
|
|
1351
|
+
await backend.writeSpecFile(name, "tasks.md", tasks);
|
|
1352
|
+
window.location.hash = `#/spec/${encodeURIComponent(name)}`;
|
|
1353
|
+
} catch (err) {
|
|
1354
|
+
void showAlert("Error", err.message);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
function renderEmptyState() {
|
|
1358
|
+
const wrap = document.createElement("div");
|
|
1359
|
+
wrap.className = "zg-empty-state";
|
|
1360
|
+
const title = document.createElement("h2");
|
|
1361
|
+
title.textContent = "No specs yet";
|
|
1362
|
+
wrap.appendChild(title);
|
|
1363
|
+
const blurb = document.createElement("p");
|
|
1364
|
+
blurb.textContent = "A spec is a folder under specs/ with at least one of requirements.md, tasks.md, handoff.md, or lenses/*.md. Create one by hand or ask your agent to.";
|
|
1365
|
+
wrap.appendChild(blurb);
|
|
1366
|
+
const example = document.createElement("pre");
|
|
1367
|
+
example.className = "zg-empty-example";
|
|
1368
|
+
example.textContent = 'specs/my-first-spec/\n requirements.md # markdown body (optionally with --- frontmatter ---)\n tasks.md # "- [ ] 1. task text" lines\n\n# then:\nzettelgeist regen # rebuilds specs/INDEX.md\n';
|
|
1369
|
+
wrap.appendChild(example);
|
|
1370
|
+
const hint = document.createElement("p");
|
|
1371
|
+
hint.className = "zg-empty-hint";
|
|
1372
|
+
hint.innerHTML = "Refresh this page once the spec is on disk \u2014 the board will pick it up.";
|
|
1373
|
+
wrap.appendChild(hint);
|
|
1374
|
+
return wrap;
|
|
1375
|
+
}
|
|
1376
|
+
function attachDropHandlers(column, status) {
|
|
1377
|
+
column.addEventListener("dragover", (e) => {
|
|
1378
|
+
e.preventDefault();
|
|
1379
|
+
column.classList.add("zg-column-drop-target");
|
|
1380
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
|
1381
|
+
});
|
|
1382
|
+
column.addEventListener("dragleave", () => {
|
|
1383
|
+
column.classList.remove("zg-column-drop-target");
|
|
1384
|
+
});
|
|
1385
|
+
column.addEventListener("drop", async (e) => {
|
|
1386
|
+
e.preventDefault();
|
|
1387
|
+
column.classList.remove("zg-column-drop-target");
|
|
1388
|
+
const specName = e.dataTransfer?.getData("text/plain");
|
|
1389
|
+
if (!specName) return;
|
|
1390
|
+
const sourceStatus = e.dataTransfer?.getData("application/x-zg-status") || "";
|
|
1391
|
+
if (sourceStatus === status) return;
|
|
1392
|
+
let reason;
|
|
1393
|
+
if (status === "blocked" || status === "cancelled") {
|
|
1394
|
+
const { showReasonModal } = await import("./reason-modal-MK34MQ73.js");
|
|
1395
|
+
reason = await showReasonModal({
|
|
1396
|
+
title: status === "blocked" ? "Mark as Blocked" : "Mark as Cancelled",
|
|
1397
|
+
message: `Mark "${specName}" as ${status}.`,
|
|
1398
|
+
reasonRequired: status === "blocked",
|
|
1399
|
+
reasonLabel: status === "blocked" ? "What's blocking it?" : "Reason (optional):",
|
|
1400
|
+
confirmLabel: status === "blocked" ? "Mark Blocked" : "Mark Cancelled"
|
|
1401
|
+
});
|
|
1402
|
+
if (reason === null) return;
|
|
1403
|
+
}
|
|
1404
|
+
try {
|
|
1405
|
+
await window.zettelgeistBackend.setStatus(specName, status, reason || void 0);
|
|
1406
|
+
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
1407
|
+
} catch (err) {
|
|
1408
|
+
void showAlert("Error", err.message);
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/components/tabs.ts
|
|
1414
|
+
function renderTabs(tabs, opts = {}) {
|
|
1415
|
+
const wrapper = document.createElement("div");
|
|
1416
|
+
wrapper.className = "zg-tabs";
|
|
1417
|
+
const nav = document.createElement("nav");
|
|
1418
|
+
nav.className = "zg-tab-nav";
|
|
1419
|
+
const content = document.createElement("div");
|
|
1420
|
+
content.className = "zg-tab-content";
|
|
1421
|
+
function activate(id) {
|
|
1422
|
+
const tab = tabs.find((t) => t.id === id);
|
|
1423
|
+
if (!tab) return;
|
|
1424
|
+
nav.querySelectorAll("button").forEach((b) => {
|
|
1425
|
+
b.classList.toggle("active", b.dataset.tab === id);
|
|
1426
|
+
});
|
|
1427
|
+
content.innerHTML = "";
|
|
1428
|
+
content.appendChild(tab.render());
|
|
1429
|
+
opts.onActivate?.(id);
|
|
1430
|
+
}
|
|
1431
|
+
for (const tab of tabs) {
|
|
1432
|
+
const btn = document.createElement("button");
|
|
1433
|
+
btn.type = "button";
|
|
1434
|
+
btn.dataset.tab = tab.id;
|
|
1435
|
+
btn.textContent = tab.label;
|
|
1436
|
+
btn.addEventListener("click", () => activate(tab.id));
|
|
1437
|
+
nav.appendChild(btn);
|
|
1438
|
+
}
|
|
1439
|
+
wrapper.appendChild(nav);
|
|
1440
|
+
wrapper.appendChild(content);
|
|
1441
|
+
if (tabs.length > 0) {
|
|
1442
|
+
const initial = opts.initialTabId && tabs.some((t) => t.id === opts.initialTabId) ? opts.initialTabId : tabs[0].id;
|
|
1443
|
+
activate(initial);
|
|
1444
|
+
}
|
|
1445
|
+
return wrapper;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// src/components/task-list.ts
|
|
1449
|
+
function renderTaskList(specName, tasks) {
|
|
1450
|
+
const wrap = document.createElement("div");
|
|
1451
|
+
wrap.className = "zg-tasks";
|
|
1452
|
+
const list2 = document.createElement("ul");
|
|
1453
|
+
list2.className = "zg-task-list";
|
|
1454
|
+
if (tasks.length === 0) {
|
|
1455
|
+
const empty = document.createElement("li");
|
|
1456
|
+
const em = document.createElement("em");
|
|
1457
|
+
em.textContent = "No tasks yet \u2014 add the first one below.";
|
|
1458
|
+
empty.appendChild(em);
|
|
1459
|
+
list2.appendChild(empty);
|
|
1460
|
+
} else {
|
|
1461
|
+
for (const task of tasks) list2.appendChild(renderTaskItem(specName, task));
|
|
1462
|
+
}
|
|
1463
|
+
wrap.appendChild(list2);
|
|
1464
|
+
wrap.appendChild(renderAddForm(specName));
|
|
1465
|
+
return wrap;
|
|
1466
|
+
}
|
|
1467
|
+
function renderTaskItem(specName, task) {
|
|
1468
|
+
const item = document.createElement("li");
|
|
1469
|
+
item.className = "zg-task";
|
|
1470
|
+
item.dataset.index = String(task.index);
|
|
1471
|
+
const checkbox = document.createElement("input");
|
|
1472
|
+
checkbox.type = "checkbox";
|
|
1473
|
+
checkbox.checked = task.checked;
|
|
1474
|
+
checkbox.addEventListener("change", async () => {
|
|
1475
|
+
const backend = window.zettelgeistBackend;
|
|
1476
|
+
try {
|
|
1477
|
+
if (checkbox.checked) await backend.tickTask(specName, task.index);
|
|
1478
|
+
else await backend.untickTask(specName, task.index);
|
|
1479
|
+
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
1480
|
+
} catch (err) {
|
|
1481
|
+
checkbox.checked = !checkbox.checked;
|
|
1482
|
+
void showAlert("Error", err.message);
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
const label = document.createElement("label");
|
|
1486
|
+
label.className = "zg-task-label";
|
|
1487
|
+
label.textContent = ` ${task.text}`;
|
|
1488
|
+
label.prepend(checkbox);
|
|
1489
|
+
item.appendChild(label);
|
|
1490
|
+
if (task.tags.length > 0) {
|
|
1491
|
+
const tags = document.createElement("span");
|
|
1492
|
+
tags.className = "zg-task-tags";
|
|
1493
|
+
for (const tag2 of task.tags) {
|
|
1494
|
+
const badge = document.createElement("small");
|
|
1495
|
+
badge.className = "zg-tag";
|
|
1496
|
+
badge.textContent = tag2;
|
|
1497
|
+
tags.appendChild(badge);
|
|
1498
|
+
}
|
|
1499
|
+
item.appendChild(tags);
|
|
1500
|
+
}
|
|
1501
|
+
const actions = document.createElement("span");
|
|
1502
|
+
actions.className = "zg-task-actions";
|
|
1503
|
+
const editBtn = document.createElement("button");
|
|
1504
|
+
editBtn.type = "button";
|
|
1505
|
+
editBtn.className = "zg-task-edit";
|
|
1506
|
+
editBtn.title = "Edit task text";
|
|
1507
|
+
editBtn.setAttribute("aria-label", `Edit task ${task.index}`);
|
|
1508
|
+
editBtn.textContent = "\u270E";
|
|
1509
|
+
editBtn.addEventListener("click", () => startInlineEdit(specName, task, item));
|
|
1510
|
+
const delBtn = document.createElement("button");
|
|
1511
|
+
delBtn.type = "button";
|
|
1512
|
+
delBtn.className = "zg-task-delete";
|
|
1513
|
+
delBtn.title = "Delete task";
|
|
1514
|
+
delBtn.setAttribute("aria-label", `Delete task ${task.index}`);
|
|
1515
|
+
delBtn.textContent = "\u{1F5D1}";
|
|
1516
|
+
delBtn.addEventListener("click", () => deleteTask(specName, task.index));
|
|
1517
|
+
actions.appendChild(editBtn);
|
|
1518
|
+
actions.appendChild(delBtn);
|
|
1519
|
+
item.appendChild(actions);
|
|
1520
|
+
return item;
|
|
1521
|
+
}
|
|
1522
|
+
function renderAddForm(specName) {
|
|
1523
|
+
const form = document.createElement("form");
|
|
1524
|
+
form.className = "zg-task-add";
|
|
1525
|
+
form.addEventListener("submit", (e) => e.preventDefault());
|
|
1526
|
+
const input = document.createElement("input");
|
|
1527
|
+
input.type = "text";
|
|
1528
|
+
input.className = "zg-task-add-input";
|
|
1529
|
+
input.placeholder = "New task\u2026 (use #human-only / #agent-only / #skip for tags)";
|
|
1530
|
+
input.setAttribute("aria-label", "New task text");
|
|
1531
|
+
const btn = document.createElement("button");
|
|
1532
|
+
btn.type = "submit";
|
|
1533
|
+
btn.className = "zg-task-add-btn";
|
|
1534
|
+
btn.textContent = "Add";
|
|
1535
|
+
const submit = async () => {
|
|
1536
|
+
const text2 = input.value.trim();
|
|
1537
|
+
if (!text2) return;
|
|
1538
|
+
btn.disabled = true;
|
|
1539
|
+
try {
|
|
1540
|
+
await appendTask(specName, text2);
|
|
1541
|
+
input.value = "";
|
|
1542
|
+
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
1543
|
+
} catch (err) {
|
|
1544
|
+
void showAlert("Error", err.message);
|
|
1545
|
+
} finally {
|
|
1546
|
+
btn.disabled = false;
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
btn.addEventListener("click", submit);
|
|
1550
|
+
input.addEventListener("keydown", (e) => {
|
|
1551
|
+
if (e.key === "Enter") {
|
|
1552
|
+
e.preventDefault();
|
|
1553
|
+
void submit();
|
|
1554
|
+
}
|
|
1555
|
+
});
|
|
1556
|
+
form.appendChild(input);
|
|
1557
|
+
form.appendChild(btn);
|
|
1558
|
+
return form;
|
|
1559
|
+
}
|
|
1560
|
+
function startInlineEdit(specName, task, item) {
|
|
1561
|
+
const label = item.querySelector(".zg-task-label");
|
|
1562
|
+
if (!label) return;
|
|
1563
|
+
const tagsSuffix = task.tags.length > 0 ? " " + task.tags.join(" ") : "";
|
|
1564
|
+
const original = task.text + tagsSuffix;
|
|
1565
|
+
const input = document.createElement("input");
|
|
1566
|
+
input.type = "text";
|
|
1567
|
+
input.className = "zg-task-edit-input";
|
|
1568
|
+
input.value = original;
|
|
1569
|
+
input.placeholder = "task text #human-only / #agent-only / #skip";
|
|
1570
|
+
label.style.display = "none";
|
|
1571
|
+
const actions = item.querySelector(".zg-task-actions");
|
|
1572
|
+
if (actions) actions.style.display = "none";
|
|
1573
|
+
const tagsEl = item.querySelector(".zg-task-tags");
|
|
1574
|
+
if (tagsEl) tagsEl.style.display = "none";
|
|
1575
|
+
item.insertBefore(input, label);
|
|
1576
|
+
input.focus();
|
|
1577
|
+
input.select();
|
|
1578
|
+
const finish = async (commit) => {
|
|
1579
|
+
const next = input.value.trim();
|
|
1580
|
+
input.remove();
|
|
1581
|
+
label.style.display = "";
|
|
1582
|
+
if (actions) actions.style.display = "";
|
|
1583
|
+
if (tagsEl) tagsEl.style.display = "";
|
|
1584
|
+
if (!commit || !next || next === original) return;
|
|
1585
|
+
try {
|
|
1586
|
+
await replaceTask(specName, task.index, next);
|
|
1587
|
+
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
1588
|
+
} catch (err) {
|
|
1589
|
+
void showAlert("Error", err.message);
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
input.addEventListener("keydown", (e) => {
|
|
1593
|
+
if (e.key === "Enter") {
|
|
1594
|
+
e.preventDefault();
|
|
1595
|
+
void finish(true);
|
|
1596
|
+
} else if (e.key === "Escape") {
|
|
1597
|
+
e.preventDefault();
|
|
1598
|
+
void finish(false);
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
input.addEventListener("blur", () => void finish(true));
|
|
1602
|
+
}
|
|
1603
|
+
async function readTasksFile(specName) {
|
|
1604
|
+
try {
|
|
1605
|
+
const { content } = await window.zettelgeistBackend.readSpecFile(specName, "tasks.md");
|
|
1606
|
+
return content;
|
|
1607
|
+
} catch {
|
|
1608
|
+
return "";
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
var TASK_LINE = /^([\s>]*[-*+]\s+\[)([ xX])(\]\s+)(.*)$/;
|
|
1612
|
+
async function appendTask(specName, text2) {
|
|
1613
|
+
const current = await readTasksFile(specName);
|
|
1614
|
+
const existing = current.split("\n").filter((l) => TASK_LINE.test(l)).length;
|
|
1615
|
+
const nextIndex = existing + 1;
|
|
1616
|
+
const newLine = `- [ ] ${nextIndex}. ${text2}`;
|
|
1617
|
+
const next = current.endsWith("\n") || current === "" ? `${current}${newLine}
|
|
1618
|
+
` : `${current}
|
|
1619
|
+
${newLine}
|
|
1620
|
+
`;
|
|
1621
|
+
await window.zettelgeistBackend.writeSpecFile(specName, "tasks.md", next);
|
|
1622
|
+
}
|
|
1623
|
+
async function replaceTask(specName, n, text2) {
|
|
1624
|
+
const current = await readTasksFile(specName);
|
|
1625
|
+
const lines = current.split("\n");
|
|
1626
|
+
let count = 0;
|
|
1627
|
+
let mutated = false;
|
|
1628
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1629
|
+
const m = lines[i]?.match(TASK_LINE);
|
|
1630
|
+
if (!m) continue;
|
|
1631
|
+
count++;
|
|
1632
|
+
if (count === n) {
|
|
1633
|
+
const body = m[4];
|
|
1634
|
+
const numPrefix = /^(\d+\.\s+)/.exec(body);
|
|
1635
|
+
const replacement = numPrefix ? numPrefix[1] + text2 : text2;
|
|
1636
|
+
lines[i] = `${m[1]}${m[2]}${m[3]}${replacement}`;
|
|
1637
|
+
mutated = true;
|
|
1638
|
+
break;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (!mutated) throw new Error(`no task at index ${n}`);
|
|
1642
|
+
await window.zettelgeistBackend.writeSpecFile(specName, "tasks.md", lines.join("\n"));
|
|
1643
|
+
}
|
|
1644
|
+
async function deleteTask(specName, n) {
|
|
1645
|
+
const ok = await showConfirmModal({
|
|
1646
|
+
title: `Delete task ${n}?`,
|
|
1647
|
+
message: "This removes the line and renumbers the remaining tasks.",
|
|
1648
|
+
confirmLabel: "Delete",
|
|
1649
|
+
destructive: true
|
|
1650
|
+
});
|
|
1651
|
+
if (!ok) return;
|
|
1652
|
+
try {
|
|
1653
|
+
const current = await readTasksFile(specName);
|
|
1654
|
+
const lines = current.split("\n");
|
|
1655
|
+
const kept = [];
|
|
1656
|
+
let count = 0;
|
|
1657
|
+
let renumber = 0;
|
|
1658
|
+
for (const line of lines) {
|
|
1659
|
+
const m = line.match(TASK_LINE);
|
|
1660
|
+
if (!m) {
|
|
1661
|
+
kept.push(line);
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
count++;
|
|
1665
|
+
if (count === n) continue;
|
|
1666
|
+
renumber++;
|
|
1667
|
+
const body = m[4];
|
|
1668
|
+
const replaced = body.replace(/^\d+\.\s+/, `${renumber}. `);
|
|
1669
|
+
kept.push(`${m[1]}${m[2]}${m[3]}${replaced}`);
|
|
1670
|
+
}
|
|
1671
|
+
await window.zettelgeistBackend.writeSpecFile(specName, "tasks.md", kept.join("\n"));
|
|
1672
|
+
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
void showAlert("Error", err.message);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// src/components/frontmatter-form.ts
|
|
1679
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["status", "blocked_by"]);
|
|
1680
|
+
var ARRAY_KEYS = /* @__PURE__ */ new Set(["depends_on"]);
|
|
1681
|
+
function renderFrontmatterForm(spec) {
|
|
1682
|
+
const container = document.createElement("details");
|
|
1683
|
+
container.className = "zg-frontmatter";
|
|
1684
|
+
container.open = false;
|
|
1685
|
+
const summary = document.createElement("summary");
|
|
1686
|
+
summary.textContent = "Frontmatter";
|
|
1687
|
+
container.appendChild(summary);
|
|
1688
|
+
const editable = Object.entries(spec.frontmatter).filter(([k]) => !FORBIDDEN_KEYS.has(k));
|
|
1689
|
+
if (editable.length === 0 && Object.keys(spec.frontmatter).length === 0) {
|
|
1690
|
+
const empty = document.createElement("p");
|
|
1691
|
+
empty.className = "zg-fm-empty";
|
|
1692
|
+
empty.innerHTML = "<em>No frontmatter set.</em> Use the field below to add one.";
|
|
1693
|
+
container.appendChild(empty);
|
|
1694
|
+
}
|
|
1695
|
+
const form = document.createElement("div");
|
|
1696
|
+
form.className = "zg-fm-form";
|
|
1697
|
+
form.addEventListener("keydown", (e) => {
|
|
1698
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
1699
|
+
e.preventDefault();
|
|
1700
|
+
void save();
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
const rows = [];
|
|
1704
|
+
for (const [key, value] of editable) rows.push(renderRow(form, key, value));
|
|
1705
|
+
const addBtn = document.createElement("button");
|
|
1706
|
+
addBtn.type = "button";
|
|
1707
|
+
addBtn.className = "zg-fm-add";
|
|
1708
|
+
addBtn.textContent = "+ Add field";
|
|
1709
|
+
addBtn.addEventListener("click", () => {
|
|
1710
|
+
const row = renderRow(form, "", "");
|
|
1711
|
+
rows.push(row);
|
|
1712
|
+
form.insertBefore(row.el, addBtn);
|
|
1713
|
+
row.focusKey();
|
|
1714
|
+
});
|
|
1715
|
+
const status = document.createElement("p");
|
|
1716
|
+
status.className = "zg-fm-status";
|
|
1717
|
+
status.style.display = "none";
|
|
1718
|
+
const saveBtn = document.createElement("button");
|
|
1719
|
+
saveBtn.type = "button";
|
|
1720
|
+
saveBtn.className = "zg-fm-save";
|
|
1721
|
+
saveBtn.textContent = "Save frontmatter";
|
|
1722
|
+
async function save() {
|
|
1723
|
+
saveBtn.disabled = true;
|
|
1724
|
+
try {
|
|
1725
|
+
const original = new Map(editable);
|
|
1726
|
+
const present = /* @__PURE__ */ new Map();
|
|
1727
|
+
for (const r of rows) {
|
|
1728
|
+
const k = r.key;
|
|
1729
|
+
if (!k) continue;
|
|
1730
|
+
present.set(k, r.getValue());
|
|
1731
|
+
}
|
|
1732
|
+
const patch = {};
|
|
1733
|
+
for (const [k, v] of present) patch[k] = v;
|
|
1734
|
+
for (const k of original.keys()) {
|
|
1735
|
+
if (!present.has(k)) patch[k] = null;
|
|
1736
|
+
}
|
|
1737
|
+
if (Object.keys(patch).length === 0) {
|
|
1738
|
+
flashStatus(status, "Nothing to save.", "info");
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
await window.zettelgeistBackend.patchFrontmatter(spec.name, patch);
|
|
1742
|
+
flashStatus(status, "Saved \u2713", "ok");
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
void showAlert("Save failed", err.message);
|
|
1745
|
+
} finally {
|
|
1746
|
+
saveBtn.disabled = false;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
saveBtn.addEventListener("click", () => void save());
|
|
1750
|
+
form.appendChild(addBtn);
|
|
1751
|
+
const actions = document.createElement("div");
|
|
1752
|
+
actions.className = "zg-fm-actions";
|
|
1753
|
+
actions.appendChild(status);
|
|
1754
|
+
actions.appendChild(saveBtn);
|
|
1755
|
+
container.appendChild(form);
|
|
1756
|
+
container.appendChild(actions);
|
|
1757
|
+
return container;
|
|
1758
|
+
}
|
|
1759
|
+
function flashStatus(el, text2, kind) {
|
|
1760
|
+
el.textContent = text2;
|
|
1761
|
+
el.dataset.kind = kind;
|
|
1762
|
+
el.style.display = "";
|
|
1763
|
+
el.style.opacity = "1";
|
|
1764
|
+
setTimeout(() => {
|
|
1765
|
+
el.style.transition = "opacity 0.4s";
|
|
1766
|
+
el.style.opacity = "0";
|
|
1767
|
+
}, 1800);
|
|
1768
|
+
}
|
|
1769
|
+
function renderRow(parent, initialKey, initialValue) {
|
|
1770
|
+
const row = document.createElement("div");
|
|
1771
|
+
row.className = "zg-fm-row";
|
|
1772
|
+
const keyInput = document.createElement("input");
|
|
1773
|
+
keyInput.type = "text";
|
|
1774
|
+
keyInput.className = "zg-fm-key";
|
|
1775
|
+
keyInput.value = initialKey;
|
|
1776
|
+
keyInput.placeholder = "field";
|
|
1777
|
+
const isExisting = initialKey !== "";
|
|
1778
|
+
if (isExisting) keyInput.readOnly = true;
|
|
1779
|
+
const valueEl = makeValueInput(initialKey, initialValue);
|
|
1780
|
+
const removeBtn = document.createElement("button");
|
|
1781
|
+
removeBtn.type = "button";
|
|
1782
|
+
removeBtn.className = "zg-fm-remove";
|
|
1783
|
+
removeBtn.title = "Remove field (will be deleted on save)";
|
|
1784
|
+
removeBtn.textContent = "\u{1F5D1}";
|
|
1785
|
+
removeBtn.addEventListener("click", () => {
|
|
1786
|
+
row.remove();
|
|
1787
|
+
});
|
|
1788
|
+
row.appendChild(keyInput);
|
|
1789
|
+
row.appendChild(valueEl.el);
|
|
1790
|
+
row.appendChild(removeBtn);
|
|
1791
|
+
parent.appendChild(row);
|
|
1792
|
+
return {
|
|
1793
|
+
get key() {
|
|
1794
|
+
return keyInput.value.trim();
|
|
1795
|
+
},
|
|
1796
|
+
el: row,
|
|
1797
|
+
getValue: valueEl.getValue,
|
|
1798
|
+
focusKey: () => keyInput.focus()
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
function makeValueInput(key, value) {
|
|
1802
|
+
if (ARRAY_KEYS.has(key) || Array.isArray(value) && value.every((v) => typeof v === "string")) {
|
|
1803
|
+
const input = document.createElement("input");
|
|
1804
|
+
input.type = "text";
|
|
1805
|
+
input.className = "zg-fm-value";
|
|
1806
|
+
input.placeholder = "comma-separated, e.g. user-auth, billing";
|
|
1807
|
+
input.value = Array.isArray(value) ? value.join(", ") : "";
|
|
1808
|
+
return {
|
|
1809
|
+
el: input,
|
|
1810
|
+
getValue: () => input.value.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
if (value === null || value === void 0 || typeof value === "string") {
|
|
1814
|
+
const input = document.createElement("input");
|
|
1815
|
+
input.type = "text";
|
|
1816
|
+
input.className = "zg-fm-value";
|
|
1817
|
+
input.value = typeof value === "string" ? value : "";
|
|
1818
|
+
return {
|
|
1819
|
+
el: input,
|
|
1820
|
+
getValue: () => {
|
|
1821
|
+
const t = input.value.trim();
|
|
1822
|
+
return t === "" ? null : t;
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
if (typeof value === "boolean") {
|
|
1827
|
+
const wrap = document.createElement("label");
|
|
1828
|
+
wrap.className = "zg-fm-value zg-fm-bool";
|
|
1829
|
+
const input = document.createElement("input");
|
|
1830
|
+
input.type = "checkbox";
|
|
1831
|
+
input.checked = value;
|
|
1832
|
+
wrap.appendChild(input);
|
|
1833
|
+
wrap.appendChild(document.createTextNode(value ? "true" : "false"));
|
|
1834
|
+
input.addEventListener("change", () => {
|
|
1835
|
+
wrap.lastChild.textContent = input.checked ? "true" : "false";
|
|
1836
|
+
});
|
|
1837
|
+
return { el: wrap, getValue: () => input.checked };
|
|
1838
|
+
}
|
|
1839
|
+
const ta = document.createElement("textarea");
|
|
1840
|
+
ta.className = "zg-fm-value zg-fm-json";
|
|
1841
|
+
ta.rows = 2;
|
|
1842
|
+
ta.value = JSON.stringify(value);
|
|
1843
|
+
return {
|
|
1844
|
+
el: ta,
|
|
1845
|
+
getValue: () => {
|
|
1846
|
+
try {
|
|
1847
|
+
return JSON.parse(ta.value);
|
|
1848
|
+
} catch {
|
|
1849
|
+
return ta.value;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// ../../node_modules/.pnpm/marked@14.1.4/node_modules/marked/lib/marked.esm.js
|
|
1856
|
+
function _getDefaults() {
|
|
1857
|
+
return {
|
|
1858
|
+
async: false,
|
|
1859
|
+
breaks: false,
|
|
1860
|
+
extensions: null,
|
|
1861
|
+
gfm: true,
|
|
1862
|
+
hooks: null,
|
|
1863
|
+
pedantic: false,
|
|
1864
|
+
renderer: null,
|
|
1865
|
+
silent: false,
|
|
1866
|
+
tokenizer: null,
|
|
1867
|
+
walkTokens: null
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
var _defaults = _getDefaults();
|
|
1871
|
+
function changeDefaults(newDefaults) {
|
|
1872
|
+
_defaults = newDefaults;
|
|
1873
|
+
}
|
|
1874
|
+
var escapeTest = /[&<>"']/;
|
|
1875
|
+
var escapeReplace = new RegExp(escapeTest.source, "g");
|
|
1876
|
+
var escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/;
|
|
1877
|
+
var escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, "g");
|
|
1878
|
+
var escapeReplacements = {
|
|
1879
|
+
"&": "&",
|
|
1880
|
+
"<": "<",
|
|
1881
|
+
">": ">",
|
|
1882
|
+
'"': """,
|
|
1883
|
+
"'": "'"
|
|
1884
|
+
};
|
|
1885
|
+
var getEscapeReplacement = (ch) => escapeReplacements[ch];
|
|
1886
|
+
function escape$1(html3, encode) {
|
|
1887
|
+
if (encode) {
|
|
1888
|
+
if (escapeTest.test(html3)) {
|
|
1889
|
+
return html3.replace(escapeReplace, getEscapeReplacement);
|
|
1890
|
+
}
|
|
1891
|
+
} else {
|
|
1892
|
+
if (escapeTestNoEncode.test(html3)) {
|
|
1893
|
+
return html3.replace(escapeReplaceNoEncode, getEscapeReplacement);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
return html3;
|
|
1897
|
+
}
|
|
1898
|
+
var caret = /(^|[^\[])\^/g;
|
|
1899
|
+
function edit(regex, opt) {
|
|
1900
|
+
let source = typeof regex === "string" ? regex : regex.source;
|
|
1901
|
+
opt = opt || "";
|
|
1902
|
+
const obj = {
|
|
1903
|
+
replace: (name, val) => {
|
|
1904
|
+
let valSource = typeof val === "string" ? val : val.source;
|
|
1905
|
+
valSource = valSource.replace(caret, "$1");
|
|
1906
|
+
source = source.replace(name, valSource);
|
|
1907
|
+
return obj;
|
|
1908
|
+
},
|
|
1909
|
+
getRegex: () => {
|
|
1910
|
+
return new RegExp(source, opt);
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
return obj;
|
|
1914
|
+
}
|
|
1915
|
+
function cleanUrl(href) {
|
|
1916
|
+
try {
|
|
1917
|
+
href = encodeURI(href).replace(/%25/g, "%");
|
|
1918
|
+
} catch {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
return href;
|
|
1922
|
+
}
|
|
1923
|
+
var noopTest = { exec: () => null };
|
|
1924
|
+
function splitCells(tableRow, count) {
|
|
1925
|
+
const row = tableRow.replace(/\|/g, (match, offset, str) => {
|
|
1926
|
+
let escaped = false;
|
|
1927
|
+
let curr = offset;
|
|
1928
|
+
while (--curr >= 0 && str[curr] === "\\")
|
|
1929
|
+
escaped = !escaped;
|
|
1930
|
+
if (escaped) {
|
|
1931
|
+
return "|";
|
|
1932
|
+
} else {
|
|
1933
|
+
return " |";
|
|
1934
|
+
}
|
|
1935
|
+
}), cells = row.split(/ \|/);
|
|
1936
|
+
let i = 0;
|
|
1937
|
+
if (!cells[0].trim()) {
|
|
1938
|
+
cells.shift();
|
|
1939
|
+
}
|
|
1940
|
+
if (cells.length > 0 && !cells[cells.length - 1].trim()) {
|
|
1941
|
+
cells.pop();
|
|
1942
|
+
}
|
|
1943
|
+
if (count) {
|
|
1944
|
+
if (cells.length > count) {
|
|
1945
|
+
cells.splice(count);
|
|
1946
|
+
} else {
|
|
1947
|
+
while (cells.length < count)
|
|
1948
|
+
cells.push("");
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
for (; i < cells.length; i++) {
|
|
1952
|
+
cells[i] = cells[i].trim().replace(/\\\|/g, "|");
|
|
1953
|
+
}
|
|
1954
|
+
return cells;
|
|
1955
|
+
}
|
|
1956
|
+
function rtrim(str, c, invert) {
|
|
1957
|
+
const l = str.length;
|
|
1958
|
+
if (l === 0) {
|
|
1959
|
+
return "";
|
|
1960
|
+
}
|
|
1961
|
+
let suffLen = 0;
|
|
1962
|
+
while (suffLen < l) {
|
|
1963
|
+
const currChar = str.charAt(l - suffLen - 1);
|
|
1964
|
+
if (currChar === c && !invert) {
|
|
1965
|
+
suffLen++;
|
|
1966
|
+
} else if (currChar !== c && invert) {
|
|
1967
|
+
suffLen++;
|
|
1968
|
+
} else {
|
|
1969
|
+
break;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
return str.slice(0, l - suffLen);
|
|
1973
|
+
}
|
|
1974
|
+
function findClosingBracket(str, b) {
|
|
1975
|
+
if (str.indexOf(b[1]) === -1) {
|
|
1976
|
+
return -1;
|
|
1977
|
+
}
|
|
1978
|
+
let level = 0;
|
|
1979
|
+
for (let i = 0; i < str.length; i++) {
|
|
1980
|
+
if (str[i] === "\\") {
|
|
1981
|
+
i++;
|
|
1982
|
+
} else if (str[i] === b[0]) {
|
|
1983
|
+
level++;
|
|
1984
|
+
} else if (str[i] === b[1]) {
|
|
1985
|
+
level--;
|
|
1986
|
+
if (level < 0) {
|
|
1987
|
+
return i;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
return -1;
|
|
1992
|
+
}
|
|
1993
|
+
function outputLink(cap, link2, raw, lexer2) {
|
|
1994
|
+
const href = link2.href;
|
|
1995
|
+
const title = link2.title ? escape$1(link2.title) : null;
|
|
1996
|
+
const text2 = cap[1].replace(/\\([\[\]])/g, "$1");
|
|
1997
|
+
if (cap[0].charAt(0) !== "!") {
|
|
1998
|
+
lexer2.state.inLink = true;
|
|
1999
|
+
const token = {
|
|
2000
|
+
type: "link",
|
|
2001
|
+
raw,
|
|
2002
|
+
href,
|
|
2003
|
+
title,
|
|
2004
|
+
text: text2,
|
|
2005
|
+
tokens: lexer2.inlineTokens(text2)
|
|
2006
|
+
};
|
|
2007
|
+
lexer2.state.inLink = false;
|
|
2008
|
+
return token;
|
|
2009
|
+
}
|
|
2010
|
+
return {
|
|
2011
|
+
type: "image",
|
|
2012
|
+
raw,
|
|
2013
|
+
href,
|
|
2014
|
+
title,
|
|
2015
|
+
text: escape$1(text2)
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
function indentCodeCompensation(raw, text2) {
|
|
2019
|
+
const matchIndentToCode = raw.match(/^(\s+)(?:```)/);
|
|
2020
|
+
if (matchIndentToCode === null) {
|
|
2021
|
+
return text2;
|
|
2022
|
+
}
|
|
2023
|
+
const indentToCode = matchIndentToCode[1];
|
|
2024
|
+
return text2.split("\n").map((node) => {
|
|
2025
|
+
const matchIndentInNode = node.match(/^\s+/);
|
|
2026
|
+
if (matchIndentInNode === null) {
|
|
2027
|
+
return node;
|
|
2028
|
+
}
|
|
2029
|
+
const [indentInNode] = matchIndentInNode;
|
|
2030
|
+
if (indentInNode.length >= indentToCode.length) {
|
|
2031
|
+
return node.slice(indentToCode.length);
|
|
2032
|
+
}
|
|
2033
|
+
return node;
|
|
2034
|
+
}).join("\n");
|
|
2035
|
+
}
|
|
2036
|
+
var _Tokenizer = class {
|
|
2037
|
+
options;
|
|
2038
|
+
rules;
|
|
2039
|
+
// set by the lexer
|
|
2040
|
+
lexer;
|
|
2041
|
+
// set by the lexer
|
|
2042
|
+
constructor(options2) {
|
|
2043
|
+
this.options = options2 || _defaults;
|
|
2044
|
+
}
|
|
2045
|
+
space(src) {
|
|
2046
|
+
const cap = this.rules.block.newline.exec(src);
|
|
2047
|
+
if (cap && cap[0].length > 0) {
|
|
2048
|
+
return {
|
|
2049
|
+
type: "space",
|
|
2050
|
+
raw: cap[0]
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
code(src) {
|
|
2055
|
+
const cap = this.rules.block.code.exec(src);
|
|
2056
|
+
if (cap) {
|
|
2057
|
+
const text2 = cap[0].replace(/^(?: {1,4}| {0,3}\t)/gm, "");
|
|
2058
|
+
return {
|
|
2059
|
+
type: "code",
|
|
2060
|
+
raw: cap[0],
|
|
2061
|
+
codeBlockStyle: "indented",
|
|
2062
|
+
text: !this.options.pedantic ? rtrim(text2, "\n") : text2
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
fences(src) {
|
|
2067
|
+
const cap = this.rules.block.fences.exec(src);
|
|
2068
|
+
if (cap) {
|
|
2069
|
+
const raw = cap[0];
|
|
2070
|
+
const text2 = indentCodeCompensation(raw, cap[3] || "");
|
|
2071
|
+
return {
|
|
2072
|
+
type: "code",
|
|
2073
|
+
raw,
|
|
2074
|
+
lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : cap[2],
|
|
2075
|
+
text: text2
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
heading(src) {
|
|
2080
|
+
const cap = this.rules.block.heading.exec(src);
|
|
2081
|
+
if (cap) {
|
|
2082
|
+
let text2 = cap[2].trim();
|
|
2083
|
+
if (/#$/.test(text2)) {
|
|
2084
|
+
const trimmed = rtrim(text2, "#");
|
|
2085
|
+
if (this.options.pedantic) {
|
|
2086
|
+
text2 = trimmed.trim();
|
|
2087
|
+
} else if (!trimmed || / $/.test(trimmed)) {
|
|
2088
|
+
text2 = trimmed.trim();
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
return {
|
|
2092
|
+
type: "heading",
|
|
2093
|
+
raw: cap[0],
|
|
2094
|
+
depth: cap[1].length,
|
|
2095
|
+
text: text2,
|
|
2096
|
+
tokens: this.lexer.inline(text2)
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
hr(src) {
|
|
2101
|
+
const cap = this.rules.block.hr.exec(src);
|
|
2102
|
+
if (cap) {
|
|
2103
|
+
return {
|
|
2104
|
+
type: "hr",
|
|
2105
|
+
raw: rtrim(cap[0], "\n")
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
blockquote(src) {
|
|
2110
|
+
const cap = this.rules.block.blockquote.exec(src);
|
|
2111
|
+
if (cap) {
|
|
2112
|
+
let lines = rtrim(cap[0], "\n").split("\n");
|
|
2113
|
+
let raw = "";
|
|
2114
|
+
let text2 = "";
|
|
2115
|
+
const tokens = [];
|
|
2116
|
+
while (lines.length > 0) {
|
|
2117
|
+
let inBlockquote = false;
|
|
2118
|
+
const currentLines = [];
|
|
2119
|
+
let i;
|
|
2120
|
+
for (i = 0; i < lines.length; i++) {
|
|
2121
|
+
if (/^ {0,3}>/.test(lines[i])) {
|
|
2122
|
+
currentLines.push(lines[i]);
|
|
2123
|
+
inBlockquote = true;
|
|
2124
|
+
} else if (!inBlockquote) {
|
|
2125
|
+
currentLines.push(lines[i]);
|
|
2126
|
+
} else {
|
|
2127
|
+
break;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
lines = lines.slice(i);
|
|
2131
|
+
const currentRaw = currentLines.join("\n");
|
|
2132
|
+
const currentText = currentRaw.replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g, "\n $1").replace(/^ {0,3}>[ \t]?/gm, "");
|
|
2133
|
+
raw = raw ? `${raw}
|
|
2134
|
+
${currentRaw}` : currentRaw;
|
|
2135
|
+
text2 = text2 ? `${text2}
|
|
2136
|
+
${currentText}` : currentText;
|
|
2137
|
+
const top = this.lexer.state.top;
|
|
2138
|
+
this.lexer.state.top = true;
|
|
2139
|
+
this.lexer.blockTokens(currentText, tokens, true);
|
|
2140
|
+
this.lexer.state.top = top;
|
|
2141
|
+
if (lines.length === 0) {
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
const lastToken = tokens[tokens.length - 1];
|
|
2145
|
+
if (lastToken?.type === "code") {
|
|
2146
|
+
break;
|
|
2147
|
+
} else if (lastToken?.type === "blockquote") {
|
|
2148
|
+
const oldToken = lastToken;
|
|
2149
|
+
const newText = oldToken.raw + "\n" + lines.join("\n");
|
|
2150
|
+
const newToken = this.blockquote(newText);
|
|
2151
|
+
tokens[tokens.length - 1] = newToken;
|
|
2152
|
+
raw = raw.substring(0, raw.length - oldToken.raw.length) + newToken.raw;
|
|
2153
|
+
text2 = text2.substring(0, text2.length - oldToken.text.length) + newToken.text;
|
|
2154
|
+
break;
|
|
2155
|
+
} else if (lastToken?.type === "list") {
|
|
2156
|
+
const oldToken = lastToken;
|
|
2157
|
+
const newText = oldToken.raw + "\n" + lines.join("\n");
|
|
2158
|
+
const newToken = this.list(newText);
|
|
2159
|
+
tokens[tokens.length - 1] = newToken;
|
|
2160
|
+
raw = raw.substring(0, raw.length - lastToken.raw.length) + newToken.raw;
|
|
2161
|
+
text2 = text2.substring(0, text2.length - oldToken.raw.length) + newToken.raw;
|
|
2162
|
+
lines = newText.substring(tokens[tokens.length - 1].raw.length).split("\n");
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
return {
|
|
2167
|
+
type: "blockquote",
|
|
2168
|
+
raw,
|
|
2169
|
+
tokens,
|
|
2170
|
+
text: text2
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
list(src) {
|
|
2175
|
+
let cap = this.rules.block.list.exec(src);
|
|
2176
|
+
if (cap) {
|
|
2177
|
+
let bull = cap[1].trim();
|
|
2178
|
+
const isordered = bull.length > 1;
|
|
2179
|
+
const list2 = {
|
|
2180
|
+
type: "list",
|
|
2181
|
+
raw: "",
|
|
2182
|
+
ordered: isordered,
|
|
2183
|
+
start: isordered ? +bull.slice(0, -1) : "",
|
|
2184
|
+
loose: false,
|
|
2185
|
+
items: []
|
|
2186
|
+
};
|
|
2187
|
+
bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`;
|
|
2188
|
+
if (this.options.pedantic) {
|
|
2189
|
+
bull = isordered ? bull : "[*+-]";
|
|
2190
|
+
}
|
|
2191
|
+
const itemRegex = new RegExp(`^( {0,3}${bull})((?:[ ][^\\n]*)?(?:\\n|$))`);
|
|
2192
|
+
let endsWithBlankLine = false;
|
|
2193
|
+
while (src) {
|
|
2194
|
+
let endEarly = false;
|
|
2195
|
+
let raw = "";
|
|
2196
|
+
let itemContents = "";
|
|
2197
|
+
if (!(cap = itemRegex.exec(src))) {
|
|
2198
|
+
break;
|
|
2199
|
+
}
|
|
2200
|
+
if (this.rules.block.hr.test(src)) {
|
|
2201
|
+
break;
|
|
2202
|
+
}
|
|
2203
|
+
raw = cap[0];
|
|
2204
|
+
src = src.substring(raw.length);
|
|
2205
|
+
let line = cap[2].split("\n", 1)[0].replace(/^\t+/, (t) => " ".repeat(3 * t.length));
|
|
2206
|
+
let nextLine = src.split("\n", 1)[0];
|
|
2207
|
+
let blankLine = !line.trim();
|
|
2208
|
+
let indent = 0;
|
|
2209
|
+
if (this.options.pedantic) {
|
|
2210
|
+
indent = 2;
|
|
2211
|
+
itemContents = line.trimStart();
|
|
2212
|
+
} else if (blankLine) {
|
|
2213
|
+
indent = cap[1].length + 1;
|
|
2214
|
+
} else {
|
|
2215
|
+
indent = cap[2].search(/[^ ]/);
|
|
2216
|
+
indent = indent > 4 ? 1 : indent;
|
|
2217
|
+
itemContents = line.slice(indent);
|
|
2218
|
+
indent += cap[1].length;
|
|
2219
|
+
}
|
|
2220
|
+
if (blankLine && /^[ \t]*$/.test(nextLine)) {
|
|
2221
|
+
raw += nextLine + "\n";
|
|
2222
|
+
src = src.substring(nextLine.length + 1);
|
|
2223
|
+
endEarly = true;
|
|
2224
|
+
}
|
|
2225
|
+
if (!endEarly) {
|
|
2226
|
+
const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`);
|
|
2227
|
+
const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`);
|
|
2228
|
+
const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`);
|
|
2229
|
+
const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`);
|
|
2230
|
+
const htmlBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, "i");
|
|
2231
|
+
while (src) {
|
|
2232
|
+
const rawLine = src.split("\n", 1)[0];
|
|
2233
|
+
let nextLineWithoutTabs;
|
|
2234
|
+
nextLine = rawLine;
|
|
2235
|
+
if (this.options.pedantic) {
|
|
2236
|
+
nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, " ");
|
|
2237
|
+
nextLineWithoutTabs = nextLine;
|
|
2238
|
+
} else {
|
|
2239
|
+
nextLineWithoutTabs = nextLine.replace(/\t/g, " ");
|
|
2240
|
+
}
|
|
2241
|
+
if (fencesBeginRegex.test(nextLine)) {
|
|
2242
|
+
break;
|
|
2243
|
+
}
|
|
2244
|
+
if (headingBeginRegex.test(nextLine)) {
|
|
2245
|
+
break;
|
|
2246
|
+
}
|
|
2247
|
+
if (htmlBeginRegex.test(nextLine)) {
|
|
2248
|
+
break;
|
|
2249
|
+
}
|
|
2250
|
+
if (nextBulletRegex.test(nextLine)) {
|
|
2251
|
+
break;
|
|
2252
|
+
}
|
|
2253
|
+
if (hrRegex.test(nextLine)) {
|
|
2254
|
+
break;
|
|
2255
|
+
}
|
|
2256
|
+
if (nextLineWithoutTabs.search(/[^ ]/) >= indent || !nextLine.trim()) {
|
|
2257
|
+
itemContents += "\n" + nextLineWithoutTabs.slice(indent);
|
|
2258
|
+
} else {
|
|
2259
|
+
if (blankLine) {
|
|
2260
|
+
break;
|
|
2261
|
+
}
|
|
2262
|
+
if (line.replace(/\t/g, " ").search(/[^ ]/) >= 4) {
|
|
2263
|
+
break;
|
|
2264
|
+
}
|
|
2265
|
+
if (fencesBeginRegex.test(line)) {
|
|
2266
|
+
break;
|
|
2267
|
+
}
|
|
2268
|
+
if (headingBeginRegex.test(line)) {
|
|
2269
|
+
break;
|
|
2270
|
+
}
|
|
2271
|
+
if (hrRegex.test(line)) {
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
2274
|
+
itemContents += "\n" + nextLine;
|
|
2275
|
+
}
|
|
2276
|
+
if (!blankLine && !nextLine.trim()) {
|
|
2277
|
+
blankLine = true;
|
|
2278
|
+
}
|
|
2279
|
+
raw += rawLine + "\n";
|
|
2280
|
+
src = src.substring(rawLine.length + 1);
|
|
2281
|
+
line = nextLineWithoutTabs.slice(indent);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
if (!list2.loose) {
|
|
2285
|
+
if (endsWithBlankLine) {
|
|
2286
|
+
list2.loose = true;
|
|
2287
|
+
} else if (/\n[ \t]*\n[ \t]*$/.test(raw)) {
|
|
2288
|
+
endsWithBlankLine = true;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
let istask = null;
|
|
2292
|
+
let ischecked;
|
|
2293
|
+
if (this.options.gfm) {
|
|
2294
|
+
istask = /^\[[ xX]\] /.exec(itemContents);
|
|
2295
|
+
if (istask) {
|
|
2296
|
+
ischecked = istask[0] !== "[ ] ";
|
|
2297
|
+
itemContents = itemContents.replace(/^\[[ xX]\] +/, "");
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
list2.items.push({
|
|
2301
|
+
type: "list_item",
|
|
2302
|
+
raw,
|
|
2303
|
+
task: !!istask,
|
|
2304
|
+
checked: ischecked,
|
|
2305
|
+
loose: false,
|
|
2306
|
+
text: itemContents,
|
|
2307
|
+
tokens: []
|
|
2308
|
+
});
|
|
2309
|
+
list2.raw += raw;
|
|
2310
|
+
}
|
|
2311
|
+
list2.items[list2.items.length - 1].raw = list2.items[list2.items.length - 1].raw.trimEnd();
|
|
2312
|
+
list2.items[list2.items.length - 1].text = list2.items[list2.items.length - 1].text.trimEnd();
|
|
2313
|
+
list2.raw = list2.raw.trimEnd();
|
|
2314
|
+
for (let i = 0; i < list2.items.length; i++) {
|
|
2315
|
+
this.lexer.state.top = false;
|
|
2316
|
+
list2.items[i].tokens = this.lexer.blockTokens(list2.items[i].text, []);
|
|
2317
|
+
if (!list2.loose) {
|
|
2318
|
+
const spacers = list2.items[i].tokens.filter((t) => t.type === "space");
|
|
2319
|
+
const hasMultipleLineBreaks = spacers.length > 0 && spacers.some((t) => /\n.*\n/.test(t.raw));
|
|
2320
|
+
list2.loose = hasMultipleLineBreaks;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
if (list2.loose) {
|
|
2324
|
+
for (let i = 0; i < list2.items.length; i++) {
|
|
2325
|
+
list2.items[i].loose = true;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
return list2;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
html(src) {
|
|
2332
|
+
const cap = this.rules.block.html.exec(src);
|
|
2333
|
+
if (cap) {
|
|
2334
|
+
const token = {
|
|
2335
|
+
type: "html",
|
|
2336
|
+
block: true,
|
|
2337
|
+
raw: cap[0],
|
|
2338
|
+
pre: cap[1] === "pre" || cap[1] === "script" || cap[1] === "style",
|
|
2339
|
+
text: cap[0]
|
|
2340
|
+
};
|
|
2341
|
+
return token;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
def(src) {
|
|
2345
|
+
const cap = this.rules.block.def.exec(src);
|
|
2346
|
+
if (cap) {
|
|
2347
|
+
const tag2 = cap[1].toLowerCase().replace(/\s+/g, " ");
|
|
2348
|
+
const href = cap[2] ? cap[2].replace(/^<(.*)>$/, "$1").replace(this.rules.inline.anyPunctuation, "$1") : "";
|
|
2349
|
+
const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline.anyPunctuation, "$1") : cap[3];
|
|
2350
|
+
return {
|
|
2351
|
+
type: "def",
|
|
2352
|
+
tag: tag2,
|
|
2353
|
+
raw: cap[0],
|
|
2354
|
+
href,
|
|
2355
|
+
title
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
table(src) {
|
|
2360
|
+
const cap = this.rules.block.table.exec(src);
|
|
2361
|
+
if (!cap) {
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
if (!/[:|]/.test(cap[2])) {
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
const headers = splitCells(cap[1]);
|
|
2368
|
+
const aligns = cap[2].replace(/^\||\| *$/g, "").split("|");
|
|
2369
|
+
const rows = cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, "").split("\n") : [];
|
|
2370
|
+
const item = {
|
|
2371
|
+
type: "table",
|
|
2372
|
+
raw: cap[0],
|
|
2373
|
+
header: [],
|
|
2374
|
+
align: [],
|
|
2375
|
+
rows: []
|
|
2376
|
+
};
|
|
2377
|
+
if (headers.length !== aligns.length) {
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
for (const align of aligns) {
|
|
2381
|
+
if (/^ *-+: *$/.test(align)) {
|
|
2382
|
+
item.align.push("right");
|
|
2383
|
+
} else if (/^ *:-+: *$/.test(align)) {
|
|
2384
|
+
item.align.push("center");
|
|
2385
|
+
} else if (/^ *:-+ *$/.test(align)) {
|
|
2386
|
+
item.align.push("left");
|
|
2387
|
+
} else {
|
|
2388
|
+
item.align.push(null);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
for (let i = 0; i < headers.length; i++) {
|
|
2392
|
+
item.header.push({
|
|
2393
|
+
text: headers[i],
|
|
2394
|
+
tokens: this.lexer.inline(headers[i]),
|
|
2395
|
+
header: true,
|
|
2396
|
+
align: item.align[i]
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
for (const row of rows) {
|
|
2400
|
+
item.rows.push(splitCells(row, item.header.length).map((cell, i) => {
|
|
2401
|
+
return {
|
|
2402
|
+
text: cell,
|
|
2403
|
+
tokens: this.lexer.inline(cell),
|
|
2404
|
+
header: false,
|
|
2405
|
+
align: item.align[i]
|
|
2406
|
+
};
|
|
2407
|
+
}));
|
|
2408
|
+
}
|
|
2409
|
+
return item;
|
|
2410
|
+
}
|
|
2411
|
+
lheading(src) {
|
|
2412
|
+
const cap = this.rules.block.lheading.exec(src);
|
|
2413
|
+
if (cap) {
|
|
2414
|
+
return {
|
|
2415
|
+
type: "heading",
|
|
2416
|
+
raw: cap[0],
|
|
2417
|
+
depth: cap[2].charAt(0) === "=" ? 1 : 2,
|
|
2418
|
+
text: cap[1],
|
|
2419
|
+
tokens: this.lexer.inline(cap[1])
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
paragraph(src) {
|
|
2424
|
+
const cap = this.rules.block.paragraph.exec(src);
|
|
2425
|
+
if (cap) {
|
|
2426
|
+
const text2 = cap[1].charAt(cap[1].length - 1) === "\n" ? cap[1].slice(0, -1) : cap[1];
|
|
2427
|
+
return {
|
|
2428
|
+
type: "paragraph",
|
|
2429
|
+
raw: cap[0],
|
|
2430
|
+
text: text2,
|
|
2431
|
+
tokens: this.lexer.inline(text2)
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
text(src) {
|
|
2436
|
+
const cap = this.rules.block.text.exec(src);
|
|
2437
|
+
if (cap) {
|
|
2438
|
+
return {
|
|
2439
|
+
type: "text",
|
|
2440
|
+
raw: cap[0],
|
|
2441
|
+
text: cap[0],
|
|
2442
|
+
tokens: this.lexer.inline(cap[0])
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
escape(src) {
|
|
2447
|
+
const cap = this.rules.inline.escape.exec(src);
|
|
2448
|
+
if (cap) {
|
|
2449
|
+
return {
|
|
2450
|
+
type: "escape",
|
|
2451
|
+
raw: cap[0],
|
|
2452
|
+
text: escape$1(cap[1])
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
tag(src) {
|
|
2457
|
+
const cap = this.rules.inline.tag.exec(src);
|
|
2458
|
+
if (cap) {
|
|
2459
|
+
if (!this.lexer.state.inLink && /^<a /i.test(cap[0])) {
|
|
2460
|
+
this.lexer.state.inLink = true;
|
|
2461
|
+
} else if (this.lexer.state.inLink && /^<\/a>/i.test(cap[0])) {
|
|
2462
|
+
this.lexer.state.inLink = false;
|
|
2463
|
+
}
|
|
2464
|
+
if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
|
2465
|
+
this.lexer.state.inRawBlock = true;
|
|
2466
|
+
} else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
|
2467
|
+
this.lexer.state.inRawBlock = false;
|
|
2468
|
+
}
|
|
2469
|
+
return {
|
|
2470
|
+
type: "html",
|
|
2471
|
+
raw: cap[0],
|
|
2472
|
+
inLink: this.lexer.state.inLink,
|
|
2473
|
+
inRawBlock: this.lexer.state.inRawBlock,
|
|
2474
|
+
block: false,
|
|
2475
|
+
text: cap[0]
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
link(src) {
|
|
2480
|
+
const cap = this.rules.inline.link.exec(src);
|
|
2481
|
+
if (cap) {
|
|
2482
|
+
const trimmedUrl = cap[2].trim();
|
|
2483
|
+
if (!this.options.pedantic && /^</.test(trimmedUrl)) {
|
|
2484
|
+
if (!/>$/.test(trimmedUrl)) {
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), "\\");
|
|
2488
|
+
if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
} else {
|
|
2492
|
+
const lastParenIndex = findClosingBracket(cap[2], "()");
|
|
2493
|
+
if (lastParenIndex > -1) {
|
|
2494
|
+
const start = cap[0].indexOf("!") === 0 ? 5 : 4;
|
|
2495
|
+
const linkLen = start + cap[1].length + lastParenIndex;
|
|
2496
|
+
cap[2] = cap[2].substring(0, lastParenIndex);
|
|
2497
|
+
cap[0] = cap[0].substring(0, linkLen).trim();
|
|
2498
|
+
cap[3] = "";
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
let href = cap[2];
|
|
2502
|
+
let title = "";
|
|
2503
|
+
if (this.options.pedantic) {
|
|
2504
|
+
const link2 = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
|
|
2505
|
+
if (link2) {
|
|
2506
|
+
href = link2[1];
|
|
2507
|
+
title = link2[3];
|
|
2508
|
+
}
|
|
2509
|
+
} else {
|
|
2510
|
+
title = cap[3] ? cap[3].slice(1, -1) : "";
|
|
2511
|
+
}
|
|
2512
|
+
href = href.trim();
|
|
2513
|
+
if (/^</.test(href)) {
|
|
2514
|
+
if (this.options.pedantic && !/>$/.test(trimmedUrl)) {
|
|
2515
|
+
href = href.slice(1);
|
|
2516
|
+
} else {
|
|
2517
|
+
href = href.slice(1, -1);
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
return outputLink(cap, {
|
|
2521
|
+
href: href ? href.replace(this.rules.inline.anyPunctuation, "$1") : href,
|
|
2522
|
+
title: title ? title.replace(this.rules.inline.anyPunctuation, "$1") : title
|
|
2523
|
+
}, cap[0], this.lexer);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
reflink(src, links) {
|
|
2527
|
+
let cap;
|
|
2528
|
+
if ((cap = this.rules.inline.reflink.exec(src)) || (cap = this.rules.inline.nolink.exec(src))) {
|
|
2529
|
+
const linkString = (cap[2] || cap[1]).replace(/\s+/g, " ");
|
|
2530
|
+
const link2 = links[linkString.toLowerCase()];
|
|
2531
|
+
if (!link2) {
|
|
2532
|
+
const text2 = cap[0].charAt(0);
|
|
2533
|
+
return {
|
|
2534
|
+
type: "text",
|
|
2535
|
+
raw: text2,
|
|
2536
|
+
text: text2
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
return outputLink(cap, link2, cap[0], this.lexer);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
emStrong(src, maskedSrc, prevChar = "") {
|
|
2543
|
+
let match = this.rules.inline.emStrongLDelim.exec(src);
|
|
2544
|
+
if (!match)
|
|
2545
|
+
return;
|
|
2546
|
+
if (match[3] && prevChar.match(/[\p{L}\p{N}]/u))
|
|
2547
|
+
return;
|
|
2548
|
+
const nextChar = match[1] || match[2] || "";
|
|
2549
|
+
if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) {
|
|
2550
|
+
const lLength = [...match[0]].length - 1;
|
|
2551
|
+
let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
|
|
2552
|
+
const endReg = match[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
|
|
2553
|
+
endReg.lastIndex = 0;
|
|
2554
|
+
maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
|
|
2555
|
+
while ((match = endReg.exec(maskedSrc)) != null) {
|
|
2556
|
+
rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
|
|
2557
|
+
if (!rDelim)
|
|
2558
|
+
continue;
|
|
2559
|
+
rLength = [...rDelim].length;
|
|
2560
|
+
if (match[3] || match[4]) {
|
|
2561
|
+
delimTotal += rLength;
|
|
2562
|
+
continue;
|
|
2563
|
+
} else if (match[5] || match[6]) {
|
|
2564
|
+
if (lLength % 3 && !((lLength + rLength) % 3)) {
|
|
2565
|
+
midDelimTotal += rLength;
|
|
2566
|
+
continue;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
delimTotal -= rLength;
|
|
2570
|
+
if (delimTotal > 0)
|
|
2571
|
+
continue;
|
|
2572
|
+
rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
|
|
2573
|
+
const lastCharLength = [...match[0]][0].length;
|
|
2574
|
+
const raw = src.slice(0, lLength + match.index + lastCharLength + rLength);
|
|
2575
|
+
if (Math.min(lLength, rLength) % 2) {
|
|
2576
|
+
const text3 = raw.slice(1, -1);
|
|
2577
|
+
return {
|
|
2578
|
+
type: "em",
|
|
2579
|
+
raw,
|
|
2580
|
+
text: text3,
|
|
2581
|
+
tokens: this.lexer.inlineTokens(text3)
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
const text2 = raw.slice(2, -2);
|
|
2585
|
+
return {
|
|
2586
|
+
type: "strong",
|
|
2587
|
+
raw,
|
|
2588
|
+
text: text2,
|
|
2589
|
+
tokens: this.lexer.inlineTokens(text2)
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
codespan(src) {
|
|
2595
|
+
const cap = this.rules.inline.code.exec(src);
|
|
2596
|
+
if (cap) {
|
|
2597
|
+
let text2 = cap[2].replace(/\n/g, " ");
|
|
2598
|
+
const hasNonSpaceChars = /[^ ]/.test(text2);
|
|
2599
|
+
const hasSpaceCharsOnBothEnds = /^ /.test(text2) && / $/.test(text2);
|
|
2600
|
+
if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
|
|
2601
|
+
text2 = text2.substring(1, text2.length - 1);
|
|
2602
|
+
}
|
|
2603
|
+
text2 = escape$1(text2, true);
|
|
2604
|
+
return {
|
|
2605
|
+
type: "codespan",
|
|
2606
|
+
raw: cap[0],
|
|
2607
|
+
text: text2
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
br(src) {
|
|
2612
|
+
const cap = this.rules.inline.br.exec(src);
|
|
2613
|
+
if (cap) {
|
|
2614
|
+
return {
|
|
2615
|
+
type: "br",
|
|
2616
|
+
raw: cap[0]
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
del(src) {
|
|
2621
|
+
const cap = this.rules.inline.del.exec(src);
|
|
2622
|
+
if (cap) {
|
|
2623
|
+
return {
|
|
2624
|
+
type: "del",
|
|
2625
|
+
raw: cap[0],
|
|
2626
|
+
text: cap[2],
|
|
2627
|
+
tokens: this.lexer.inlineTokens(cap[2])
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
autolink(src) {
|
|
2632
|
+
const cap = this.rules.inline.autolink.exec(src);
|
|
2633
|
+
if (cap) {
|
|
2634
|
+
let text2, href;
|
|
2635
|
+
if (cap[2] === "@") {
|
|
2636
|
+
text2 = escape$1(cap[1]);
|
|
2637
|
+
href = "mailto:" + text2;
|
|
2638
|
+
} else {
|
|
2639
|
+
text2 = escape$1(cap[1]);
|
|
2640
|
+
href = text2;
|
|
2641
|
+
}
|
|
2642
|
+
return {
|
|
2643
|
+
type: "link",
|
|
2644
|
+
raw: cap[0],
|
|
2645
|
+
text: text2,
|
|
2646
|
+
href,
|
|
2647
|
+
tokens: [
|
|
2648
|
+
{
|
|
2649
|
+
type: "text",
|
|
2650
|
+
raw: text2,
|
|
2651
|
+
text: text2
|
|
2652
|
+
}
|
|
2653
|
+
]
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
url(src) {
|
|
2658
|
+
let cap;
|
|
2659
|
+
if (cap = this.rules.inline.url.exec(src)) {
|
|
2660
|
+
let text2, href;
|
|
2661
|
+
if (cap[2] === "@") {
|
|
2662
|
+
text2 = escape$1(cap[0]);
|
|
2663
|
+
href = "mailto:" + text2;
|
|
2664
|
+
} else {
|
|
2665
|
+
let prevCapZero;
|
|
2666
|
+
do {
|
|
2667
|
+
prevCapZero = cap[0];
|
|
2668
|
+
cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? "";
|
|
2669
|
+
} while (prevCapZero !== cap[0]);
|
|
2670
|
+
text2 = escape$1(cap[0]);
|
|
2671
|
+
if (cap[1] === "www.") {
|
|
2672
|
+
href = "http://" + cap[0];
|
|
2673
|
+
} else {
|
|
2674
|
+
href = cap[0];
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
return {
|
|
2678
|
+
type: "link",
|
|
2679
|
+
raw: cap[0],
|
|
2680
|
+
text: text2,
|
|
2681
|
+
href,
|
|
2682
|
+
tokens: [
|
|
2683
|
+
{
|
|
2684
|
+
type: "text",
|
|
2685
|
+
raw: text2,
|
|
2686
|
+
text: text2
|
|
2687
|
+
}
|
|
2688
|
+
]
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
inlineText(src) {
|
|
2693
|
+
const cap = this.rules.inline.text.exec(src);
|
|
2694
|
+
if (cap) {
|
|
2695
|
+
let text2;
|
|
2696
|
+
if (this.lexer.state.inRawBlock) {
|
|
2697
|
+
text2 = cap[0];
|
|
2698
|
+
} else {
|
|
2699
|
+
text2 = escape$1(cap[0]);
|
|
2700
|
+
}
|
|
2701
|
+
return {
|
|
2702
|
+
type: "text",
|
|
2703
|
+
raw: cap[0],
|
|
2704
|
+
text: text2
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
};
|
|
2709
|
+
var newline = /^(?:[ \t]*(?:\n|$))+/;
|
|
2710
|
+
var blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
|
|
2711
|
+
var fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
|
|
2712
|
+
var hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
|
|
2713
|
+
var heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
|
|
2714
|
+
var bullet = /(?:[*+-]|\d{1,9}[.)])/;
|
|
2715
|
+
var lheading = edit(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g, bullet).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).getRegex();
|
|
2716
|
+
var _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/;
|
|
2717
|
+
var blockText = /^[^\n]+/;
|
|
2718
|
+
var _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/;
|
|
2719
|
+
var def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", _blockLabel).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
|
|
2720
|
+
var list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, bullet).getRegex();
|
|
2721
|
+
var _tag = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
|
|
2722
|
+
var _comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
|
|
2723
|
+
var html2 = edit("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", _comment).replace("tag", _tag).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
|
|
2724
|
+
var paragraph = edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
|
|
2725
|
+
var blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", paragraph).getRegex();
|
|
2726
|
+
var blockNormal = {
|
|
2727
|
+
blockquote,
|
|
2728
|
+
code: blockCode,
|
|
2729
|
+
def,
|
|
2730
|
+
fences,
|
|
2731
|
+
heading,
|
|
2732
|
+
hr,
|
|
2733
|
+
html: html2,
|
|
2734
|
+
lheading,
|
|
2735
|
+
list,
|
|
2736
|
+
newline,
|
|
2737
|
+
paragraph,
|
|
2738
|
+
table: noopTest,
|
|
2739
|
+
text: blockText
|
|
2740
|
+
};
|
|
2741
|
+
var gfmTable = edit("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
|
|
2742
|
+
var blockGfm = {
|
|
2743
|
+
...blockNormal,
|
|
2744
|
+
table: gfmTable,
|
|
2745
|
+
paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", gfmTable).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex()
|
|
2746
|
+
};
|
|
2747
|
+
var blockPedantic = {
|
|
2748
|
+
...blockNormal,
|
|
2749
|
+
html: edit(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", _comment).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),
|
|
2750
|
+
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
|
|
2751
|
+
heading: /^(#{1,6})(.*)(?:\n+|$)/,
|
|
2752
|
+
fences: noopTest,
|
|
2753
|
+
// fences not supported
|
|
2754
|
+
lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
|
|
2755
|
+
paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " *#{1,6} *[^\n]").replace("lheading", lheading).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex()
|
|
2756
|
+
};
|
|
2757
|
+
var escape = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
|
|
2758
|
+
var inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
|
|
2759
|
+
var br = /^( {2,}|\\)\n(?!\s*$)/;
|
|
2760
|
+
var inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
|
|
2761
|
+
var _punctuation = "\\p{P}\\p{S}";
|
|
2762
|
+
var punctuation = edit(/^((?![*_])[\spunctuation])/, "u").replace(/punctuation/g, _punctuation).getRegex();
|
|
2763
|
+
var blockSkip = /\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g;
|
|
2764
|
+
var emStrongLDelim = edit(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, "u").replace(/punct/g, _punctuation).getRegex();
|
|
2765
|
+
var emStrongRDelimAst = edit("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])", "gu").replace(/punct/g, _punctuation).getRegex();
|
|
2766
|
+
var emStrongRDelimUnd = edit("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])", "gu").replace(/punct/g, _punctuation).getRegex();
|
|
2767
|
+
var anyPunctuation = edit(/\\([punct])/, "gu").replace(/punct/g, _punctuation).getRegex();
|
|
2768
|
+
var autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
|
|
2769
|
+
var _inlineComment = edit(_comment).replace("(?:-->|$)", "-->").getRegex();
|
|
2770
|
+
var tag = edit("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment", _inlineComment).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
|
|
2771
|
+
var _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
|
|
2772
|
+
var link = edit(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label", _inlineLabel).replace("href", /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
|
|
2773
|
+
var reflink = edit(/^!?\[(label)\]\[(ref)\]/).replace("label", _inlineLabel).replace("ref", _blockLabel).getRegex();
|
|
2774
|
+
var nolink = edit(/^!?\[(ref)\](?:\[\])?/).replace("ref", _blockLabel).getRegex();
|
|
2775
|
+
var reflinkSearch = edit("reflink|nolink(?!\\()", "g").replace("reflink", reflink).replace("nolink", nolink).getRegex();
|
|
2776
|
+
var inlineNormal = {
|
|
2777
|
+
_backpedal: noopTest,
|
|
2778
|
+
// only used for GFM url
|
|
2779
|
+
anyPunctuation,
|
|
2780
|
+
autolink,
|
|
2781
|
+
blockSkip,
|
|
2782
|
+
br,
|
|
2783
|
+
code: inlineCode,
|
|
2784
|
+
del: noopTest,
|
|
2785
|
+
emStrongLDelim,
|
|
2786
|
+
emStrongRDelimAst,
|
|
2787
|
+
emStrongRDelimUnd,
|
|
2788
|
+
escape,
|
|
2789
|
+
link,
|
|
2790
|
+
nolink,
|
|
2791
|
+
punctuation,
|
|
2792
|
+
reflink,
|
|
2793
|
+
reflinkSearch,
|
|
2794
|
+
tag,
|
|
2795
|
+
text: inlineText,
|
|
2796
|
+
url: noopTest
|
|
2797
|
+
};
|
|
2798
|
+
var inlinePedantic = {
|
|
2799
|
+
...inlineNormal,
|
|
2800
|
+
link: edit(/^!?\[(label)\]\((.*?)\)/).replace("label", _inlineLabel).getRegex(),
|
|
2801
|
+
reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", _inlineLabel).getRegex()
|
|
2802
|
+
};
|
|
2803
|
+
var inlineGfm = {
|
|
2804
|
+
...inlineNormal,
|
|
2805
|
+
escape: edit(escape).replace("])", "~|])").getRegex(),
|
|
2806
|
+
url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, "i").replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),
|
|
2807
|
+
_backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,
|
|
2808
|
+
del: /^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,
|
|
2809
|
+
text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/
|
|
2810
|
+
};
|
|
2811
|
+
var inlineBreaks = {
|
|
2812
|
+
...inlineGfm,
|
|
2813
|
+
br: edit(br).replace("{2,}", "*").getRegex(),
|
|
2814
|
+
text: edit(inlineGfm.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex()
|
|
2815
|
+
};
|
|
2816
|
+
var block = {
|
|
2817
|
+
normal: blockNormal,
|
|
2818
|
+
gfm: blockGfm,
|
|
2819
|
+
pedantic: blockPedantic
|
|
2820
|
+
};
|
|
2821
|
+
var inline = {
|
|
2822
|
+
normal: inlineNormal,
|
|
2823
|
+
gfm: inlineGfm,
|
|
2824
|
+
breaks: inlineBreaks,
|
|
2825
|
+
pedantic: inlinePedantic
|
|
2826
|
+
};
|
|
2827
|
+
var _Lexer = class __Lexer {
|
|
2828
|
+
tokens;
|
|
2829
|
+
options;
|
|
2830
|
+
state;
|
|
2831
|
+
tokenizer;
|
|
2832
|
+
inlineQueue;
|
|
2833
|
+
constructor(options2) {
|
|
2834
|
+
this.tokens = [];
|
|
2835
|
+
this.tokens.links = /* @__PURE__ */ Object.create(null);
|
|
2836
|
+
this.options = options2 || _defaults;
|
|
2837
|
+
this.options.tokenizer = this.options.tokenizer || new _Tokenizer();
|
|
2838
|
+
this.tokenizer = this.options.tokenizer;
|
|
2839
|
+
this.tokenizer.options = this.options;
|
|
2840
|
+
this.tokenizer.lexer = this;
|
|
2841
|
+
this.inlineQueue = [];
|
|
2842
|
+
this.state = {
|
|
2843
|
+
inLink: false,
|
|
2844
|
+
inRawBlock: false,
|
|
2845
|
+
top: true
|
|
2846
|
+
};
|
|
2847
|
+
const rules = {
|
|
2848
|
+
block: block.normal,
|
|
2849
|
+
inline: inline.normal
|
|
2850
|
+
};
|
|
2851
|
+
if (this.options.pedantic) {
|
|
2852
|
+
rules.block = block.pedantic;
|
|
2853
|
+
rules.inline = inline.pedantic;
|
|
2854
|
+
} else if (this.options.gfm) {
|
|
2855
|
+
rules.block = block.gfm;
|
|
2856
|
+
if (this.options.breaks) {
|
|
2857
|
+
rules.inline = inline.breaks;
|
|
2858
|
+
} else {
|
|
2859
|
+
rules.inline = inline.gfm;
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
this.tokenizer.rules = rules;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Expose Rules
|
|
2866
|
+
*/
|
|
2867
|
+
static get rules() {
|
|
2868
|
+
return {
|
|
2869
|
+
block,
|
|
2870
|
+
inline
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Static Lex Method
|
|
2875
|
+
*/
|
|
2876
|
+
static lex(src, options2) {
|
|
2877
|
+
const lexer2 = new __Lexer(options2);
|
|
2878
|
+
return lexer2.lex(src);
|
|
2879
|
+
}
|
|
2880
|
+
/**
|
|
2881
|
+
* Static Lex Inline Method
|
|
2882
|
+
*/
|
|
2883
|
+
static lexInline(src, options2) {
|
|
2884
|
+
const lexer2 = new __Lexer(options2);
|
|
2885
|
+
return lexer2.inlineTokens(src);
|
|
2886
|
+
}
|
|
2887
|
+
/**
|
|
2888
|
+
* Preprocessing
|
|
2889
|
+
*/
|
|
2890
|
+
lex(src) {
|
|
2891
|
+
src = src.replace(/\r\n|\r/g, "\n");
|
|
2892
|
+
this.blockTokens(src, this.tokens);
|
|
2893
|
+
for (let i = 0; i < this.inlineQueue.length; i++) {
|
|
2894
|
+
const next = this.inlineQueue[i];
|
|
2895
|
+
this.inlineTokens(next.src, next.tokens);
|
|
2896
|
+
}
|
|
2897
|
+
this.inlineQueue = [];
|
|
2898
|
+
return this.tokens;
|
|
2899
|
+
}
|
|
2900
|
+
blockTokens(src, tokens = [], lastParagraphClipped = false) {
|
|
2901
|
+
if (this.options.pedantic) {
|
|
2902
|
+
src = src.replace(/\t/g, " ").replace(/^ +$/gm, "");
|
|
2903
|
+
}
|
|
2904
|
+
let token;
|
|
2905
|
+
let lastToken;
|
|
2906
|
+
let cutSrc;
|
|
2907
|
+
while (src) {
|
|
2908
|
+
if (this.options.extensions && this.options.extensions.block && this.options.extensions.block.some((extTokenizer) => {
|
|
2909
|
+
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
|
|
2910
|
+
src = src.substring(token.raw.length);
|
|
2911
|
+
tokens.push(token);
|
|
2912
|
+
return true;
|
|
2913
|
+
}
|
|
2914
|
+
return false;
|
|
2915
|
+
})) {
|
|
2916
|
+
continue;
|
|
2917
|
+
}
|
|
2918
|
+
if (token = this.tokenizer.space(src)) {
|
|
2919
|
+
src = src.substring(token.raw.length);
|
|
2920
|
+
if (token.raw.length === 1 && tokens.length > 0) {
|
|
2921
|
+
tokens[tokens.length - 1].raw += "\n";
|
|
2922
|
+
} else {
|
|
2923
|
+
tokens.push(token);
|
|
2924
|
+
}
|
|
2925
|
+
continue;
|
|
2926
|
+
}
|
|
2927
|
+
if (token = this.tokenizer.code(src)) {
|
|
2928
|
+
src = src.substring(token.raw.length);
|
|
2929
|
+
lastToken = tokens[tokens.length - 1];
|
|
2930
|
+
if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
|
|
2931
|
+
lastToken.raw += "\n" + token.raw;
|
|
2932
|
+
lastToken.text += "\n" + token.text;
|
|
2933
|
+
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
|
|
2934
|
+
} else {
|
|
2935
|
+
tokens.push(token);
|
|
2936
|
+
}
|
|
2937
|
+
continue;
|
|
2938
|
+
}
|
|
2939
|
+
if (token = this.tokenizer.fences(src)) {
|
|
2940
|
+
src = src.substring(token.raw.length);
|
|
2941
|
+
tokens.push(token);
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2944
|
+
if (token = this.tokenizer.heading(src)) {
|
|
2945
|
+
src = src.substring(token.raw.length);
|
|
2946
|
+
tokens.push(token);
|
|
2947
|
+
continue;
|
|
2948
|
+
}
|
|
2949
|
+
if (token = this.tokenizer.hr(src)) {
|
|
2950
|
+
src = src.substring(token.raw.length);
|
|
2951
|
+
tokens.push(token);
|
|
2952
|
+
continue;
|
|
2953
|
+
}
|
|
2954
|
+
if (token = this.tokenizer.blockquote(src)) {
|
|
2955
|
+
src = src.substring(token.raw.length);
|
|
2956
|
+
tokens.push(token);
|
|
2957
|
+
continue;
|
|
2958
|
+
}
|
|
2959
|
+
if (token = this.tokenizer.list(src)) {
|
|
2960
|
+
src = src.substring(token.raw.length);
|
|
2961
|
+
tokens.push(token);
|
|
2962
|
+
continue;
|
|
2963
|
+
}
|
|
2964
|
+
if (token = this.tokenizer.html(src)) {
|
|
2965
|
+
src = src.substring(token.raw.length);
|
|
2966
|
+
tokens.push(token);
|
|
2967
|
+
continue;
|
|
2968
|
+
}
|
|
2969
|
+
if (token = this.tokenizer.def(src)) {
|
|
2970
|
+
src = src.substring(token.raw.length);
|
|
2971
|
+
lastToken = tokens[tokens.length - 1];
|
|
2972
|
+
if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
|
|
2973
|
+
lastToken.raw += "\n" + token.raw;
|
|
2974
|
+
lastToken.text += "\n" + token.raw;
|
|
2975
|
+
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
|
|
2976
|
+
} else if (!this.tokens.links[token.tag]) {
|
|
2977
|
+
this.tokens.links[token.tag] = {
|
|
2978
|
+
href: token.href,
|
|
2979
|
+
title: token.title
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2982
|
+
continue;
|
|
2983
|
+
}
|
|
2984
|
+
if (token = this.tokenizer.table(src)) {
|
|
2985
|
+
src = src.substring(token.raw.length);
|
|
2986
|
+
tokens.push(token);
|
|
2987
|
+
continue;
|
|
2988
|
+
}
|
|
2989
|
+
if (token = this.tokenizer.lheading(src)) {
|
|
2990
|
+
src = src.substring(token.raw.length);
|
|
2991
|
+
tokens.push(token);
|
|
2992
|
+
continue;
|
|
2993
|
+
}
|
|
2994
|
+
cutSrc = src;
|
|
2995
|
+
if (this.options.extensions && this.options.extensions.startBlock) {
|
|
2996
|
+
let startIndex = Infinity;
|
|
2997
|
+
const tempSrc = src.slice(1);
|
|
2998
|
+
let tempStart;
|
|
2999
|
+
this.options.extensions.startBlock.forEach((getStartIndex) => {
|
|
3000
|
+
tempStart = getStartIndex.call({ lexer: this }, tempSrc);
|
|
3001
|
+
if (typeof tempStart === "number" && tempStart >= 0) {
|
|
3002
|
+
startIndex = Math.min(startIndex, tempStart);
|
|
3003
|
+
}
|
|
3004
|
+
});
|
|
3005
|
+
if (startIndex < Infinity && startIndex >= 0) {
|
|
3006
|
+
cutSrc = src.substring(0, startIndex + 1);
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
|
|
3010
|
+
lastToken = tokens[tokens.length - 1];
|
|
3011
|
+
if (lastParagraphClipped && lastToken?.type === "paragraph") {
|
|
3012
|
+
lastToken.raw += "\n" + token.raw;
|
|
3013
|
+
lastToken.text += "\n" + token.text;
|
|
3014
|
+
this.inlineQueue.pop();
|
|
3015
|
+
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
|
|
3016
|
+
} else {
|
|
3017
|
+
tokens.push(token);
|
|
3018
|
+
}
|
|
3019
|
+
lastParagraphClipped = cutSrc.length !== src.length;
|
|
3020
|
+
src = src.substring(token.raw.length);
|
|
3021
|
+
continue;
|
|
3022
|
+
}
|
|
3023
|
+
if (token = this.tokenizer.text(src)) {
|
|
3024
|
+
src = src.substring(token.raw.length);
|
|
3025
|
+
lastToken = tokens[tokens.length - 1];
|
|
3026
|
+
if (lastToken && lastToken.type === "text") {
|
|
3027
|
+
lastToken.raw += "\n" + token.raw;
|
|
3028
|
+
lastToken.text += "\n" + token.text;
|
|
3029
|
+
this.inlineQueue.pop();
|
|
3030
|
+
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
|
|
3031
|
+
} else {
|
|
3032
|
+
tokens.push(token);
|
|
3033
|
+
}
|
|
3034
|
+
continue;
|
|
3035
|
+
}
|
|
3036
|
+
if (src) {
|
|
3037
|
+
const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
|
|
3038
|
+
if (this.options.silent) {
|
|
3039
|
+
console.error(errMsg);
|
|
3040
|
+
break;
|
|
3041
|
+
} else {
|
|
3042
|
+
throw new Error(errMsg);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
this.state.top = true;
|
|
3047
|
+
return tokens;
|
|
3048
|
+
}
|
|
3049
|
+
inline(src, tokens = []) {
|
|
3050
|
+
this.inlineQueue.push({ src, tokens });
|
|
3051
|
+
return tokens;
|
|
3052
|
+
}
|
|
3053
|
+
/**
|
|
3054
|
+
* Lexing/Compiling
|
|
3055
|
+
*/
|
|
3056
|
+
inlineTokens(src, tokens = []) {
|
|
3057
|
+
let token, lastToken, cutSrc;
|
|
3058
|
+
let maskedSrc = src;
|
|
3059
|
+
let match;
|
|
3060
|
+
let keepPrevChar, prevChar;
|
|
3061
|
+
if (this.tokens.links) {
|
|
3062
|
+
const links = Object.keys(this.tokens.links);
|
|
3063
|
+
if (links.length > 0) {
|
|
3064
|
+
while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
|
|
3065
|
+
if (links.includes(match[0].slice(match[0].lastIndexOf("[") + 1, -1))) {
|
|
3066
|
+
maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
|
|
3072
|
+
maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
|
|
3073
|
+
}
|
|
3074
|
+
while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) {
|
|
3075
|
+
maskedSrc = maskedSrc.slice(0, match.index) + "++" + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);
|
|
3076
|
+
}
|
|
3077
|
+
while (src) {
|
|
3078
|
+
if (!keepPrevChar) {
|
|
3079
|
+
prevChar = "";
|
|
3080
|
+
}
|
|
3081
|
+
keepPrevChar = false;
|
|
3082
|
+
if (this.options.extensions && this.options.extensions.inline && this.options.extensions.inline.some((extTokenizer) => {
|
|
3083
|
+
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
|
|
3084
|
+
src = src.substring(token.raw.length);
|
|
3085
|
+
tokens.push(token);
|
|
3086
|
+
return true;
|
|
3087
|
+
}
|
|
3088
|
+
return false;
|
|
3089
|
+
})) {
|
|
3090
|
+
continue;
|
|
3091
|
+
}
|
|
3092
|
+
if (token = this.tokenizer.escape(src)) {
|
|
3093
|
+
src = src.substring(token.raw.length);
|
|
3094
|
+
tokens.push(token);
|
|
3095
|
+
continue;
|
|
3096
|
+
}
|
|
3097
|
+
if (token = this.tokenizer.tag(src)) {
|
|
3098
|
+
src = src.substring(token.raw.length);
|
|
3099
|
+
lastToken = tokens[tokens.length - 1];
|
|
3100
|
+
if (lastToken && token.type === "text" && lastToken.type === "text") {
|
|
3101
|
+
lastToken.raw += token.raw;
|
|
3102
|
+
lastToken.text += token.text;
|
|
3103
|
+
} else {
|
|
3104
|
+
tokens.push(token);
|
|
3105
|
+
}
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
if (token = this.tokenizer.link(src)) {
|
|
3109
|
+
src = src.substring(token.raw.length);
|
|
3110
|
+
tokens.push(token);
|
|
3111
|
+
continue;
|
|
3112
|
+
}
|
|
3113
|
+
if (token = this.tokenizer.reflink(src, this.tokens.links)) {
|
|
3114
|
+
src = src.substring(token.raw.length);
|
|
3115
|
+
lastToken = tokens[tokens.length - 1];
|
|
3116
|
+
if (lastToken && token.type === "text" && lastToken.type === "text") {
|
|
3117
|
+
lastToken.raw += token.raw;
|
|
3118
|
+
lastToken.text += token.text;
|
|
3119
|
+
} else {
|
|
3120
|
+
tokens.push(token);
|
|
3121
|
+
}
|
|
3122
|
+
continue;
|
|
3123
|
+
}
|
|
3124
|
+
if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) {
|
|
3125
|
+
src = src.substring(token.raw.length);
|
|
3126
|
+
tokens.push(token);
|
|
3127
|
+
continue;
|
|
3128
|
+
}
|
|
3129
|
+
if (token = this.tokenizer.codespan(src)) {
|
|
3130
|
+
src = src.substring(token.raw.length);
|
|
3131
|
+
tokens.push(token);
|
|
3132
|
+
continue;
|
|
3133
|
+
}
|
|
3134
|
+
if (token = this.tokenizer.br(src)) {
|
|
3135
|
+
src = src.substring(token.raw.length);
|
|
3136
|
+
tokens.push(token);
|
|
3137
|
+
continue;
|
|
3138
|
+
}
|
|
3139
|
+
if (token = this.tokenizer.del(src)) {
|
|
3140
|
+
src = src.substring(token.raw.length);
|
|
3141
|
+
tokens.push(token);
|
|
3142
|
+
continue;
|
|
3143
|
+
}
|
|
3144
|
+
if (token = this.tokenizer.autolink(src)) {
|
|
3145
|
+
src = src.substring(token.raw.length);
|
|
3146
|
+
tokens.push(token);
|
|
3147
|
+
continue;
|
|
3148
|
+
}
|
|
3149
|
+
if (!this.state.inLink && (token = this.tokenizer.url(src))) {
|
|
3150
|
+
src = src.substring(token.raw.length);
|
|
3151
|
+
tokens.push(token);
|
|
3152
|
+
continue;
|
|
3153
|
+
}
|
|
3154
|
+
cutSrc = src;
|
|
3155
|
+
if (this.options.extensions && this.options.extensions.startInline) {
|
|
3156
|
+
let startIndex = Infinity;
|
|
3157
|
+
const tempSrc = src.slice(1);
|
|
3158
|
+
let tempStart;
|
|
3159
|
+
this.options.extensions.startInline.forEach((getStartIndex) => {
|
|
3160
|
+
tempStart = getStartIndex.call({ lexer: this }, tempSrc);
|
|
3161
|
+
if (typeof tempStart === "number" && tempStart >= 0) {
|
|
3162
|
+
startIndex = Math.min(startIndex, tempStart);
|
|
3163
|
+
}
|
|
3164
|
+
});
|
|
3165
|
+
if (startIndex < Infinity && startIndex >= 0) {
|
|
3166
|
+
cutSrc = src.substring(0, startIndex + 1);
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
if (token = this.tokenizer.inlineText(cutSrc)) {
|
|
3170
|
+
src = src.substring(token.raw.length);
|
|
3171
|
+
if (token.raw.slice(-1) !== "_") {
|
|
3172
|
+
prevChar = token.raw.slice(-1);
|
|
3173
|
+
}
|
|
3174
|
+
keepPrevChar = true;
|
|
3175
|
+
lastToken = tokens[tokens.length - 1];
|
|
3176
|
+
if (lastToken && lastToken.type === "text") {
|
|
3177
|
+
lastToken.raw += token.raw;
|
|
3178
|
+
lastToken.text += token.text;
|
|
3179
|
+
} else {
|
|
3180
|
+
tokens.push(token);
|
|
3181
|
+
}
|
|
3182
|
+
continue;
|
|
3183
|
+
}
|
|
3184
|
+
if (src) {
|
|
3185
|
+
const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
|
|
3186
|
+
if (this.options.silent) {
|
|
3187
|
+
console.error(errMsg);
|
|
3188
|
+
break;
|
|
3189
|
+
} else {
|
|
3190
|
+
throw new Error(errMsg);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
return tokens;
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
var _Renderer = class {
|
|
3198
|
+
options;
|
|
3199
|
+
parser;
|
|
3200
|
+
// set by the parser
|
|
3201
|
+
constructor(options2) {
|
|
3202
|
+
this.options = options2 || _defaults;
|
|
3203
|
+
}
|
|
3204
|
+
space(token) {
|
|
3205
|
+
return "";
|
|
3206
|
+
}
|
|
3207
|
+
code({ text: text2, lang, escaped }) {
|
|
3208
|
+
const langString = (lang || "").match(/^\S*/)?.[0];
|
|
3209
|
+
const code = text2.replace(/\n$/, "") + "\n";
|
|
3210
|
+
if (!langString) {
|
|
3211
|
+
return "<pre><code>" + (escaped ? code : escape$1(code, true)) + "</code></pre>\n";
|
|
3212
|
+
}
|
|
3213
|
+
return '<pre><code class="language-' + escape$1(langString) + '">' + (escaped ? code : escape$1(code, true)) + "</code></pre>\n";
|
|
3214
|
+
}
|
|
3215
|
+
blockquote({ tokens }) {
|
|
3216
|
+
const body = this.parser.parse(tokens);
|
|
3217
|
+
return `<blockquote>
|
|
3218
|
+
${body}</blockquote>
|
|
3219
|
+
`;
|
|
3220
|
+
}
|
|
3221
|
+
html({ text: text2 }) {
|
|
3222
|
+
return text2;
|
|
3223
|
+
}
|
|
3224
|
+
heading({ tokens, depth }) {
|
|
3225
|
+
return `<h${depth}>${this.parser.parseInline(tokens)}</h${depth}>
|
|
3226
|
+
`;
|
|
3227
|
+
}
|
|
3228
|
+
hr(token) {
|
|
3229
|
+
return "<hr>\n";
|
|
3230
|
+
}
|
|
3231
|
+
list(token) {
|
|
3232
|
+
const ordered = token.ordered;
|
|
3233
|
+
const start = token.start;
|
|
3234
|
+
let body = "";
|
|
3235
|
+
for (let j = 0; j < token.items.length; j++) {
|
|
3236
|
+
const item = token.items[j];
|
|
3237
|
+
body += this.listitem(item);
|
|
3238
|
+
}
|
|
3239
|
+
const type = ordered ? "ol" : "ul";
|
|
3240
|
+
const startAttr = ordered && start !== 1 ? ' start="' + start + '"' : "";
|
|
3241
|
+
return "<" + type + startAttr + ">\n" + body + "</" + type + ">\n";
|
|
3242
|
+
}
|
|
3243
|
+
listitem(item) {
|
|
3244
|
+
let itemBody = "";
|
|
3245
|
+
if (item.task) {
|
|
3246
|
+
const checkbox = this.checkbox({ checked: !!item.checked });
|
|
3247
|
+
if (item.loose) {
|
|
3248
|
+
if (item.tokens.length > 0 && item.tokens[0].type === "paragraph") {
|
|
3249
|
+
item.tokens[0].text = checkbox + " " + item.tokens[0].text;
|
|
3250
|
+
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === "text") {
|
|
3251
|
+
item.tokens[0].tokens[0].text = checkbox + " " + item.tokens[0].tokens[0].text;
|
|
3252
|
+
}
|
|
3253
|
+
} else {
|
|
3254
|
+
item.tokens.unshift({
|
|
3255
|
+
type: "text",
|
|
3256
|
+
raw: checkbox + " ",
|
|
3257
|
+
text: checkbox + " "
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3260
|
+
} else {
|
|
3261
|
+
itemBody += checkbox + " ";
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
itemBody += this.parser.parse(item.tokens, !!item.loose);
|
|
3265
|
+
return `<li>${itemBody}</li>
|
|
3266
|
+
`;
|
|
3267
|
+
}
|
|
3268
|
+
checkbox({ checked }) {
|
|
3269
|
+
return "<input " + (checked ? 'checked="" ' : "") + 'disabled="" type="checkbox">';
|
|
3270
|
+
}
|
|
3271
|
+
paragraph({ tokens }) {
|
|
3272
|
+
return `<p>${this.parser.parseInline(tokens)}</p>
|
|
3273
|
+
`;
|
|
3274
|
+
}
|
|
3275
|
+
table(token) {
|
|
3276
|
+
let header = "";
|
|
3277
|
+
let cell = "";
|
|
3278
|
+
for (let j = 0; j < token.header.length; j++) {
|
|
3279
|
+
cell += this.tablecell(token.header[j]);
|
|
3280
|
+
}
|
|
3281
|
+
header += this.tablerow({ text: cell });
|
|
3282
|
+
let body = "";
|
|
3283
|
+
for (let j = 0; j < token.rows.length; j++) {
|
|
3284
|
+
const row = token.rows[j];
|
|
3285
|
+
cell = "";
|
|
3286
|
+
for (let k = 0; k < row.length; k++) {
|
|
3287
|
+
cell += this.tablecell(row[k]);
|
|
3288
|
+
}
|
|
3289
|
+
body += this.tablerow({ text: cell });
|
|
3290
|
+
}
|
|
3291
|
+
if (body)
|
|
3292
|
+
body = `<tbody>${body}</tbody>`;
|
|
3293
|
+
return "<table>\n<thead>\n" + header + "</thead>\n" + body + "</table>\n";
|
|
3294
|
+
}
|
|
3295
|
+
tablerow({ text: text2 }) {
|
|
3296
|
+
return `<tr>
|
|
3297
|
+
${text2}</tr>
|
|
3298
|
+
`;
|
|
3299
|
+
}
|
|
3300
|
+
tablecell(token) {
|
|
3301
|
+
const content = this.parser.parseInline(token.tokens);
|
|
3302
|
+
const type = token.header ? "th" : "td";
|
|
3303
|
+
const tag2 = token.align ? `<${type} align="${token.align}">` : `<${type}>`;
|
|
3304
|
+
return tag2 + content + `</${type}>
|
|
3305
|
+
`;
|
|
3306
|
+
}
|
|
3307
|
+
/**
|
|
3308
|
+
* span level renderer
|
|
3309
|
+
*/
|
|
3310
|
+
strong({ tokens }) {
|
|
3311
|
+
return `<strong>${this.parser.parseInline(tokens)}</strong>`;
|
|
3312
|
+
}
|
|
3313
|
+
em({ tokens }) {
|
|
3314
|
+
return `<em>${this.parser.parseInline(tokens)}</em>`;
|
|
3315
|
+
}
|
|
3316
|
+
codespan({ text: text2 }) {
|
|
3317
|
+
return `<code>${text2}</code>`;
|
|
3318
|
+
}
|
|
3319
|
+
br(token) {
|
|
3320
|
+
return "<br>";
|
|
3321
|
+
}
|
|
3322
|
+
del({ tokens }) {
|
|
3323
|
+
return `<del>${this.parser.parseInline(tokens)}</del>`;
|
|
3324
|
+
}
|
|
3325
|
+
link({ href, title, tokens }) {
|
|
3326
|
+
const text2 = this.parser.parseInline(tokens);
|
|
3327
|
+
const cleanHref = cleanUrl(href);
|
|
3328
|
+
if (cleanHref === null) {
|
|
3329
|
+
return text2;
|
|
3330
|
+
}
|
|
3331
|
+
href = cleanHref;
|
|
3332
|
+
let out = '<a href="' + href + '"';
|
|
3333
|
+
if (title) {
|
|
3334
|
+
out += ' title="' + title + '"';
|
|
3335
|
+
}
|
|
3336
|
+
out += ">" + text2 + "</a>";
|
|
3337
|
+
return out;
|
|
3338
|
+
}
|
|
3339
|
+
image({ href, title, text: text2 }) {
|
|
3340
|
+
const cleanHref = cleanUrl(href);
|
|
3341
|
+
if (cleanHref === null) {
|
|
3342
|
+
return text2;
|
|
3343
|
+
}
|
|
3344
|
+
href = cleanHref;
|
|
3345
|
+
let out = `<img src="${href}" alt="${text2}"`;
|
|
3346
|
+
if (title) {
|
|
3347
|
+
out += ` title="${title}"`;
|
|
3348
|
+
}
|
|
3349
|
+
out += ">";
|
|
3350
|
+
return out;
|
|
3351
|
+
}
|
|
3352
|
+
text(token) {
|
|
3353
|
+
return "tokens" in token && token.tokens ? this.parser.parseInline(token.tokens) : token.text;
|
|
3354
|
+
}
|
|
3355
|
+
};
|
|
3356
|
+
var _TextRenderer = class {
|
|
3357
|
+
// no need for block level renderers
|
|
3358
|
+
strong({ text: text2 }) {
|
|
3359
|
+
return text2;
|
|
3360
|
+
}
|
|
3361
|
+
em({ text: text2 }) {
|
|
3362
|
+
return text2;
|
|
3363
|
+
}
|
|
3364
|
+
codespan({ text: text2 }) {
|
|
3365
|
+
return text2;
|
|
3366
|
+
}
|
|
3367
|
+
del({ text: text2 }) {
|
|
3368
|
+
return text2;
|
|
3369
|
+
}
|
|
3370
|
+
html({ text: text2 }) {
|
|
3371
|
+
return text2;
|
|
3372
|
+
}
|
|
3373
|
+
text({ text: text2 }) {
|
|
3374
|
+
return text2;
|
|
3375
|
+
}
|
|
3376
|
+
link({ text: text2 }) {
|
|
3377
|
+
return "" + text2;
|
|
3378
|
+
}
|
|
3379
|
+
image({ text: text2 }) {
|
|
3380
|
+
return "" + text2;
|
|
3381
|
+
}
|
|
3382
|
+
br() {
|
|
3383
|
+
return "";
|
|
3384
|
+
}
|
|
3385
|
+
};
|
|
3386
|
+
var _Parser = class __Parser {
|
|
3387
|
+
options;
|
|
3388
|
+
renderer;
|
|
3389
|
+
textRenderer;
|
|
3390
|
+
constructor(options2) {
|
|
3391
|
+
this.options = options2 || _defaults;
|
|
3392
|
+
this.options.renderer = this.options.renderer || new _Renderer();
|
|
3393
|
+
this.renderer = this.options.renderer;
|
|
3394
|
+
this.renderer.options = this.options;
|
|
3395
|
+
this.renderer.parser = this;
|
|
3396
|
+
this.textRenderer = new _TextRenderer();
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* Static Parse Method
|
|
3400
|
+
*/
|
|
3401
|
+
static parse(tokens, options2) {
|
|
3402
|
+
const parser2 = new __Parser(options2);
|
|
3403
|
+
return parser2.parse(tokens);
|
|
3404
|
+
}
|
|
3405
|
+
/**
|
|
3406
|
+
* Static Parse Inline Method
|
|
3407
|
+
*/
|
|
3408
|
+
static parseInline(tokens, options2) {
|
|
3409
|
+
const parser2 = new __Parser(options2);
|
|
3410
|
+
return parser2.parseInline(tokens);
|
|
3411
|
+
}
|
|
3412
|
+
/**
|
|
3413
|
+
* Parse Loop
|
|
3414
|
+
*/
|
|
3415
|
+
parse(tokens, top = true) {
|
|
3416
|
+
let out = "";
|
|
3417
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
3418
|
+
const anyToken = tokens[i];
|
|
3419
|
+
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[anyToken.type]) {
|
|
3420
|
+
const genericToken = anyToken;
|
|
3421
|
+
const ret = this.options.extensions.renderers[genericToken.type].call({ parser: this }, genericToken);
|
|
3422
|
+
if (ret !== false || !["space", "hr", "heading", "code", "table", "blockquote", "list", "html", "paragraph", "text"].includes(genericToken.type)) {
|
|
3423
|
+
out += ret || "";
|
|
3424
|
+
continue;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
const token = anyToken;
|
|
3428
|
+
switch (token.type) {
|
|
3429
|
+
case "space": {
|
|
3430
|
+
out += this.renderer.space(token);
|
|
3431
|
+
continue;
|
|
3432
|
+
}
|
|
3433
|
+
case "hr": {
|
|
3434
|
+
out += this.renderer.hr(token);
|
|
3435
|
+
continue;
|
|
3436
|
+
}
|
|
3437
|
+
case "heading": {
|
|
3438
|
+
out += this.renderer.heading(token);
|
|
3439
|
+
continue;
|
|
3440
|
+
}
|
|
3441
|
+
case "code": {
|
|
3442
|
+
out += this.renderer.code(token);
|
|
3443
|
+
continue;
|
|
3444
|
+
}
|
|
3445
|
+
case "table": {
|
|
3446
|
+
out += this.renderer.table(token);
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
case "blockquote": {
|
|
3450
|
+
out += this.renderer.blockquote(token);
|
|
3451
|
+
continue;
|
|
3452
|
+
}
|
|
3453
|
+
case "list": {
|
|
3454
|
+
out += this.renderer.list(token);
|
|
3455
|
+
continue;
|
|
3456
|
+
}
|
|
3457
|
+
case "html": {
|
|
3458
|
+
out += this.renderer.html(token);
|
|
3459
|
+
continue;
|
|
3460
|
+
}
|
|
3461
|
+
case "paragraph": {
|
|
3462
|
+
out += this.renderer.paragraph(token);
|
|
3463
|
+
continue;
|
|
3464
|
+
}
|
|
3465
|
+
case "text": {
|
|
3466
|
+
let textToken = token;
|
|
3467
|
+
let body = this.renderer.text(textToken);
|
|
3468
|
+
while (i + 1 < tokens.length && tokens[i + 1].type === "text") {
|
|
3469
|
+
textToken = tokens[++i];
|
|
3470
|
+
body += "\n" + this.renderer.text(textToken);
|
|
3471
|
+
}
|
|
3472
|
+
if (top) {
|
|
3473
|
+
out += this.renderer.paragraph({
|
|
3474
|
+
type: "paragraph",
|
|
3475
|
+
raw: body,
|
|
3476
|
+
text: body,
|
|
3477
|
+
tokens: [{ type: "text", raw: body, text: body }]
|
|
3478
|
+
});
|
|
3479
|
+
} else {
|
|
3480
|
+
out += body;
|
|
3481
|
+
}
|
|
3482
|
+
continue;
|
|
3483
|
+
}
|
|
3484
|
+
default: {
|
|
3485
|
+
const errMsg = 'Token with "' + token.type + '" type was not found.';
|
|
3486
|
+
if (this.options.silent) {
|
|
3487
|
+
console.error(errMsg);
|
|
3488
|
+
return "";
|
|
3489
|
+
} else {
|
|
3490
|
+
throw new Error(errMsg);
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
return out;
|
|
3496
|
+
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Parse Inline Tokens
|
|
3499
|
+
*/
|
|
3500
|
+
parseInline(tokens, renderer) {
|
|
3501
|
+
renderer = renderer || this.renderer;
|
|
3502
|
+
let out = "";
|
|
3503
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
3504
|
+
const anyToken = tokens[i];
|
|
3505
|
+
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[anyToken.type]) {
|
|
3506
|
+
const ret = this.options.extensions.renderers[anyToken.type].call({ parser: this }, anyToken);
|
|
3507
|
+
if (ret !== false || !["escape", "html", "link", "image", "strong", "em", "codespan", "br", "del", "text"].includes(anyToken.type)) {
|
|
3508
|
+
out += ret || "";
|
|
3509
|
+
continue;
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
const token = anyToken;
|
|
3513
|
+
switch (token.type) {
|
|
3514
|
+
case "escape": {
|
|
3515
|
+
out += renderer.text(token);
|
|
3516
|
+
break;
|
|
3517
|
+
}
|
|
3518
|
+
case "html": {
|
|
3519
|
+
out += renderer.html(token);
|
|
3520
|
+
break;
|
|
3521
|
+
}
|
|
3522
|
+
case "link": {
|
|
3523
|
+
out += renderer.link(token);
|
|
3524
|
+
break;
|
|
3525
|
+
}
|
|
3526
|
+
case "image": {
|
|
3527
|
+
out += renderer.image(token);
|
|
3528
|
+
break;
|
|
3529
|
+
}
|
|
3530
|
+
case "strong": {
|
|
3531
|
+
out += renderer.strong(token);
|
|
3532
|
+
break;
|
|
3533
|
+
}
|
|
3534
|
+
case "em": {
|
|
3535
|
+
out += renderer.em(token);
|
|
3536
|
+
break;
|
|
3537
|
+
}
|
|
3538
|
+
case "codespan": {
|
|
3539
|
+
out += renderer.codespan(token);
|
|
3540
|
+
break;
|
|
3541
|
+
}
|
|
3542
|
+
case "br": {
|
|
3543
|
+
out += renderer.br(token);
|
|
3544
|
+
break;
|
|
3545
|
+
}
|
|
3546
|
+
case "del": {
|
|
3547
|
+
out += renderer.del(token);
|
|
3548
|
+
break;
|
|
3549
|
+
}
|
|
3550
|
+
case "text": {
|
|
3551
|
+
out += renderer.text(token);
|
|
3552
|
+
break;
|
|
3553
|
+
}
|
|
3554
|
+
default: {
|
|
3555
|
+
const errMsg = 'Token with "' + token.type + '" type was not found.';
|
|
3556
|
+
if (this.options.silent) {
|
|
3557
|
+
console.error(errMsg);
|
|
3558
|
+
return "";
|
|
3559
|
+
} else {
|
|
3560
|
+
throw new Error(errMsg);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
return out;
|
|
3566
|
+
}
|
|
3567
|
+
};
|
|
3568
|
+
var _Hooks = class {
|
|
3569
|
+
options;
|
|
3570
|
+
block;
|
|
3571
|
+
constructor(options2) {
|
|
3572
|
+
this.options = options2 || _defaults;
|
|
3573
|
+
}
|
|
3574
|
+
static passThroughHooks = /* @__PURE__ */ new Set([
|
|
3575
|
+
"preprocess",
|
|
3576
|
+
"postprocess",
|
|
3577
|
+
"processAllTokens"
|
|
3578
|
+
]);
|
|
3579
|
+
/**
|
|
3580
|
+
* Process markdown before marked
|
|
3581
|
+
*/
|
|
3582
|
+
preprocess(markdown) {
|
|
3583
|
+
return markdown;
|
|
3584
|
+
}
|
|
3585
|
+
/**
|
|
3586
|
+
* Process HTML after marked is finished
|
|
3587
|
+
*/
|
|
3588
|
+
postprocess(html3) {
|
|
3589
|
+
return html3;
|
|
3590
|
+
}
|
|
3591
|
+
/**
|
|
3592
|
+
* Process all tokens before walk tokens
|
|
3593
|
+
*/
|
|
3594
|
+
processAllTokens(tokens) {
|
|
3595
|
+
return tokens;
|
|
3596
|
+
}
|
|
3597
|
+
/**
|
|
3598
|
+
* Provide function to tokenize markdown
|
|
3599
|
+
*/
|
|
3600
|
+
provideLexer() {
|
|
3601
|
+
return this.block ? _Lexer.lex : _Lexer.lexInline;
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Provide function to parse tokens
|
|
3605
|
+
*/
|
|
3606
|
+
provideParser() {
|
|
3607
|
+
return this.block ? _Parser.parse : _Parser.parseInline;
|
|
3608
|
+
}
|
|
3609
|
+
};
|
|
3610
|
+
var Marked = class {
|
|
3611
|
+
defaults = _getDefaults();
|
|
3612
|
+
options = this.setOptions;
|
|
3613
|
+
parse = this.parseMarkdown(true);
|
|
3614
|
+
parseInline = this.parseMarkdown(false);
|
|
3615
|
+
Parser = _Parser;
|
|
3616
|
+
Renderer = _Renderer;
|
|
3617
|
+
TextRenderer = _TextRenderer;
|
|
3618
|
+
Lexer = _Lexer;
|
|
3619
|
+
Tokenizer = _Tokenizer;
|
|
3620
|
+
Hooks = _Hooks;
|
|
3621
|
+
constructor(...args) {
|
|
3622
|
+
this.use(...args);
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* Run callback for every token
|
|
3626
|
+
*/
|
|
3627
|
+
walkTokens(tokens, callback) {
|
|
3628
|
+
let values = [];
|
|
3629
|
+
for (const token of tokens) {
|
|
3630
|
+
values = values.concat(callback.call(this, token));
|
|
3631
|
+
switch (token.type) {
|
|
3632
|
+
case "table": {
|
|
3633
|
+
const tableToken = token;
|
|
3634
|
+
for (const cell of tableToken.header) {
|
|
3635
|
+
values = values.concat(this.walkTokens(cell.tokens, callback));
|
|
3636
|
+
}
|
|
3637
|
+
for (const row of tableToken.rows) {
|
|
3638
|
+
for (const cell of row) {
|
|
3639
|
+
values = values.concat(this.walkTokens(cell.tokens, callback));
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
break;
|
|
3643
|
+
}
|
|
3644
|
+
case "list": {
|
|
3645
|
+
const listToken = token;
|
|
3646
|
+
values = values.concat(this.walkTokens(listToken.items, callback));
|
|
3647
|
+
break;
|
|
3648
|
+
}
|
|
3649
|
+
default: {
|
|
3650
|
+
const genericToken = token;
|
|
3651
|
+
if (this.defaults.extensions?.childTokens?.[genericToken.type]) {
|
|
3652
|
+
this.defaults.extensions.childTokens[genericToken.type].forEach((childTokens) => {
|
|
3653
|
+
const tokens2 = genericToken[childTokens].flat(Infinity);
|
|
3654
|
+
values = values.concat(this.walkTokens(tokens2, callback));
|
|
3655
|
+
});
|
|
3656
|
+
} else if (genericToken.tokens) {
|
|
3657
|
+
values = values.concat(this.walkTokens(genericToken.tokens, callback));
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
return values;
|
|
3663
|
+
}
|
|
3664
|
+
use(...args) {
|
|
3665
|
+
const extensions = this.defaults.extensions || { renderers: {}, childTokens: {} };
|
|
3666
|
+
args.forEach((pack) => {
|
|
3667
|
+
const opts = { ...pack };
|
|
3668
|
+
opts.async = this.defaults.async || opts.async || false;
|
|
3669
|
+
if (pack.extensions) {
|
|
3670
|
+
pack.extensions.forEach((ext) => {
|
|
3671
|
+
if (!ext.name) {
|
|
3672
|
+
throw new Error("extension name required");
|
|
3673
|
+
}
|
|
3674
|
+
if ("renderer" in ext) {
|
|
3675
|
+
const prevRenderer = extensions.renderers[ext.name];
|
|
3676
|
+
if (prevRenderer) {
|
|
3677
|
+
extensions.renderers[ext.name] = function(...args2) {
|
|
3678
|
+
let ret = ext.renderer.apply(this, args2);
|
|
3679
|
+
if (ret === false) {
|
|
3680
|
+
ret = prevRenderer.apply(this, args2);
|
|
3681
|
+
}
|
|
3682
|
+
return ret;
|
|
3683
|
+
};
|
|
3684
|
+
} else {
|
|
3685
|
+
extensions.renderers[ext.name] = ext.renderer;
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
if ("tokenizer" in ext) {
|
|
3689
|
+
if (!ext.level || ext.level !== "block" && ext.level !== "inline") {
|
|
3690
|
+
throw new Error("extension level must be 'block' or 'inline'");
|
|
3691
|
+
}
|
|
3692
|
+
const extLevel = extensions[ext.level];
|
|
3693
|
+
if (extLevel) {
|
|
3694
|
+
extLevel.unshift(ext.tokenizer);
|
|
3695
|
+
} else {
|
|
3696
|
+
extensions[ext.level] = [ext.tokenizer];
|
|
3697
|
+
}
|
|
3698
|
+
if (ext.start) {
|
|
3699
|
+
if (ext.level === "block") {
|
|
3700
|
+
if (extensions.startBlock) {
|
|
3701
|
+
extensions.startBlock.push(ext.start);
|
|
3702
|
+
} else {
|
|
3703
|
+
extensions.startBlock = [ext.start];
|
|
3704
|
+
}
|
|
3705
|
+
} else if (ext.level === "inline") {
|
|
3706
|
+
if (extensions.startInline) {
|
|
3707
|
+
extensions.startInline.push(ext.start);
|
|
3708
|
+
} else {
|
|
3709
|
+
extensions.startInline = [ext.start];
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
if ("childTokens" in ext && ext.childTokens) {
|
|
3715
|
+
extensions.childTokens[ext.name] = ext.childTokens;
|
|
3716
|
+
}
|
|
3717
|
+
});
|
|
3718
|
+
opts.extensions = extensions;
|
|
3719
|
+
}
|
|
3720
|
+
if (pack.renderer) {
|
|
3721
|
+
const renderer = this.defaults.renderer || new _Renderer(this.defaults);
|
|
3722
|
+
for (const prop in pack.renderer) {
|
|
3723
|
+
if (!(prop in renderer)) {
|
|
3724
|
+
throw new Error(`renderer '${prop}' does not exist`);
|
|
3725
|
+
}
|
|
3726
|
+
if (["options", "parser"].includes(prop)) {
|
|
3727
|
+
continue;
|
|
3728
|
+
}
|
|
3729
|
+
const rendererProp = prop;
|
|
3730
|
+
const rendererFunc = pack.renderer[rendererProp];
|
|
3731
|
+
const prevRenderer = renderer[rendererProp];
|
|
3732
|
+
renderer[rendererProp] = (...args2) => {
|
|
3733
|
+
let ret = rendererFunc.apply(renderer, args2);
|
|
3734
|
+
if (ret === false) {
|
|
3735
|
+
ret = prevRenderer.apply(renderer, args2);
|
|
3736
|
+
}
|
|
3737
|
+
return ret || "";
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
opts.renderer = renderer;
|
|
3741
|
+
}
|
|
3742
|
+
if (pack.tokenizer) {
|
|
3743
|
+
const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults);
|
|
3744
|
+
for (const prop in pack.tokenizer) {
|
|
3745
|
+
if (!(prop in tokenizer)) {
|
|
3746
|
+
throw new Error(`tokenizer '${prop}' does not exist`);
|
|
3747
|
+
}
|
|
3748
|
+
if (["options", "rules", "lexer"].includes(prop)) {
|
|
3749
|
+
continue;
|
|
3750
|
+
}
|
|
3751
|
+
const tokenizerProp = prop;
|
|
3752
|
+
const tokenizerFunc = pack.tokenizer[tokenizerProp];
|
|
3753
|
+
const prevTokenizer = tokenizer[tokenizerProp];
|
|
3754
|
+
tokenizer[tokenizerProp] = (...args2) => {
|
|
3755
|
+
let ret = tokenizerFunc.apply(tokenizer, args2);
|
|
3756
|
+
if (ret === false) {
|
|
3757
|
+
ret = prevTokenizer.apply(tokenizer, args2);
|
|
3758
|
+
}
|
|
3759
|
+
return ret;
|
|
3760
|
+
};
|
|
3761
|
+
}
|
|
3762
|
+
opts.tokenizer = tokenizer;
|
|
3763
|
+
}
|
|
3764
|
+
if (pack.hooks) {
|
|
3765
|
+
const hooks = this.defaults.hooks || new _Hooks();
|
|
3766
|
+
for (const prop in pack.hooks) {
|
|
3767
|
+
if (!(prop in hooks)) {
|
|
3768
|
+
throw new Error(`hook '${prop}' does not exist`);
|
|
3769
|
+
}
|
|
3770
|
+
if (["options", "block"].includes(prop)) {
|
|
3771
|
+
continue;
|
|
3772
|
+
}
|
|
3773
|
+
const hooksProp = prop;
|
|
3774
|
+
const hooksFunc = pack.hooks[hooksProp];
|
|
3775
|
+
const prevHook = hooks[hooksProp];
|
|
3776
|
+
if (_Hooks.passThroughHooks.has(prop)) {
|
|
3777
|
+
hooks[hooksProp] = (arg) => {
|
|
3778
|
+
if (this.defaults.async) {
|
|
3779
|
+
return Promise.resolve(hooksFunc.call(hooks, arg)).then((ret2) => {
|
|
3780
|
+
return prevHook.call(hooks, ret2);
|
|
3781
|
+
});
|
|
3782
|
+
}
|
|
3783
|
+
const ret = hooksFunc.call(hooks, arg);
|
|
3784
|
+
return prevHook.call(hooks, ret);
|
|
3785
|
+
};
|
|
3786
|
+
} else {
|
|
3787
|
+
hooks[hooksProp] = (...args2) => {
|
|
3788
|
+
let ret = hooksFunc.apply(hooks, args2);
|
|
3789
|
+
if (ret === false) {
|
|
3790
|
+
ret = prevHook.apply(hooks, args2);
|
|
3791
|
+
}
|
|
3792
|
+
return ret;
|
|
3793
|
+
};
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
opts.hooks = hooks;
|
|
3797
|
+
}
|
|
3798
|
+
if (pack.walkTokens) {
|
|
3799
|
+
const walkTokens2 = this.defaults.walkTokens;
|
|
3800
|
+
const packWalktokens = pack.walkTokens;
|
|
3801
|
+
opts.walkTokens = function(token) {
|
|
3802
|
+
let values = [];
|
|
3803
|
+
values.push(packWalktokens.call(this, token));
|
|
3804
|
+
if (walkTokens2) {
|
|
3805
|
+
values = values.concat(walkTokens2.call(this, token));
|
|
3806
|
+
}
|
|
3807
|
+
return values;
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3810
|
+
this.defaults = { ...this.defaults, ...opts };
|
|
3811
|
+
});
|
|
3812
|
+
return this;
|
|
3813
|
+
}
|
|
3814
|
+
setOptions(opt) {
|
|
3815
|
+
this.defaults = { ...this.defaults, ...opt };
|
|
3816
|
+
return this;
|
|
3817
|
+
}
|
|
3818
|
+
lexer(src, options2) {
|
|
3819
|
+
return _Lexer.lex(src, options2 ?? this.defaults);
|
|
3820
|
+
}
|
|
3821
|
+
parser(tokens, options2) {
|
|
3822
|
+
return _Parser.parse(tokens, options2 ?? this.defaults);
|
|
3823
|
+
}
|
|
3824
|
+
parseMarkdown(blockType) {
|
|
3825
|
+
const parse = (src, options2) => {
|
|
3826
|
+
const origOpt = { ...options2 };
|
|
3827
|
+
const opt = { ...this.defaults, ...origOpt };
|
|
3828
|
+
const throwError = this.onError(!!opt.silent, !!opt.async);
|
|
3829
|
+
if (this.defaults.async === true && origOpt.async === false) {
|
|
3830
|
+
return throwError(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));
|
|
3831
|
+
}
|
|
3832
|
+
if (typeof src === "undefined" || src === null) {
|
|
3833
|
+
return throwError(new Error("marked(): input parameter is undefined or null"));
|
|
3834
|
+
}
|
|
3835
|
+
if (typeof src !== "string") {
|
|
3836
|
+
return throwError(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(src) + ", string expected"));
|
|
3837
|
+
}
|
|
3838
|
+
if (opt.hooks) {
|
|
3839
|
+
opt.hooks.options = opt;
|
|
3840
|
+
opt.hooks.block = blockType;
|
|
3841
|
+
}
|
|
3842
|
+
const lexer2 = opt.hooks ? opt.hooks.provideLexer() : blockType ? _Lexer.lex : _Lexer.lexInline;
|
|
3843
|
+
const parser2 = opt.hooks ? opt.hooks.provideParser() : blockType ? _Parser.parse : _Parser.parseInline;
|
|
3844
|
+
if (opt.async) {
|
|
3845
|
+
return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src).then((src2) => lexer2(src2, opt)).then((tokens) => opt.hooks ? opt.hooks.processAllTokens(tokens) : tokens).then((tokens) => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens).then((tokens) => parser2(tokens, opt)).then((html3) => opt.hooks ? opt.hooks.postprocess(html3) : html3).catch(throwError);
|
|
3846
|
+
}
|
|
3847
|
+
try {
|
|
3848
|
+
if (opt.hooks) {
|
|
3849
|
+
src = opt.hooks.preprocess(src);
|
|
3850
|
+
}
|
|
3851
|
+
let tokens = lexer2(src, opt);
|
|
3852
|
+
if (opt.hooks) {
|
|
3853
|
+
tokens = opt.hooks.processAllTokens(tokens);
|
|
3854
|
+
}
|
|
3855
|
+
if (opt.walkTokens) {
|
|
3856
|
+
this.walkTokens(tokens, opt.walkTokens);
|
|
3857
|
+
}
|
|
3858
|
+
let html3 = parser2(tokens, opt);
|
|
3859
|
+
if (opt.hooks) {
|
|
3860
|
+
html3 = opt.hooks.postprocess(html3);
|
|
3861
|
+
}
|
|
3862
|
+
return html3;
|
|
3863
|
+
} catch (e) {
|
|
3864
|
+
return throwError(e);
|
|
3865
|
+
}
|
|
3866
|
+
};
|
|
3867
|
+
return parse;
|
|
3868
|
+
}
|
|
3869
|
+
onError(silent, async) {
|
|
3870
|
+
return (e) => {
|
|
3871
|
+
e.message += "\nPlease report this to https://github.com/markedjs/marked.";
|
|
3872
|
+
if (silent) {
|
|
3873
|
+
const msg = "<p>An error occurred:</p><pre>" + escape$1(e.message + "", true) + "</pre>";
|
|
3874
|
+
if (async) {
|
|
3875
|
+
return Promise.resolve(msg);
|
|
3876
|
+
}
|
|
3877
|
+
return msg;
|
|
3878
|
+
}
|
|
3879
|
+
if (async) {
|
|
3880
|
+
return Promise.reject(e);
|
|
3881
|
+
}
|
|
3882
|
+
throw e;
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
};
|
|
3886
|
+
var markedInstance = new Marked();
|
|
3887
|
+
function marked(src, opt) {
|
|
3888
|
+
return markedInstance.parse(src, opt);
|
|
3889
|
+
}
|
|
3890
|
+
marked.options = marked.setOptions = function(options2) {
|
|
3891
|
+
markedInstance.setOptions(options2);
|
|
3892
|
+
marked.defaults = markedInstance.defaults;
|
|
3893
|
+
changeDefaults(marked.defaults);
|
|
3894
|
+
return marked;
|
|
3895
|
+
};
|
|
3896
|
+
marked.getDefaults = _getDefaults;
|
|
3897
|
+
marked.defaults = _defaults;
|
|
3898
|
+
marked.use = function(...args) {
|
|
3899
|
+
markedInstance.use(...args);
|
|
3900
|
+
marked.defaults = markedInstance.defaults;
|
|
3901
|
+
changeDefaults(marked.defaults);
|
|
3902
|
+
return marked;
|
|
3903
|
+
};
|
|
3904
|
+
marked.walkTokens = function(tokens, callback) {
|
|
3905
|
+
return markedInstance.walkTokens(tokens, callback);
|
|
3906
|
+
};
|
|
3907
|
+
marked.parseInline = markedInstance.parseInline;
|
|
3908
|
+
marked.Parser = _Parser;
|
|
3909
|
+
marked.parser = _Parser.parse;
|
|
3910
|
+
marked.Renderer = _Renderer;
|
|
3911
|
+
marked.TextRenderer = _TextRenderer;
|
|
3912
|
+
marked.Lexer = _Lexer;
|
|
3913
|
+
marked.lexer = _Lexer.lex;
|
|
3914
|
+
marked.Tokenizer = _Tokenizer;
|
|
3915
|
+
marked.Hooks = _Hooks;
|
|
3916
|
+
marked.parse = marked;
|
|
3917
|
+
var options = marked.options;
|
|
3918
|
+
var setOptions = marked.setOptions;
|
|
3919
|
+
var use = marked.use;
|
|
3920
|
+
var walkTokens = marked.walkTokens;
|
|
3921
|
+
var parseInline = marked.parseInline;
|
|
3922
|
+
var parser = _Parser.parse;
|
|
3923
|
+
var lexer = _Lexer.lex;
|
|
3924
|
+
|
|
3925
|
+
// src/components/markdown-editor.ts
|
|
3926
|
+
marked.setOptions({ gfm: true });
|
|
3927
|
+
var TASK_LIST_LINE_RE = /^([\s>]*[-*+]\s+\[)([ xX])(\])/;
|
|
3928
|
+
function toggleNthTaskLine(body, n, checked) {
|
|
3929
|
+
const lines = body.split("\n");
|
|
3930
|
+
let count = 0;
|
|
3931
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3932
|
+
const line = lines[i] ?? "";
|
|
3933
|
+
const m = TASK_LIST_LINE_RE.exec(line);
|
|
3934
|
+
if (!m) continue;
|
|
3935
|
+
if (count === n) {
|
|
3936
|
+
lines[i] = `${m[1]}${checked ? "x" : " "}${m[3]}${line.slice(m[0].length)}`;
|
|
3937
|
+
return lines.join("\n");
|
|
3938
|
+
}
|
|
3939
|
+
count++;
|
|
3940
|
+
}
|
|
3941
|
+
return null;
|
|
3942
|
+
}
|
|
3943
|
+
function renderMarkdownEditor(opts) {
|
|
3944
|
+
const container = document.createElement("div");
|
|
3945
|
+
container.className = "zg-md-editor";
|
|
3946
|
+
let currentBody = opts.body ?? "";
|
|
3947
|
+
let mode = "view";
|
|
3948
|
+
let pendingTemplate = null;
|
|
3949
|
+
function render() {
|
|
3950
|
+
container.innerHTML = "";
|
|
3951
|
+
if (mode === "view") {
|
|
3952
|
+
container.appendChild(viewMode());
|
|
3953
|
+
} else {
|
|
3954
|
+
container.appendChild(editMode());
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
function enterEditMode(template) {
|
|
3958
|
+
pendingTemplate = template;
|
|
3959
|
+
mode = "edit";
|
|
3960
|
+
render();
|
|
3961
|
+
}
|
|
3962
|
+
function viewMode() {
|
|
3963
|
+
const wrap = document.createElement("div");
|
|
3964
|
+
wrap.className = "zg-md-view";
|
|
3965
|
+
if (currentBody.trim()) {
|
|
3966
|
+
const body = document.createElement("div");
|
|
3967
|
+
body.className = "zg-markdown";
|
|
3968
|
+
body.innerHTML = sanitizeHtml(marked.parse(currentBody));
|
|
3969
|
+
if (opts.interactiveCheckboxes) wireInteractiveCheckboxes(body);
|
|
3970
|
+
opts.postRender?.(body);
|
|
3971
|
+
wrap.appendChild(body);
|
|
3972
|
+
const editBtn = document.createElement("button");
|
|
3973
|
+
editBtn.type = "button";
|
|
3974
|
+
editBtn.className = "zg-md-edit-btn";
|
|
3975
|
+
editBtn.textContent = "\u270E Edit";
|
|
3976
|
+
editBtn.addEventListener("click", () => enterEditMode(null));
|
|
3977
|
+
wrap.appendChild(editBtn);
|
|
3978
|
+
} else {
|
|
3979
|
+
wrap.appendChild(renderEmpty());
|
|
3980
|
+
}
|
|
3981
|
+
return wrap;
|
|
3982
|
+
}
|
|
3983
|
+
function wireInteractiveCheckboxes(body) {
|
|
3984
|
+
const inputs = Array.from(body.querySelectorAll('input[type="checkbox"]'));
|
|
3985
|
+
inputs.forEach((input, idx) => {
|
|
3986
|
+
input.removeAttribute("disabled");
|
|
3987
|
+
input.disabled = false;
|
|
3988
|
+
input.style.cursor = "pointer";
|
|
3989
|
+
input.style.pointerEvents = "auto";
|
|
3990
|
+
async function commit(desired) {
|
|
3991
|
+
const nextBody = toggleNthTaskLine(currentBody, idx, desired);
|
|
3992
|
+
if (nextBody === null) {
|
|
3993
|
+
input.checked = !desired;
|
|
3994
|
+
return;
|
|
3995
|
+
}
|
|
3996
|
+
input.disabled = true;
|
|
3997
|
+
try {
|
|
3998
|
+
await opts.onSave(nextBody);
|
|
3999
|
+
currentBody = nextBody;
|
|
4000
|
+
input.checked = desired;
|
|
4001
|
+
} catch (err) {
|
|
4002
|
+
input.checked = !desired;
|
|
4003
|
+
void showAlert("Save failed", err.message);
|
|
4004
|
+
} finally {
|
|
4005
|
+
input.removeAttribute("disabled");
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
input.addEventListener("change", () => void commit(input.checked));
|
|
4009
|
+
const li = input.closest("li");
|
|
4010
|
+
if (li) {
|
|
4011
|
+
li.style.cursor = "pointer";
|
|
4012
|
+
li.addEventListener("click", (e) => {
|
|
4013
|
+
if (e.target instanceof HTMLInputElement) return;
|
|
4014
|
+
if (e.target instanceof HTMLAnchorElement) return;
|
|
4015
|
+
e.preventDefault();
|
|
4016
|
+
const next = !input.checked;
|
|
4017
|
+
input.checked = next;
|
|
4018
|
+
void commit(next);
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
});
|
|
4022
|
+
}
|
|
4023
|
+
function renderEmpty() {
|
|
4024
|
+
const empty = document.createElement("div");
|
|
4025
|
+
empty.className = "zg-md-empty";
|
|
4026
|
+
const title = document.createElement("h3");
|
|
4027
|
+
title.textContent = opts.emptyPlaceholder;
|
|
4028
|
+
empty.appendChild(title);
|
|
4029
|
+
if (opts.emptyHint) {
|
|
4030
|
+
const hint = document.createElement("p");
|
|
4031
|
+
hint.className = "zg-md-empty-hint";
|
|
4032
|
+
hint.textContent = opts.emptyHint;
|
|
4033
|
+
empty.appendChild(hint);
|
|
4034
|
+
}
|
|
4035
|
+
const startBtn = document.createElement("button");
|
|
4036
|
+
startBtn.type = "button";
|
|
4037
|
+
startBtn.className = "zg-md-start-btn";
|
|
4038
|
+
startBtn.textContent = opts.startingTemplate ? "\u270E Start writing (with a template)" : "\u270E Start writing";
|
|
4039
|
+
startBtn.addEventListener("click", () => enterEditMode(opts.startingTemplate ?? null));
|
|
4040
|
+
empty.appendChild(startBtn);
|
|
4041
|
+
return empty;
|
|
4042
|
+
}
|
|
4043
|
+
function editMode() {
|
|
4044
|
+
const wrap = document.createElement("div");
|
|
4045
|
+
wrap.className = "zg-md-edit";
|
|
4046
|
+
const textarea = document.createElement("textarea");
|
|
4047
|
+
textarea.className = "zg-md-textarea";
|
|
4048
|
+
const initial = currentBody || pendingTemplate || "";
|
|
4049
|
+
pendingTemplate = null;
|
|
4050
|
+
textarea.value = initial;
|
|
4051
|
+
textarea.rows = Math.max(8, Math.min(30, initial.split("\n").length + 2));
|
|
4052
|
+
wrap.appendChild(textarea);
|
|
4053
|
+
const error = document.createElement("p");
|
|
4054
|
+
error.className = "zg-modal-error";
|
|
4055
|
+
error.style.display = "none";
|
|
4056
|
+
wrap.appendChild(error);
|
|
4057
|
+
const buttons = document.createElement("div");
|
|
4058
|
+
buttons.className = "zg-md-edit-buttons";
|
|
4059
|
+
const cancelBtn = document.createElement("button");
|
|
4060
|
+
cancelBtn.type = "button";
|
|
4061
|
+
cancelBtn.className = "zg-modal-cancel";
|
|
4062
|
+
cancelBtn.textContent = "Cancel";
|
|
4063
|
+
cancelBtn.addEventListener("click", () => {
|
|
4064
|
+
mode = "view";
|
|
4065
|
+
render();
|
|
4066
|
+
});
|
|
4067
|
+
const saveBtn = document.createElement("button");
|
|
4068
|
+
saveBtn.type = "button";
|
|
4069
|
+
saveBtn.className = "zg-modal-confirm";
|
|
4070
|
+
saveBtn.textContent = "Save";
|
|
4071
|
+
saveBtn.addEventListener("click", async () => {
|
|
4072
|
+
const newBody = textarea.value;
|
|
4073
|
+
saveBtn.disabled = true;
|
|
4074
|
+
cancelBtn.disabled = true;
|
|
4075
|
+
try {
|
|
4076
|
+
await opts.onSave(newBody);
|
|
4077
|
+
currentBody = newBody;
|
|
4078
|
+
mode = "view";
|
|
4079
|
+
render();
|
|
4080
|
+
} catch (err) {
|
|
4081
|
+
error.textContent = err.message;
|
|
4082
|
+
error.style.display = "";
|
|
4083
|
+
saveBtn.disabled = false;
|
|
4084
|
+
cancelBtn.disabled = false;
|
|
4085
|
+
}
|
|
4086
|
+
});
|
|
4087
|
+
buttons.appendChild(cancelBtn);
|
|
4088
|
+
buttons.appendChild(saveBtn);
|
|
4089
|
+
wrap.appendChild(buttons);
|
|
4090
|
+
setTimeout(() => textarea.focus(), 0);
|
|
4091
|
+
return wrap;
|
|
4092
|
+
}
|
|
4093
|
+
render();
|
|
4094
|
+
return container;
|
|
4095
|
+
}
|
|
4096
|
+
function splitFrontmatter(text2) {
|
|
4097
|
+
const m = /^(---\r?\n[\s\S]*?\r?\n---\r?\n?)/.exec(text2);
|
|
4098
|
+
if (!m) return { frontmatter: "", body: text2 };
|
|
4099
|
+
return { frontmatter: m[1], body: text2.slice(m[1].length) };
|
|
4100
|
+
}
|
|
4101
|
+
|
|
4102
|
+
// src/util/wiki-links.ts
|
|
4103
|
+
var WIKILINK_RE = /\[\[([^\]\n]+)\]\]/g;
|
|
4104
|
+
var EXCLUDED_TAGS = /* @__PURE__ */ new Set(["CODE", "PRE", "A"]);
|
|
4105
|
+
function collectCandidates(node, out) {
|
|
4106
|
+
for (const child of Array.from(node.childNodes)) {
|
|
4107
|
+
if (child.nodeType === 3) {
|
|
4108
|
+
const text2 = child.textContent ?? "";
|
|
4109
|
+
if (text2.includes("[[")) out.push(child);
|
|
4110
|
+
} else if (child.nodeType === 1) {
|
|
4111
|
+
const tag2 = child.tagName;
|
|
4112
|
+
if (EXCLUDED_TAGS.has(tag2)) continue;
|
|
4113
|
+
collectCandidates(child, out);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
function makeWikiLinkResolver(specNames, docPaths) {
|
|
4118
|
+
const specs = new Set(specNames);
|
|
4119
|
+
const docs = /* @__PURE__ */ new Map();
|
|
4120
|
+
for (const p of docPaths) {
|
|
4121
|
+
const basename = p.replace(/^.*\//, "").replace(/\.md$/, "");
|
|
4122
|
+
docs.set(basename, p);
|
|
4123
|
+
}
|
|
4124
|
+
return (name) => {
|
|
4125
|
+
if (specs.has(name)) return `#/spec/${encodeURIComponent(name)}`;
|
|
4126
|
+
const docPath = docs.get(name);
|
|
4127
|
+
if (docPath) return `#/docs/${encodeURIComponent(docPath)}`;
|
|
4128
|
+
return null;
|
|
4129
|
+
};
|
|
4130
|
+
}
|
|
4131
|
+
function processWikiLinks(root, resolver) {
|
|
4132
|
+
const targets = [];
|
|
4133
|
+
collectCandidates(root, targets);
|
|
4134
|
+
for (const textNode of targets) {
|
|
4135
|
+
const frag = document.createDocumentFragment();
|
|
4136
|
+
const text2 = textNode.textContent ?? "";
|
|
4137
|
+
let lastIdx = 0;
|
|
4138
|
+
const re = new RegExp(WIKILINK_RE.source, "g");
|
|
4139
|
+
let m;
|
|
4140
|
+
while (m = re.exec(text2)) {
|
|
4141
|
+
if (m.index > lastIdx) {
|
|
4142
|
+
frag.appendChild(document.createTextNode(text2.slice(lastIdx, m.index)));
|
|
4143
|
+
}
|
|
4144
|
+
const name = m[1].trim();
|
|
4145
|
+
const route = resolver(name);
|
|
4146
|
+
const a = document.createElement("a");
|
|
4147
|
+
a.href = route ?? `#/spec/${encodeURIComponent(name)}`;
|
|
4148
|
+
a.textContent = name;
|
|
4149
|
+
a.className = "zg-wikilink" + (route ? "" : " zg-wikilink-missing");
|
|
4150
|
+
if (!route) a.title = `"${name}" doesn't exist yet`;
|
|
4151
|
+
frag.appendChild(a);
|
|
4152
|
+
lastIdx = m.index + m[0].length;
|
|
4153
|
+
}
|
|
4154
|
+
if (lastIdx < text2.length) {
|
|
4155
|
+
frag.appendChild(document.createTextNode(text2.slice(lastIdx)));
|
|
4156
|
+
}
|
|
4157
|
+
textNode.replaceWith(frag);
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
// src/views/detail.ts
|
|
4162
|
+
async function renderDetail(params) {
|
|
4163
|
+
const app = document.getElementById("app");
|
|
4164
|
+
const name = params.name;
|
|
4165
|
+
if (!name) {
|
|
4166
|
+
app.innerHTML = '<p class="zg-error">Missing spec name in URL.</p>';
|
|
4167
|
+
return;
|
|
4168
|
+
}
|
|
4169
|
+
if (app.children.length === 0 || app.querySelector(".zg-detail") === null) {
|
|
4170
|
+
app.innerHTML = "<p>Loading\u2026</p>";
|
|
4171
|
+
}
|
|
4172
|
+
const backend = window.zettelgeistBackend;
|
|
4173
|
+
let spec;
|
|
4174
|
+
let summary = null;
|
|
4175
|
+
let specNames = [];
|
|
4176
|
+
let docPaths = [];
|
|
4177
|
+
try {
|
|
4178
|
+
spec = await backend.readSpec(name);
|
|
4179
|
+
const [all, docs] = await Promise.all([backend.listSpecs(), backend.listDocs().catch(() => [])]);
|
|
4180
|
+
summary = all.find((s) => s.name === name) ?? null;
|
|
4181
|
+
specNames = all.map((s) => s.name);
|
|
4182
|
+
docPaths = docs.map((d) => d.path);
|
|
4183
|
+
} catch (err) {
|
|
4184
|
+
app.innerHTML = `<p class="zg-error">Failed to load spec "${escapeHtml(name)}": ${escapeHtml(err.message)}</p>`;
|
|
4185
|
+
return;
|
|
4186
|
+
}
|
|
4187
|
+
const resolver = makeWikiLinkResolver(specNames, docPaths);
|
|
4188
|
+
const enrich = (root) => processWikiLinks(root, resolver);
|
|
4189
|
+
const wrapper = document.createElement("article");
|
|
4190
|
+
wrapper.className = "zg-detail";
|
|
4191
|
+
wrapper.appendChild(renderHeader(spec, summary));
|
|
4192
|
+
const tabs = [
|
|
4193
|
+
{ id: "requirements", label: "Requirements", render: () => renderRequirementsTab(spec, enrich) },
|
|
4194
|
+
{ id: "tasks", label: `Tasks (${spec.tasks.length})`, render: () => renderTasksTab(spec) },
|
|
4195
|
+
{ id: "handoff", label: "Handoff", render: () => renderHandoffTab(spec, enrich) }
|
|
4196
|
+
];
|
|
4197
|
+
if (Object.keys(spec.lenses).length > 0) {
|
|
4198
|
+
tabs.push({ id: "lenses", label: "Lenses", render: () => renderLensesTab(spec, enrich) });
|
|
4199
|
+
}
|
|
4200
|
+
const tabKey = `zg:tab:${spec.name}`;
|
|
4201
|
+
const stored = (() => {
|
|
4202
|
+
try {
|
|
4203
|
+
return sessionStorage.getItem(tabKey);
|
|
4204
|
+
} catch {
|
|
4205
|
+
return null;
|
|
4206
|
+
}
|
|
4207
|
+
})();
|
|
4208
|
+
const tabsOpts = {
|
|
4209
|
+
onActivate: (id) => {
|
|
4210
|
+
try {
|
|
4211
|
+
sessionStorage.setItem(tabKey, id);
|
|
4212
|
+
} catch {
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
};
|
|
4216
|
+
if (stored) tabsOpts.initialTabId = stored;
|
|
4217
|
+
wrapper.appendChild(renderTabs(tabs, tabsOpts));
|
|
4218
|
+
app.replaceChildren(wrapper);
|
|
4219
|
+
}
|
|
4220
|
+
function renderHeader(spec, summary) {
|
|
4221
|
+
const header = document.createElement("header");
|
|
4222
|
+
header.className = "zg-detail-header";
|
|
4223
|
+
const referrer = sessionStorage.getItem("zg:prev-route") ?? "#/";
|
|
4224
|
+
const back = document.createElement("a");
|
|
4225
|
+
back.href = referrer;
|
|
4226
|
+
back.textContent = referrer === "#/graph" ? "\u2190 Back to graph" : "\u2190 Back to board";
|
|
4227
|
+
back.className = "zg-back-link";
|
|
4228
|
+
header.appendChild(back);
|
|
4229
|
+
const titleRow = document.createElement("div");
|
|
4230
|
+
titleRow.className = "zg-detail-title-row";
|
|
4231
|
+
const title = document.createElement("h2");
|
|
4232
|
+
title.textContent = spec.name;
|
|
4233
|
+
titleRow.appendChild(title);
|
|
4234
|
+
if (summary) {
|
|
4235
|
+
const editBtn = document.createElement("button");
|
|
4236
|
+
editBtn.type = "button";
|
|
4237
|
+
editBtn.className = "zg-detail-edit-btn";
|
|
4238
|
+
editBtn.textContent = "\u270E Edit details";
|
|
4239
|
+
editBtn.addEventListener("click", async () => {
|
|
4240
|
+
const { showEditModal } = await import("./edit-modal-BEGC2AO6.js");
|
|
4241
|
+
const saved = await showEditModal({ spec: summary });
|
|
4242
|
+
if (saved) window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
4243
|
+
});
|
|
4244
|
+
titleRow.appendChild(editBtn);
|
|
4245
|
+
}
|
|
4246
|
+
header.appendChild(titleRow);
|
|
4247
|
+
if (summary) header.appendChild(renderMeta(summary));
|
|
4248
|
+
return header;
|
|
4249
|
+
}
|
|
4250
|
+
function renderMeta(summary) {
|
|
4251
|
+
const meta = document.createElement("div");
|
|
4252
|
+
meta.className = "zg-detail-meta";
|
|
4253
|
+
const status = document.createElement("span");
|
|
4254
|
+
status.className = `zg-status-pill zg-status-${summary.status}`;
|
|
4255
|
+
status.textContent = summary.status;
|
|
4256
|
+
meta.appendChild(status);
|
|
4257
|
+
const { done, total } = parseProgress2(summary.progress);
|
|
4258
|
+
const progressWrap = document.createElement("span");
|
|
4259
|
+
progressWrap.className = "zg-detail-progress";
|
|
4260
|
+
progressWrap.textContent = `${summary.progress} tasks`;
|
|
4261
|
+
if (total > 0) {
|
|
4262
|
+
const bar = document.createElement("span");
|
|
4263
|
+
bar.className = "zg-detail-progress-bar";
|
|
4264
|
+
const fill = document.createElement("span");
|
|
4265
|
+
fill.className = "zg-detail-progress-fill";
|
|
4266
|
+
fill.style.width = `${Math.round(done / total * 100)}%`;
|
|
4267
|
+
bar.appendChild(fill);
|
|
4268
|
+
progressWrap.appendChild(bar);
|
|
4269
|
+
}
|
|
4270
|
+
meta.appendChild(progressWrap);
|
|
4271
|
+
if (summary.pr) {
|
|
4272
|
+
const a = document.createElement("a");
|
|
4273
|
+
a.className = "zg-badge zg-badge-pr";
|
|
4274
|
+
a.href = summary.pr;
|
|
4275
|
+
a.target = "_blank";
|
|
4276
|
+
a.rel = "noopener";
|
|
4277
|
+
a.textContent = prLabel2(summary.pr);
|
|
4278
|
+
meta.appendChild(a);
|
|
4279
|
+
}
|
|
4280
|
+
if (summary.branch) {
|
|
4281
|
+
const span = document.createElement("span");
|
|
4282
|
+
span.className = "zg-badge zg-badge-branch";
|
|
4283
|
+
span.textContent = summary.branch;
|
|
4284
|
+
meta.appendChild(span);
|
|
4285
|
+
}
|
|
4286
|
+
if (summary.worktree) {
|
|
4287
|
+
const span = document.createElement("span");
|
|
4288
|
+
span.className = "zg-badge zg-badge-worktree";
|
|
4289
|
+
span.title = `worktree: ${summary.worktree}`;
|
|
4290
|
+
span.textContent = `\u{1F4C1} ${summary.worktree}`;
|
|
4291
|
+
meta.appendChild(span);
|
|
4292
|
+
}
|
|
4293
|
+
if (summary.blockedBy) {
|
|
4294
|
+
const span = document.createElement("span");
|
|
4295
|
+
span.className = "zg-badge zg-badge-blocked";
|
|
4296
|
+
span.title = summary.blockedBy;
|
|
4297
|
+
span.textContent = `blocked: ${summary.blockedBy}`;
|
|
4298
|
+
meta.appendChild(span);
|
|
4299
|
+
}
|
|
4300
|
+
return meta;
|
|
4301
|
+
}
|
|
4302
|
+
function renderRequirementsTab(spec, postRender) {
|
|
4303
|
+
const container = document.createElement("div");
|
|
4304
|
+
container.appendChild(renderFrontmatterForm(spec));
|
|
4305
|
+
container.appendChild(
|
|
4306
|
+
renderMarkdownEditor({
|
|
4307
|
+
body: spec.requirements,
|
|
4308
|
+
emptyPlaceholder: `No requirements yet for "${spec.name}"`,
|
|
4309
|
+
emptyHint: "Write what this spec is for, the acceptance criteria, what's out of scope, and any references. Use `- [ ]` checkboxes for each acceptance criterion.",
|
|
4310
|
+
startingTemplate: REQUIREMENTS_TEMPLATE.replace("{NAME}", spec.name),
|
|
4311
|
+
postRender,
|
|
4312
|
+
interactiveCheckboxes: true,
|
|
4313
|
+
// requirements.md has frontmatter we must preserve on body-only edits.
|
|
4314
|
+
onSave: async (newBody) => {
|
|
4315
|
+
const backend = window.zettelgeistBackend;
|
|
4316
|
+
const file = await backend.readSpecFile(spec.name, "requirements.md").catch(() => ({ content: "" }));
|
|
4317
|
+
const { frontmatter } = splitFrontmatter(file.content);
|
|
4318
|
+
const next = frontmatter + (newBody.startsWith("\n") ? newBody : `
|
|
4319
|
+
${newBody}`);
|
|
4320
|
+
await backend.writeSpecFile(spec.name, "requirements.md", next);
|
|
4321
|
+
}
|
|
4322
|
+
})
|
|
4323
|
+
);
|
|
4324
|
+
return container;
|
|
4325
|
+
}
|
|
4326
|
+
function renderTasksTab(spec) {
|
|
4327
|
+
return renderTaskList(spec.name, spec.tasks);
|
|
4328
|
+
}
|
|
4329
|
+
function renderHandoffTab(spec, postRender) {
|
|
4330
|
+
return renderMarkdownEditor({
|
|
4331
|
+
body: spec.handoff,
|
|
4332
|
+
emptyPlaceholder: "No handoff notes yet",
|
|
4333
|
+
emptyHint: "When you pause work on this spec, leave a note for the next person (or agent): what you did, what's next, and any open questions.",
|
|
4334
|
+
startingTemplate: HANDOFF_TEMPLATE,
|
|
4335
|
+
postRender,
|
|
4336
|
+
interactiveCheckboxes: true,
|
|
4337
|
+
onSave: async (newBody) => {
|
|
4338
|
+
await window.zettelgeistBackend.writeHandoff(spec.name, newBody);
|
|
4339
|
+
}
|
|
4340
|
+
});
|
|
4341
|
+
}
|
|
4342
|
+
var REQUIREMENTS_TEMPLATE = `# {NAME}
|
|
4343
|
+
|
|
4344
|
+
## Why
|
|
4345
|
+
|
|
4346
|
+
<!-- Why does this spec exist? What problem does it solve, for whom? -->
|
|
4347
|
+
|
|
4348
|
+
## Acceptance criteria
|
|
4349
|
+
|
|
4350
|
+
- [ ] WHEN <trigger>
|
|
4351
|
+
- [ ] THE SYSTEM SHALL <observable behavior>
|
|
4352
|
+
- [ ] AND <additional behavior>
|
|
4353
|
+
|
|
4354
|
+
## Out of scope
|
|
4355
|
+
|
|
4356
|
+
- <thing this spec deliberately doesn't cover>
|
|
4357
|
+
|
|
4358
|
+
## References
|
|
4359
|
+
|
|
4360
|
+
- <link or note>
|
|
4361
|
+
`;
|
|
4362
|
+
var HANDOFF_TEMPLATE = `## What I did
|
|
4363
|
+
|
|
4364
|
+
<!-- Concrete progress: code, decisions, dead ends. -->
|
|
4365
|
+
|
|
4366
|
+
## What's next
|
|
4367
|
+
|
|
4368
|
+
<!-- The most useful 1\u20133 things the next session should pick up. -->
|
|
4369
|
+
|
|
4370
|
+
## Open questions
|
|
4371
|
+
|
|
4372
|
+
<!-- Anything ambiguous that needs a human or another agent to weigh in. -->
|
|
4373
|
+
`;
|
|
4374
|
+
function renderLensesTab(spec, postRender) {
|
|
4375
|
+
const container = document.createElement("div");
|
|
4376
|
+
for (const [name, content] of Object.entries(spec.lenses)) {
|
|
4377
|
+
const heading2 = document.createElement("h3");
|
|
4378
|
+
heading2.textContent = name;
|
|
4379
|
+
container.appendChild(heading2);
|
|
4380
|
+
container.appendChild(
|
|
4381
|
+
renderMarkdownEditor({
|
|
4382
|
+
body: content,
|
|
4383
|
+
emptyPlaceholder: "(empty)",
|
|
4384
|
+
postRender,
|
|
4385
|
+
interactiveCheckboxes: true,
|
|
4386
|
+
onSave: async (newBody) => {
|
|
4387
|
+
await window.zettelgeistBackend.writeSpecFile(spec.name, `lenses/${name}.md`, newBody);
|
|
4388
|
+
}
|
|
4389
|
+
})
|
|
4390
|
+
);
|
|
4391
|
+
}
|
|
4392
|
+
return container;
|
|
4393
|
+
}
|
|
4394
|
+
function parseProgress2(s) {
|
|
4395
|
+
const m = /^(\d+)\s*\/\s*(\d+)$/.exec(s);
|
|
4396
|
+
if (!m) return { done: 0, total: 0 };
|
|
4397
|
+
return { done: Number(m[1]), total: Number(m[2]) };
|
|
4398
|
+
}
|
|
4399
|
+
function prLabel2(url) {
|
|
4400
|
+
const gh = /\/pull\/(\d+)/.exec(url);
|
|
4401
|
+
if (gh) return `PR #${gh[1]}`;
|
|
4402
|
+
return "PR";
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
// src/views/graph.ts
|
|
4406
|
+
var mermaidModule = null;
|
|
4407
|
+
async function loadMermaid() {
|
|
4408
|
+
if (!mermaidModule) {
|
|
4409
|
+
mermaidModule = await import("./mermaid.core-AEBXU2JK.js");
|
|
4410
|
+
}
|
|
4411
|
+
const mermaid = mermaidModule.default;
|
|
4412
|
+
mermaid.initialize({
|
|
4413
|
+
startOnLoad: false,
|
|
4414
|
+
theme: document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "default",
|
|
4415
|
+
securityLevel: "strict",
|
|
4416
|
+
// htmlLabels: false → native SVG <text> labels (sanitizer-friendly,
|
|
4417
|
+
// and lets our newline-in-label trick work for "name + progress").
|
|
4418
|
+
flowchart: { htmlLabels: false, curve: "basis" }
|
|
4419
|
+
});
|
|
4420
|
+
return mermaid;
|
|
4421
|
+
}
|
|
4422
|
+
async function renderGraph() {
|
|
4423
|
+
const app = document.getElementById("app");
|
|
4424
|
+
app.innerHTML = "<p>Loading graph\u2026</p>";
|
|
4425
|
+
const backend = window.zettelgeistBackend;
|
|
4426
|
+
let specs;
|
|
4427
|
+
try {
|
|
4428
|
+
specs = await backend.listSpecs();
|
|
4429
|
+
} catch (err) {
|
|
4430
|
+
app.innerHTML = `<p class="zg-error">Failed to load: ${escapeHtml(err.message)}</p>`;
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4433
|
+
const edges = [];
|
|
4434
|
+
const meta = /* @__PURE__ */ new Map();
|
|
4435
|
+
for (const summary of specs) {
|
|
4436
|
+
try {
|
|
4437
|
+
const spec = await backend.readSpec(summary.name);
|
|
4438
|
+
const deps = spec.frontmatter.depends_on;
|
|
4439
|
+
if (Array.isArray(deps)) {
|
|
4440
|
+
for (const dep of deps) {
|
|
4441
|
+
if (typeof dep === "string" && specs.some((s) => s.name === dep)) {
|
|
4442
|
+
edges.push({ from: summary.name, to: dep });
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
const partOf = typeof spec.frontmatter.part_of === "string" ? spec.frontmatter.part_of : null;
|
|
4447
|
+
meta.set(summary.name, { partOf });
|
|
4448
|
+
} catch {
|
|
4449
|
+
meta.set(summary.name, { partOf: null });
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
app.innerHTML = "";
|
|
4453
|
+
const banner = await fetchAndRenderValidationBanner();
|
|
4454
|
+
if (banner) app.appendChild(banner);
|
|
4455
|
+
const wrapper = document.createElement("div");
|
|
4456
|
+
wrapper.className = "zg-graph";
|
|
4457
|
+
const heading2 = document.createElement("div");
|
|
4458
|
+
heading2.className = "zg-graph-header";
|
|
4459
|
+
const h2 = document.createElement("h2");
|
|
4460
|
+
h2.textContent = "Dependency Graph";
|
|
4461
|
+
heading2.appendChild(h2);
|
|
4462
|
+
const sub = document.createElement("p");
|
|
4463
|
+
sub.className = "zg-graph-subtitle";
|
|
4464
|
+
sub.textContent = "Each box is a spec \u2014 its color is the status, the number underneath is task progress. Arrows point from a spec to a spec it depends on. Click any box to open it. Dashed enclosures group specs by their `part_of` field (e.g. an epic or product area).";
|
|
4465
|
+
heading2.appendChild(sub);
|
|
4466
|
+
wrapper.appendChild(heading2);
|
|
4467
|
+
if (specs.length === 0) {
|
|
4468
|
+
const empty = document.createElement("div");
|
|
4469
|
+
empty.className = "zg-empty-state";
|
|
4470
|
+
const h = document.createElement("h3");
|
|
4471
|
+
h.textContent = "No specs to graph";
|
|
4472
|
+
empty.appendChild(h);
|
|
4473
|
+
const p = document.createElement("p");
|
|
4474
|
+
p.textContent = "Once specs exist, depends_on edges between them are rendered here.";
|
|
4475
|
+
empty.appendChild(p);
|
|
4476
|
+
wrapper.appendChild(empty);
|
|
4477
|
+
app.appendChild(wrapper);
|
|
4478
|
+
return;
|
|
4479
|
+
}
|
|
4480
|
+
wrapper.appendChild(renderLegend(specs));
|
|
4481
|
+
const mermaidSrc = renderMermaidSource(specs, edges, meta);
|
|
4482
|
+
const container = document.createElement("div");
|
|
4483
|
+
container.className = "zg-graph-container";
|
|
4484
|
+
wrapper.appendChild(container);
|
|
4485
|
+
app.appendChild(wrapper);
|
|
4486
|
+
document.querySelectorAll('[id^="dzg-graph-svg"], [id^="zg-graph-svg"]').forEach((el) => {
|
|
4487
|
+
if (!container.contains(el)) el.remove();
|
|
4488
|
+
});
|
|
4489
|
+
const renderId = `zg-graph-svg-${Date.now()}`;
|
|
4490
|
+
try {
|
|
4491
|
+
const mermaid = await loadMermaid();
|
|
4492
|
+
const { svg: svg2 } = await mermaid.render(renderId, mermaidSrc);
|
|
4493
|
+
container.innerHTML = svg2;
|
|
4494
|
+
document.querySelectorAll(`#d${renderId}, #${renderId}`).forEach((el) => {
|
|
4495
|
+
if (!container.contains(el)) el.remove();
|
|
4496
|
+
});
|
|
4497
|
+
const idToName = /* @__PURE__ */ new Map();
|
|
4498
|
+
for (const s of specs) idToName.set(nodeId(s.name), s.name);
|
|
4499
|
+
container.querySelectorAll("g.node").forEach((node) => {
|
|
4500
|
+
const id = node.id;
|
|
4501
|
+
const matchedName = [...idToName.entries()].find(([nid]) => id.includes(nid))?.[1];
|
|
4502
|
+
if (!matchedName) return;
|
|
4503
|
+
node.style.cursor = "pointer";
|
|
4504
|
+
node.addEventListener("click", () => {
|
|
4505
|
+
window.location.hash = `#/spec/${encodeURIComponent(matchedName)}`;
|
|
4506
|
+
});
|
|
4507
|
+
});
|
|
4508
|
+
} catch (err) {
|
|
4509
|
+
container.innerHTML = `<pre class="zg-graph-fallback">${escapeMermaidSrcForDisplay(mermaidSrc)}</pre><p class="zg-error">Could not render Mermaid graph: ${escapeHtml(err.message)}. Showing source.</p>`;
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
function renderLegend(specs) {
|
|
4513
|
+
const present = /* @__PURE__ */ new Set();
|
|
4514
|
+
for (const s of specs) present.add(s.status);
|
|
4515
|
+
const legend = document.createElement("div");
|
|
4516
|
+
legend.className = "zg-graph-legend";
|
|
4517
|
+
const items = [
|
|
4518
|
+
{ s: "draft", label: "Draft" },
|
|
4519
|
+
{ s: "planned", label: "Planned" },
|
|
4520
|
+
{ s: "in-progress", label: "In Progress" },
|
|
4521
|
+
{ s: "in-review", label: "In Review" },
|
|
4522
|
+
{ s: "done", label: "Done" },
|
|
4523
|
+
{ s: "blocked", label: "Blocked" },
|
|
4524
|
+
{ s: "cancelled", label: "Cancelled" }
|
|
4525
|
+
];
|
|
4526
|
+
for (const { s, label } of items) {
|
|
4527
|
+
if (!present.has(s)) continue;
|
|
4528
|
+
const item = document.createElement("span");
|
|
4529
|
+
item.className = "zg-legend-item";
|
|
4530
|
+
const swatch = document.createElement("span");
|
|
4531
|
+
swatch.className = `zg-legend-swatch zg-legend-${s}`;
|
|
4532
|
+
item.appendChild(swatch);
|
|
4533
|
+
item.appendChild(document.createTextNode(label));
|
|
4534
|
+
legend.appendChild(item);
|
|
4535
|
+
}
|
|
4536
|
+
return legend;
|
|
4537
|
+
}
|
|
4538
|
+
function renderMermaidSource(specs, edges, meta) {
|
|
4539
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4540
|
+
const ungrouped = [];
|
|
4541
|
+
for (const s of specs) {
|
|
4542
|
+
const part = meta.get(s.name)?.partOf ?? null;
|
|
4543
|
+
if (!part) {
|
|
4544
|
+
ungrouped.push(s);
|
|
4545
|
+
continue;
|
|
4546
|
+
}
|
|
4547
|
+
if (!groups.has(part)) groups.set(part, []);
|
|
4548
|
+
groups.get(part).push(s);
|
|
4549
|
+
}
|
|
4550
|
+
const lines = ["graph TD"];
|
|
4551
|
+
for (const s of ungrouped) lines.push(` ${emitNode(s)}`);
|
|
4552
|
+
let sgIdx = 0;
|
|
4553
|
+
const clusterStyles = [];
|
|
4554
|
+
for (const [part, members] of groups) {
|
|
4555
|
+
const sgId = `sg_${sgIdx++}`;
|
|
4556
|
+
lines.push(` subgraph ${sgId} ["\u{1F4C1} ${escapeForLabel(part)}"]`);
|
|
4557
|
+
for (const s of members) lines.push(` ${emitNode(s)}`);
|
|
4558
|
+
lines.push(" end");
|
|
4559
|
+
const [fill, stroke] = clusterPalette(part);
|
|
4560
|
+
clusterStyles.push(`style ${sgId} fill:${fill},stroke:${stroke},stroke-dasharray:4 4`);
|
|
4561
|
+
}
|
|
4562
|
+
for (const e of edges) lines.push(` ${nodeId(e.from)} --> ${nodeId(e.to)}`);
|
|
4563
|
+
const palette = [
|
|
4564
|
+
// [status, fill, stroke, text-color]
|
|
4565
|
+
["draft", "#f3f4f6", "#9ca3af", "#374151"],
|
|
4566
|
+
["planned", "#dbeafe", "#60a5fa", "#1e3a8a"],
|
|
4567
|
+
["in-progress", "#fef3c7", "#f59e0b", "#78350f"],
|
|
4568
|
+
["in-review", "#ede9fe", "#8b5cf6", "#4c1d95"],
|
|
4569
|
+
["done", "#d1fae5", "#10b981", "#064e3b"],
|
|
4570
|
+
["blocked", "#fee2e2", "#c0392b", "#7f1d1d"],
|
|
4571
|
+
["cancelled", "#f3f4f6", "#7f8c8d", "#374151"]
|
|
4572
|
+
];
|
|
4573
|
+
for (const [status, fill, stroke, color] of palette) {
|
|
4574
|
+
lines.push(`classDef ${statusClass(status)} fill:${fill},stroke:${stroke},color:${color},stroke-width:1.5px`);
|
|
4575
|
+
}
|
|
4576
|
+
const byStatus = /* @__PURE__ */ new Map();
|
|
4577
|
+
for (const s of specs) {
|
|
4578
|
+
if (!byStatus.has(s.status)) byStatus.set(s.status, []);
|
|
4579
|
+
byStatus.get(s.status).push(nodeId(s.name));
|
|
4580
|
+
}
|
|
4581
|
+
for (const [status, ids] of byStatus) {
|
|
4582
|
+
lines.push(`class ${ids.join(",")} ${statusClass(status)}`);
|
|
4583
|
+
}
|
|
4584
|
+
for (const s of clusterStyles) lines.push(s);
|
|
4585
|
+
return lines.join("\n");
|
|
4586
|
+
}
|
|
4587
|
+
var CLUSTER_PALETTE = [
|
|
4588
|
+
["#60a5fa1a", "#60a5fa8c"],
|
|
4589
|
+
// blue
|
|
4590
|
+
["#f59e0b1a", "#f59e0b8c"],
|
|
4591
|
+
// amber
|
|
4592
|
+
["#8b5cf61a", "#8b5cf68c"],
|
|
4593
|
+
// violet
|
|
4594
|
+
["#10b9811a", "#10b9818c"],
|
|
4595
|
+
// emerald
|
|
4596
|
+
["#ec48991a", "#ec48998c"],
|
|
4597
|
+
// pink
|
|
4598
|
+
["#f472b61a", "#f472b68c"],
|
|
4599
|
+
// rose
|
|
4600
|
+
["#38bdf81a", "#38bdf88c"]
|
|
4601
|
+
// sky
|
|
4602
|
+
];
|
|
4603
|
+
function clusterPalette(name) {
|
|
4604
|
+
let h = 0;
|
|
4605
|
+
for (let i = 0; i < name.length; i++) h = h * 31 + name.charCodeAt(i) | 0;
|
|
4606
|
+
const idx = Math.abs(h) % CLUSTER_PALETTE.length;
|
|
4607
|
+
return CLUSTER_PALETTE[idx];
|
|
4608
|
+
}
|
|
4609
|
+
function emitNode(s) {
|
|
4610
|
+
const label = `${s.name}\\n${s.progress}`;
|
|
4611
|
+
return `${nodeId(s.name)}["${escapeForLabel(label)}"]`;
|
|
4612
|
+
}
|
|
4613
|
+
function nodeId(name) {
|
|
4614
|
+
return `n_${name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
4615
|
+
}
|
|
4616
|
+
function statusClass(s) {
|
|
4617
|
+
return `s_${s.replace(/-/g, "_")}`;
|
|
4618
|
+
}
|
|
4619
|
+
function escapeForLabel(s) {
|
|
4620
|
+
return s.replace(/"/g, '\\"');
|
|
4621
|
+
}
|
|
4622
|
+
function escapeMermaidSrcForDisplay(s) {
|
|
4623
|
+
return s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c] ?? c);
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4626
|
+
// src/views/docs.ts
|
|
4627
|
+
async function renderDocs(params) {
|
|
4628
|
+
const app = document.getElementById("app");
|
|
4629
|
+
app.innerHTML = "<p>Loading docs\u2026</p>";
|
|
4630
|
+
const backend = window.zettelgeistBackend;
|
|
4631
|
+
let entries2;
|
|
4632
|
+
let specNames = [];
|
|
4633
|
+
try {
|
|
4634
|
+
const [docs, specs] = await Promise.all([
|
|
4635
|
+
backend.listDocs(),
|
|
4636
|
+
backend.listSpecs().catch(() => [])
|
|
4637
|
+
]);
|
|
4638
|
+
entries2 = docs;
|
|
4639
|
+
specNames = specs.map((s) => s.name);
|
|
4640
|
+
} catch (err) {
|
|
4641
|
+
app.innerHTML = `<p class="zg-error">Failed to list docs: ${escapeHtml(err.message)}</p>`;
|
|
4642
|
+
return;
|
|
4643
|
+
}
|
|
4644
|
+
const resolver = makeWikiLinkResolver(specNames, entries2.map((e) => e.path));
|
|
4645
|
+
const enrich = (root) => processWikiLinks(root, resolver);
|
|
4646
|
+
let selectedPath = params.path ? decodeURIComponent(params.path) : null;
|
|
4647
|
+
if (!selectedPath && entries2.length > 0) {
|
|
4648
|
+
const preferences = ["docs/README.md", "docs/readme.md", "docs/architecture.md", "docs/onboarding.md"];
|
|
4649
|
+
const sorted = [...entries2].sort((a, b) => a.path.localeCompare(b.path));
|
|
4650
|
+
selectedPath = preferences.find((p) => entries2.some((e) => e.path === p)) ?? sorted[0].path;
|
|
4651
|
+
}
|
|
4652
|
+
app.innerHTML = "";
|
|
4653
|
+
const wrapper = document.createElement("div");
|
|
4654
|
+
wrapper.className = "zg-docs";
|
|
4655
|
+
const sidebar = document.createElement("aside");
|
|
4656
|
+
sidebar.className = "zg-docs-sidebar";
|
|
4657
|
+
const title = document.createElement("h3");
|
|
4658
|
+
title.textContent = "Docs";
|
|
4659
|
+
sidebar.appendChild(title);
|
|
4660
|
+
const list2 = document.createElement("ul");
|
|
4661
|
+
list2.className = "zg-docs-list";
|
|
4662
|
+
for (const entry of entries2) {
|
|
4663
|
+
const li = document.createElement("li");
|
|
4664
|
+
li.className = "zg-docs-list-item";
|
|
4665
|
+
const link2 = document.createElement("a");
|
|
4666
|
+
link2.href = `#/docs/${encodeURIComponent(entry.path)}`;
|
|
4667
|
+
link2.textContent = entry.title || entry.path;
|
|
4668
|
+
if (entry.path === selectedPath) {
|
|
4669
|
+
link2.classList.add("active");
|
|
4670
|
+
}
|
|
4671
|
+
li.appendChild(link2);
|
|
4672
|
+
const renameBtn = document.createElement("button");
|
|
4673
|
+
renameBtn.type = "button";
|
|
4674
|
+
renameBtn.className = "zg-docs-rename";
|
|
4675
|
+
renameBtn.title = "Rename";
|
|
4676
|
+
renameBtn.setAttribute("aria-label", `Rename ${entry.path}`);
|
|
4677
|
+
renameBtn.textContent = "\u270E";
|
|
4678
|
+
renameBtn.addEventListener("click", async (e) => {
|
|
4679
|
+
e.preventDefault();
|
|
4680
|
+
e.stopPropagation();
|
|
4681
|
+
const next = await showInputModal({
|
|
4682
|
+
title: `Rename ${entry.path}`,
|
|
4683
|
+
message: "Path is relative to the workspace root. Use forward slashes for subfolders.",
|
|
4684
|
+
defaultValue: entry.path,
|
|
4685
|
+
placeholder: "docs/new-name.md",
|
|
4686
|
+
confirmLabel: "Rename",
|
|
4687
|
+
validate: (v) => {
|
|
4688
|
+
const t = v.trim();
|
|
4689
|
+
if (!t) return "Path is required.";
|
|
4690
|
+
if (t === entry.path) return "Pick a different path.";
|
|
4691
|
+
if (!t.endsWith(".md")) return "Path must end in .md";
|
|
4692
|
+
return null;
|
|
4693
|
+
}
|
|
4694
|
+
});
|
|
4695
|
+
if (next === null) return;
|
|
4696
|
+
const trimmed = next.trim();
|
|
4697
|
+
try {
|
|
4698
|
+
const result = await backend.renameDoc(entry.path, trimmed);
|
|
4699
|
+
const wasViewing = entry.path === selectedPath;
|
|
4700
|
+
if (wasViewing) {
|
|
4701
|
+
window.location.hash = `#/docs/${encodeURIComponent(result.newPath)}`;
|
|
4702
|
+
}
|
|
4703
|
+
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
4704
|
+
} catch (err) {
|
|
4705
|
+
void showAlert("Rename failed", err.message);
|
|
4706
|
+
}
|
|
4707
|
+
});
|
|
4708
|
+
li.appendChild(renameBtn);
|
|
4709
|
+
list2.appendChild(li);
|
|
4710
|
+
}
|
|
4711
|
+
sidebar.appendChild(list2);
|
|
4712
|
+
const main = document.createElement("article");
|
|
4713
|
+
main.className = "zg-docs-main";
|
|
4714
|
+
if (selectedPath) {
|
|
4715
|
+
try {
|
|
4716
|
+
const doc = await backend.readDoc(selectedPath);
|
|
4717
|
+
main.appendChild(
|
|
4718
|
+
renderMarkdownEditor({
|
|
4719
|
+
body: doc.source,
|
|
4720
|
+
emptyPlaceholder: `${selectedPath} is empty`,
|
|
4721
|
+
emptyHint: "Click below to start writing.",
|
|
4722
|
+
interactiveCheckboxes: true,
|
|
4723
|
+
postRender: enrich,
|
|
4724
|
+
onSave: async (newBody) => {
|
|
4725
|
+
await backend.writeDoc(selectedPath, newBody);
|
|
4726
|
+
}
|
|
4727
|
+
})
|
|
4728
|
+
);
|
|
4729
|
+
} catch (err) {
|
|
4730
|
+
main.innerHTML = `<p class="zg-error">Failed to read doc: ${escapeHtml(err.message)}</p>`;
|
|
4731
|
+
}
|
|
4732
|
+
} else {
|
|
4733
|
+
const empty = document.createElement("div");
|
|
4734
|
+
empty.className = "zg-empty-state";
|
|
4735
|
+
const h = document.createElement("h3");
|
|
4736
|
+
h.textContent = "No docs yet";
|
|
4737
|
+
empty.appendChild(h);
|
|
4738
|
+
const p = document.createElement("p");
|
|
4739
|
+
p.textContent = "Add narrative documentation as markdown files under a `docs/` folder at the repo root. Anything there shows up here.";
|
|
4740
|
+
empty.appendChild(p);
|
|
4741
|
+
main.appendChild(empty);
|
|
4742
|
+
}
|
|
4743
|
+
wrapper.appendChild(sidebar);
|
|
4744
|
+
wrapper.appendChild(main);
|
|
4745
|
+
app.appendChild(wrapper);
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
// src/main.ts
|
|
4749
|
+
var THEME_STORAGE_KEY = "zg.theme";
|
|
4750
|
+
function resolveTheme(config) {
|
|
4751
|
+
const stored = (() => {
|
|
4752
|
+
try {
|
|
4753
|
+
return localStorage.getItem(THEME_STORAGE_KEY);
|
|
4754
|
+
} catch {
|
|
4755
|
+
return null;
|
|
4756
|
+
}
|
|
4757
|
+
})();
|
|
4758
|
+
if (stored === "light" || stored === "dark") return stored;
|
|
4759
|
+
const requested = config?.theme ?? "system";
|
|
4760
|
+
if (requested === "light" || requested === "dark") return requested;
|
|
4761
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
4762
|
+
}
|
|
4763
|
+
function applyTheme(theme) {
|
|
4764
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
4765
|
+
const btn = document.getElementById("zg-theme-toggle");
|
|
4766
|
+
if (btn) btn.textContent = theme === "dark" ? "\u2600" : "\u263E";
|
|
4767
|
+
}
|
|
4768
|
+
function setupTheme(config) {
|
|
4769
|
+
let current = resolveTheme(config);
|
|
4770
|
+
applyTheme(current);
|
|
4771
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
4772
|
+
mq.addEventListener("change", (e) => {
|
|
4773
|
+
try {
|
|
4774
|
+
if (localStorage.getItem(THEME_STORAGE_KEY)) return;
|
|
4775
|
+
} catch {
|
|
4776
|
+
}
|
|
4777
|
+
current = e.matches ? "dark" : "light";
|
|
4778
|
+
applyTheme(current);
|
|
4779
|
+
});
|
|
4780
|
+
const btn = document.getElementById("zg-theme-toggle");
|
|
4781
|
+
if (btn) {
|
|
4782
|
+
btn.addEventListener("click", () => {
|
|
4783
|
+
current = current === "dark" ? "light" : "dark";
|
|
4784
|
+
try {
|
|
4785
|
+
localStorage.setItem(THEME_STORAGE_KEY, current);
|
|
4786
|
+
} catch {
|
|
4787
|
+
}
|
|
4788
|
+
applyTheme(current);
|
|
4789
|
+
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
4790
|
+
});
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
async function bootstrap() {
|
|
4794
|
+
const backend = window.zettelgeistBackend;
|
|
4795
|
+
if (!backend) {
|
|
4796
|
+
document.getElementById("app").innerHTML = "<p>Error: <code>window.zettelgeistBackend</code> is not defined. The viewer must be loaded by a host that injects a backend.</p>";
|
|
4797
|
+
throw new Error("window.zettelgeistBackend is not defined");
|
|
4798
|
+
}
|
|
4799
|
+
setupTheme(window.zettelgeistConfig);
|
|
4800
|
+
const router = new Router();
|
|
4801
|
+
router.add("/", renderBoard);
|
|
4802
|
+
router.add("/spec/:name", renderDetail);
|
|
4803
|
+
router.add("/graph", renderGraph);
|
|
4804
|
+
router.add("/docs", renderDocs);
|
|
4805
|
+
router.add("/docs/*path", renderDocs);
|
|
4806
|
+
router.start();
|
|
4807
|
+
}
|
|
4808
|
+
bootstrap().catch((err) => {
|
|
4809
|
+
console.error("viewer bootstrap failed:", err);
|
|
4810
|
+
});
|
|
4811
|
+
/*! Bundled license information:
|
|
4812
|
+
|
|
4813
|
+
dompurify/dist/purify.es.mjs:
|
|
4814
|
+
(*! @license DOMPurify 3.2.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.0/LICENSE *)
|
|
4815
|
+
*/
|
|
4816
|
+
//# sourceMappingURL=main.js.map
|