@aiquants/markdown-explorer 0.0.0-stage → 0.2.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/CHANGELOG.md +121 -0
- package/LICENSE +21 -0
- package/README.md +606 -2
- package/dist/ExplorerSplit-D2plJRRe.cjs +1 -0
- package/dist/ExplorerSplit-DB37U7Kh.js +1362 -0
- package/dist/MultiLayout-DCPDu1-l.js +909 -0
- package/dist/MultiLayout-UyWxs6Xy.cjs +1 -0
- package/dist/TreeLayout-BoyFelK2.js +22 -0
- package/dist/TreeLayout-CLWbWjOc.cjs +1 -0
- package/dist/client/MarkdownExplorer.d.cts +30 -0
- package/dist/client/MarkdownExplorer.d.ts +30 -0
- package/dist/client/storage.d.cts +11 -0
- package/dist/client/storage.d.ts +11 -0
- package/dist/core/config.d.cts +77 -0
- package/dist/core/config.d.ts +77 -0
- package/dist/core/contracts.d.cts +155 -0
- package/dist/core/contracts.d.ts +155 -0
- package/dist/core/geometry.d.cts +51 -0
- package/dist/core/geometry.d.ts +51 -0
- package/dist/core/labels.d.cts +180 -0
- package/dist/core/labels.d.ts +180 -0
- package/dist/core/location.d.cts +82 -0
- package/dist/core/location.d.ts +82 -0
- package/dist/core/paths.d.cts +17 -0
- package/dist/core/paths.d.ts +17 -0
- package/dist/core/revalidation.d.cts +2 -0
- package/dist/core/revalidation.d.ts +2 -0
- package/dist/core/theme.d.cts +6 -0
- package/dist/core/theme.d.ts +6 -0
- package/dist/core/treeSettings.d.cts +48 -0
- package/dist/core/treeSettings.d.ts +48 -0
- package/dist/core/viewSettings.d.cts +15 -0
- package/dist/core/viewSettings.d.ts +15 -0
- package/dist/dataRoute-Bkfwfp1R.js +194 -0
- package/dist/dataRoute-CKbdwkQ7.cjs +1 -0
- package/dist/index-BfuAu2GV.js +2107 -0
- package/dist/index-Cx3-jSna.cjs +3 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +44 -0
- package/dist/location-Oo21lTyV.cjs +1 -0
- package/dist/location-fVl2R1ee.js +395 -0
- package/dist/sanitizeWorker.cjs +1 -0
- package/dist/sanitizeWorker.js +281 -0
- package/dist/server/anonymous.d.cts +10 -0
- package/dist/server/anonymous.d.ts +10 -0
- package/dist/server/createMarkdownExplorer.d.cts +12 -0
- package/dist/server/createMarkdownExplorer.d.ts +12 -0
- package/dist/server/errors.d.cts +26 -0
- package/dist/server/errors.d.ts +26 -0
- package/dist/server/fileSystemSource.d.cts +38 -0
- package/dist/server/fileSystemSource.d.ts +38 -0
- package/dist/server/ports.d.cts +107 -0
- package/dist/server/ports.d.ts +107 -0
- package/dist/server/sanitizeProtocol.d.cts +17 -0
- package/dist/server/sanitizeProtocol.d.ts +17 -0
- package/dist/server/sanitizer.d.cts +51 -0
- package/dist/server/sanitizer.d.ts +51 -0
- package/dist/server/workerPool.d.cts +42 -0
- package/dist/server/workerPool.d.ts +42 -0
- package/dist/server.cjs +1 -0
- package/dist/server.d.cts +7 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +1253 -0
- package/dist/storage-Bkb2Axdi.cjs +1 -0
- package/dist/storage-DA4ItHzl.js +60 -0
- package/dist/storage.cjs +1 -0
- package/dist/storage.d.cts +1 -0
- package/dist/storage.d.ts +1 -0
- package/dist/storage.js +4 -0
- package/dist/styles/markdown-explorer.css +3 -0
- package/docs/behavior/dom-hooks.md +95 -0
- package/docs/behavior/layout.md +215 -0
- package/docs/behavior/localization.md +48 -0
- package/docs/behavior/security.md +138 -0
- package/docs/behavior/url-contract.md +135 -0
- package/package.json +141 -3
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { parentPort as ee } from "node:worker_threads";
|
|
2
|
+
import { defaultTreeAdapter as b, parse as te, html as re } from "parse5";
|
|
3
|
+
import { allowedFrameUrlOf as ne, reduceFrameFeatures as oe, EMBED_FRAME_REFERRER_POLICIES as se, EMBED_LINK_ATTRIBUTE as H, placeholderLinkOf as ae } from "@aiquants/markdown/embeds";
|
|
4
|
+
import S from "isomorphic-dompurify";
|
|
5
|
+
import { i as ie, e as ce } from "./dataRoute-Bkfwfp1R.js";
|
|
6
|
+
const F = 512, le = 4096, G = 1024, j = /* @__PURE__ */ Symbol("placement"), V = 2, ue = 3, fe = /* @__PURE__ */ new Set(["html", "head", "body"]), w = 0, D = 1, M = 2, W = 3, z = 4, O = 5, k = 6, N = 7, Q = 8, I = 9, $ = 10, _ = -1, de = 0, pe = 1, Ee = 2, me = 3, he = 4, Te = 5, K = 6, Ae = 7, ge = (t) => {
|
|
7
|
+
switch (t) {
|
|
8
|
+
case 9:
|
|
9
|
+
case 10:
|
|
10
|
+
case 12:
|
|
11
|
+
case 13:
|
|
12
|
+
case 32:
|
|
13
|
+
return de;
|
|
14
|
+
case 47:
|
|
15
|
+
return pe;
|
|
16
|
+
case 62:
|
|
17
|
+
return Ee;
|
|
18
|
+
case 61:
|
|
19
|
+
return me;
|
|
20
|
+
case 34:
|
|
21
|
+
return he;
|
|
22
|
+
case 39:
|
|
23
|
+
return Te;
|
|
24
|
+
default:
|
|
25
|
+
return K;
|
|
26
|
+
}
|
|
27
|
+
}, Se = Uint8Array.from({ length: 128 }, (t, i) => ge(i)), v = 60, X = 47, _e = /[\t\n\f\r />="'<]/g, we = /["<]/g, be = /['<]/g, A = -1 - M, Ne = Int8Array.from(
|
|
28
|
+
[
|
|
29
|
+
[D, I, _, w, w, w, w],
|
|
30
|
+
[D, I, _, A, A, A, A],
|
|
31
|
+
[W, I, _, z, M, M, M],
|
|
32
|
+
[W, I, _, z, A, A, A],
|
|
33
|
+
[z, N, _, N, O, k, N],
|
|
34
|
+
[O, O, O, O, Q, O, O],
|
|
35
|
+
[k, k, k, k, k, Q, k],
|
|
36
|
+
[D, N, _, N, N, N, N],
|
|
37
|
+
[D, I, _, A, A, A, A],
|
|
38
|
+
[D, I, _, A, A, A, A]
|
|
39
|
+
].flat()
|
|
40
|
+
), J = (t) => t >= 65 && t <= 90 || t >= 97 && t <= 122, Oe = (t, i) => {
|
|
41
|
+
let u = new Int32Array($), f = new Int32Array($), c = 0, h = 0, n = 0;
|
|
42
|
+
for (; n < t.length; ) {
|
|
43
|
+
if (c === 0) {
|
|
44
|
+
const p = t.indexOf("<", n);
|
|
45
|
+
if (p < 0) return !1;
|
|
46
|
+
n = p + 1, t.charCodeAt(n) === X && (n += 1), J(t.charCodeAt(n)) && (u[w] = 0, c = 1 << w, n += 1);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if ((c & -230) === 0 && n >= h) {
|
|
50
|
+
const p = c === 1 << O ? we : c === 1 << k ? be : _e;
|
|
51
|
+
p.lastIndex = n;
|
|
52
|
+
const a = p.exec(t);
|
|
53
|
+
if (a === null) return !1;
|
|
54
|
+
n = a.index, t.charCodeAt(n) === v && (h = n + 3);
|
|
55
|
+
}
|
|
56
|
+
const m = t.charCodeAt(n), g = m < 128 ? Se[m] : K;
|
|
57
|
+
let T = 0;
|
|
58
|
+
for (let p = c; p !== 0; p &= p - 1) {
|
|
59
|
+
const a = 31 - Math.clz32(p & -p);
|
|
60
|
+
let r = Ne[a * Ae + g], o = u[a];
|
|
61
|
+
if (r < _ && (r = -1 - r, o += 1, o > i))
|
|
62
|
+
return !0;
|
|
63
|
+
if (r === _) continue;
|
|
64
|
+
const E = 1 << r;
|
|
65
|
+
((T & E) === 0 || o > f[r]) && (f[r] = o), T |= E;
|
|
66
|
+
}
|
|
67
|
+
J(m) && (t.charCodeAt(n - 1) === v || t.charCodeAt(n - 1) === X && t.charCodeAt(n - 2) === v) && (T & 1 << w) === 0 && (f[w] = 0, T |= 1 << w);
|
|
68
|
+
const L = u;
|
|
69
|
+
u = f, f = L, c = T, n += 1;
|
|
70
|
+
}
|
|
71
|
+
return !1;
|
|
72
|
+
};
|
|
73
|
+
class C extends Error {
|
|
74
|
+
status;
|
|
75
|
+
constructor(i) {
|
|
76
|
+
super(`[markdown-explorer] the nesting guard stopped the parse: ${i}`), this.name = "ParseStop", this.status = i;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const ke = (t, i, u, f) => {
|
|
80
|
+
if (!Number.isSafeInteger(i) || i <= 0) throw new RangeError(`[markdown-explorer] the nesting guard's depth limit must be a positive safe integer; got ${String(i)}`);
|
|
81
|
+
if (!Number.isSafeInteger(u) || u <= 0) throw new RangeError(`[markdown-explorer] the nesting guard's node budget must be a positive safe integer; got ${String(u)}`);
|
|
82
|
+
if (!Number.isSafeInteger(f) || f <= 0) throw new RangeError(`[markdown-explorer] the nesting guard's attribute character budget must be a positive safe integer; got ${String(f)}`);
|
|
83
|
+
if (Oe(t, G)) return { status: "too-many-attributes" };
|
|
84
|
+
const c = i + V, h = t.length + ue;
|
|
85
|
+
let n = null, m = 0, g = 0, T = 0, L = 0;
|
|
86
|
+
const p = (e) => {
|
|
87
|
+
if (g += e, g > u) throw new C("too-many-nodes");
|
|
88
|
+
}, a = (e) => {
|
|
89
|
+
for (const s of e) L += s.name.length + s.value.length;
|
|
90
|
+
if (L > f) throw new C("too-many-attribute-characters");
|
|
91
|
+
}, r = (e, s, l, d) => {
|
|
92
|
+
const P = { node: e, above: null, levels: s, height: 0, keepsChildren: l, attributes: d, first: null, next: null, previous: null }, Z = e;
|
|
93
|
+
return Z[j] = P, P;
|
|
94
|
+
}, o = (e) => {
|
|
95
|
+
const l = e[j];
|
|
96
|
+
if (l === void 0) throw new Error("[markdown-explorer] the nesting guard was handed a node it did not create");
|
|
97
|
+
return l;
|
|
98
|
+
}, E = (e, s) => {
|
|
99
|
+
let l = s;
|
|
100
|
+
for (let d = e; d !== null && l > d.height; d = d.above)
|
|
101
|
+
d.height = l, l += d.levels;
|
|
102
|
+
if (n !== null && n.height > c) throw new C("too-deep");
|
|
103
|
+
}, y = (e) => {
|
|
104
|
+
const s = e.above;
|
|
105
|
+
s?.keepsChildren && (e.previous === null ? s.first = e.next : e.previous.next = e.next, e.next !== null && (e.next.previous = e.previous)), e.above = null, e.next = null, e.previous = null;
|
|
106
|
+
}, R = (e, s) => {
|
|
107
|
+
if (!b.isElementNode(s)) return;
|
|
108
|
+
const l = o(s), d = o(e);
|
|
109
|
+
y(l), l.above = d, d.keepsChildren && (l.next = d.first, d.first !== null && (d.first.previous = l), d.first = l), s.parentNode = e, E(d, l.height + l.levels);
|
|
110
|
+
}, x = {
|
|
111
|
+
...b,
|
|
112
|
+
createDocument: () => {
|
|
113
|
+
const e = b.createDocument();
|
|
114
|
+
return n = r(e, 0, !1, 0), e;
|
|
115
|
+
},
|
|
116
|
+
createDocumentFragment: () => {
|
|
117
|
+
const e = b.createDocumentFragment();
|
|
118
|
+
return r(e, 0, !1, 0), e;
|
|
119
|
+
},
|
|
120
|
+
createElement: (e, s, l) => {
|
|
121
|
+
if (m += 1, m > h) throw new C("too-many-elements");
|
|
122
|
+
p(1 + l.length), a(l);
|
|
123
|
+
const d = b.createElement(e, s, l);
|
|
124
|
+
return r(d, 1, !(s === re.NS.HTML && fe.has(e)), l.length), d;
|
|
125
|
+
},
|
|
126
|
+
createCommentNode: (e) => {
|
|
127
|
+
if (T += 1, T > le) throw new C("too-many-comments");
|
|
128
|
+
return p(1), b.createCommentNode(e);
|
|
129
|
+
},
|
|
130
|
+
setTemplateContent: (e, s) => {
|
|
131
|
+
b.setTemplateContent(e, s);
|
|
132
|
+
const l = o(s);
|
|
133
|
+
l.above = o(e), E(l.above, l.height);
|
|
134
|
+
},
|
|
135
|
+
appendChild: R,
|
|
136
|
+
insertBefore: (e, s) => R(e, s),
|
|
137
|
+
detachNode: (e) => {
|
|
138
|
+
b.isElementNode(e) && (y(o(e)), e.parentNode = null);
|
|
139
|
+
},
|
|
140
|
+
getFirstChild: (e) => {
|
|
141
|
+
const s = o(e);
|
|
142
|
+
if (!s.keepsChildren) throw new Error("[markdown-explorer] the nesting guard was asked for the children of a node whose children it does not keep");
|
|
143
|
+
return s.first === null ? null : s.first.node;
|
|
144
|
+
},
|
|
145
|
+
getChildNodes: () => {
|
|
146
|
+
throw new Error("[markdown-explorer] the nesting guard keeps no child lists for source locations");
|
|
147
|
+
},
|
|
148
|
+
onItemPop: (e) => {
|
|
149
|
+
const s = o(e);
|
|
150
|
+
s.keepsChildren = !1, s.first = null;
|
|
151
|
+
},
|
|
152
|
+
insertText: () => {
|
|
153
|
+
},
|
|
154
|
+
insertTextBefore: () => {
|
|
155
|
+
},
|
|
156
|
+
adoptAttributes: (e, s) => {
|
|
157
|
+
p(s.length), a(s);
|
|
158
|
+
const l = o(e);
|
|
159
|
+
if (l.attributes += s.length, l.attributes > G) throw new C("too-many-attributes");
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
let B;
|
|
163
|
+
try {
|
|
164
|
+
B = te(t, { scriptingEnabled: !1, treeAdapter: x });
|
|
165
|
+
} catch (e) {
|
|
166
|
+
if (e instanceof C) return { status: e.status };
|
|
167
|
+
throw e;
|
|
168
|
+
}
|
|
169
|
+
return { status: "within", depth: o(B).height - V, elements: m, nodes: g };
|
|
170
|
+
};
|
|
171
|
+
class Y extends RangeError {
|
|
172
|
+
constructor(i) {
|
|
173
|
+
super(`[markdown-explorer] the HTML nests deeper than ${String(i)} levels`), this.name = "HtmlNestingLimitError";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const Ce = ["script", "style", "noscript", "template", "form", "button", "textarea", "select", "option", "object", "embed", "base", "meta", "link", "frame", "frameset"], Le = ["srcdoc", "formaction"], Re = /^(?:(?:https?|mailto|tel):|[^a-z]|[a-z+.-]+(?:[^a-z+.:-]|$))/iu, Ie = /* @__PURE__ */ new Set(["left", "right", "center", "justify", "start", "end"]), ye = /* @__PURE__ */ new Set(["h1", "h2", "h3", "h4", "h5", "h6"]), U = 1, De = 3, Me = 7, Ue = 8, q = Object.freeze({
|
|
177
|
+
a: Object.freeze(["target", "data-link-kind", "data-doc-path", "data-doc-exists", "data-footnote-ref", "data-footnote-backref"]),
|
|
178
|
+
area: Object.freeze(["target"]),
|
|
179
|
+
img: Object.freeze(["data-asset-src", "data-asset-exists"]),
|
|
180
|
+
section: Object.freeze(["data-footnotes"]),
|
|
181
|
+
iframe: Object.freeze(["allow", "allowfullscreen", "frameborder", "referrerpolicy"])
|
|
182
|
+
}), xe = [...new Set(Object.values(q).flatMap((t) => t.filter((i) => i.startsWith("data-"))))], He = /* @__PURE__ */ new Set(["src", "href", "xlink:href"]), ze = /^data:/iu, ve = Object.freeze({
|
|
183
|
+
FORBID_TAGS: Ce,
|
|
184
|
+
FORBID_ATTR: Le,
|
|
185
|
+
ADD_TAGS: ["iframe"],
|
|
186
|
+
ADD_ATTR: (t, i) => q[i]?.includes(t) === !0,
|
|
187
|
+
ADD_URI_SAFE_ATTR: xe,
|
|
188
|
+
ALLOWED_URI_REGEXP: Re,
|
|
189
|
+
ALLOW_DATA_ATTR: !1
|
|
190
|
+
}), Be = (t) => {
|
|
191
|
+
let i = null;
|
|
192
|
+
for (const u of t.split(";")) {
|
|
193
|
+
const f = u.indexOf(":");
|
|
194
|
+
if (f < 0 || u.slice(0, f).trim().toLowerCase() !== "text-align") continue;
|
|
195
|
+
const c = u.slice(f + 1).trim().toLowerCase();
|
|
196
|
+
Ie.has(c) && (i = c);
|
|
197
|
+
}
|
|
198
|
+
return i === null ? null : `text-align: ${i}`;
|
|
199
|
+
}, Pe = (t) => t.nodeType === De ? { kind: "text", text: t.nodeValue ?? "" } : t.nodeType === Ue || t.nodeType === Me ? { kind: "ignored" } : t.nodeType === U && t.nodeName.toLowerCase() === "a" ? { kind: "link", href: t.getAttribute("href") } : { kind: "element" }, Fe = (t) => {
|
|
200
|
+
const i = t.getAttribute("type");
|
|
201
|
+
return i !== null && i.trim().toLowerCase() === "checkbox" && t.hasAttribute("disabled");
|
|
202
|
+
}, Ge = (t, i, u) => {
|
|
203
|
+
if (S.isSupported !== !0) throw new Error("[markdown-explorer] the HTML sanitizer cannot run in this environment");
|
|
204
|
+
if (!Number.isSafeInteger(u) || u <= 0) throw new RangeError(`[markdown-explorer] the HTML sanitizer's depth limit must be a positive integer; got ${String(u)}`);
|
|
205
|
+
const f = Object.freeze([...t]);
|
|
206
|
+
let c = /* @__PURE__ */ new WeakMap(), h = /* @__PURE__ */ new WeakMap();
|
|
207
|
+
const n = (a) => {
|
|
208
|
+
const r = a.parentNode === null ? void 0 : c.get(a.parentNode), o = r === void 0 ? 0 : r + 1;
|
|
209
|
+
if (c.set(a, o), a.nodeType === U && o > u) throw new Y(u);
|
|
210
|
+
}, m = (a, r) => {
|
|
211
|
+
if (a.nodeType !== U) return;
|
|
212
|
+
const o = a;
|
|
213
|
+
(r.tagName === "input" && !Fe(o) || r.tagName === "iframe" && ne(o.getAttribute("src")?.trim(), f) === null) && o.parentNode?.removeChild(o);
|
|
214
|
+
}, g = (a, r) => {
|
|
215
|
+
const o = a.nodeName.toLowerCase();
|
|
216
|
+
if (r.attrName === "style") {
|
|
217
|
+
const E = Be(r.attrValue);
|
|
218
|
+
E === null ? r.keepAttr = !1 : r.attrValue = E;
|
|
219
|
+
} else if (r.attrName === "allow" && o === "iframe") {
|
|
220
|
+
const E = oe(r.attrValue);
|
|
221
|
+
E === null ? r.keepAttr = !1 : r.attrValue = E;
|
|
222
|
+
} else if (r.attrName === "referrerpolicy" && o === "iframe") {
|
|
223
|
+
const E = r.attrValue.trim().toLowerCase();
|
|
224
|
+
se.has(E) ? r.attrValue = E : r.keepAttr = !1;
|
|
225
|
+
} else r.attrName === H && o === "p" ? h.set(a, a.getAttribute(H)) : He.has(r.attrName) && ze.test(r.attrValue) ? r.keepAttr = !1 : r.attrName === "id" && ye.has(o) && (r.forceKeepAttr = !0);
|
|
226
|
+
}, T = (a) => {
|
|
227
|
+
const r = a.getAttribute("data-asset-src");
|
|
228
|
+
r !== null && (a.getAttribute("data-asset-exists") !== "false" && ie(r) ? a.setAttribute("src", ce(i, r, null)) : a.removeAttribute("src"));
|
|
229
|
+
}, L = (a) => {
|
|
230
|
+
const r = a.parentNode;
|
|
231
|
+
if (r === null) return;
|
|
232
|
+
const o = h.get(r);
|
|
233
|
+
o != null && ae(o, [...r.childNodes].map(Pe)) !== null && r.setAttribute(H, o);
|
|
234
|
+
}, p = (a) => {
|
|
235
|
+
if (a.nodeType !== U) return;
|
|
236
|
+
const r = a, o = r.nodeName.toLowerCase();
|
|
237
|
+
o === "img" ? T(r) : o === "a" && L(r);
|
|
238
|
+
const E = r.getAttribute("target");
|
|
239
|
+
if (E === null || E.trim().toLowerCase() === "_self") return;
|
|
240
|
+
const y = r.getAttribute("rel"), R = new Set(y === null ? [] : y.split(/\s+/u).filter((x) => x !== ""));
|
|
241
|
+
R.add("noopener"), R.add("noreferrer"), r.setAttribute("rel", [...R].join(" "));
|
|
242
|
+
};
|
|
243
|
+
return (a) => {
|
|
244
|
+
c = /* @__PURE__ */ new WeakMap(), h = /* @__PURE__ */ new WeakMap(), S.addHook("beforeSanitizeElements", n), S.addHook("uponSanitizeElement", m), S.addHook("uponSanitizeAttribute", g), S.addHook("afterSanitizeAttributes", p);
|
|
245
|
+
try {
|
|
246
|
+
return S.sanitize(a, ve);
|
|
247
|
+
} finally {
|
|
248
|
+
S.removeHook("beforeSanitizeElements", n), S.removeHook("uponSanitizeElement", m), S.removeHook("uponSanitizeAttribute", g), S.removeHook("afterSanitizeAttributes", p);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}, je = (t) => {
|
|
252
|
+
if (t === null || typeof t != "object") return !1;
|
|
253
|
+
const { id: i, input: u } = t;
|
|
254
|
+
if (typeof i != "number" || u === null || typeof u != "object") return !1;
|
|
255
|
+
const { html: f, iframeHosts: c, dataPath: h, maxNodes: n, maxAttributeChars: m, maxSanitizedChars: g } = u;
|
|
256
|
+
return typeof f == "string" && Array.isArray(c) && c.every((T) => typeof T == "string") && typeof h == "string" && h !== "" && [n, m, g].every((T) => Number.isSafeInteger(T) && T > 0);
|
|
257
|
+
}, Ve = () => {
|
|
258
|
+
const t = ee;
|
|
259
|
+
if (t === null) throw new Error("[markdown-explorer] the sanitizer worker must run in a worker thread");
|
|
260
|
+
const i = /* @__PURE__ */ new Map();
|
|
261
|
+
t.on("message", (u) => {
|
|
262
|
+
if (!je(u)) throw new TypeError("[markdown-explorer] the sanitizer worker received a message that is not a job");
|
|
263
|
+
const { id: f, input: c } = u;
|
|
264
|
+
let h;
|
|
265
|
+
try {
|
|
266
|
+
if (ke(c.html, F, c.maxNodes, c.maxAttributeChars).status !== "within") {
|
|
267
|
+
t.postMessage({ id: f, output: { status: "too-large" } });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const n = JSON.stringify([c.iframeHosts, c.dataPath]);
|
|
271
|
+
let m = i.get(n);
|
|
272
|
+
m === void 0 && (m = Ge(c.iframeHosts, c.dataPath, F), i.set(n, m));
|
|
273
|
+
const g = m(c.html);
|
|
274
|
+
h = g.length > c.maxSanitizedChars ? { status: "too-large" } : { status: "sanitized", html: g };
|
|
275
|
+
} catch (n) {
|
|
276
|
+
h = n instanceof Y ? { status: "too-large" } : { status: "error", message: n instanceof Error ? `${n.name}: ${n.message}` : String(n) };
|
|
277
|
+
}
|
|
278
|
+
t.postMessage({ id: f, output: h });
|
|
279
|
+
}), t.postMessage({ ready: !0 });
|
|
280
|
+
};
|
|
281
|
+
Ve();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ExplorerAuthentication } from "./ports.cjs";
|
|
2
|
+
export type AnonymousPrincipal = {
|
|
3
|
+
readonly kind: "anonymous";
|
|
4
|
+
};
|
|
5
|
+
export type AnonymousAccess = {
|
|
6
|
+
readonly authenticate: (request: Request) => Promise<ExplorerAuthentication<AnonymousPrincipal>>;
|
|
7
|
+
readonly principalKey: (principal: AnonymousPrincipal) => string;
|
|
8
|
+
readonly signedOutPage: (request: Request, headers: Headers) => Response;
|
|
9
|
+
};
|
|
10
|
+
export declare const anonymousAccess: AnonymousAccess;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ExplorerAuthentication } from "./ports.js";
|
|
2
|
+
export type AnonymousPrincipal = {
|
|
3
|
+
readonly kind: "anonymous";
|
|
4
|
+
};
|
|
5
|
+
export type AnonymousAccess = {
|
|
6
|
+
readonly authenticate: (request: Request) => Promise<ExplorerAuthentication<AnonymousPrincipal>>;
|
|
7
|
+
readonly principalKey: (principal: AnonymousPrincipal) => string;
|
|
8
|
+
readonly signedOutPage: (request: Request, headers: Headers) => Response;
|
|
9
|
+
};
|
|
10
|
+
export declare const anonymousAccess: AnonymousAccess;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { HeadersFunction } from "react-router";
|
|
2
|
+
import { MarkdownExplorerServer, MarkdownExplorerServerConfig } from "./ports.cjs";
|
|
3
|
+
export declare const DEFAULT_DEFERRED_TIMEOUT_MS = 4000;
|
|
4
|
+
export declare const DEFAULT_TREE_CACHE: Readonly<{
|
|
5
|
+
ttlMs: 300000;
|
|
6
|
+
maxEntries: 64;
|
|
7
|
+
maxBytes: number;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const MIN_SCOPE_SECRET_LENGTH = 32;
|
|
10
|
+
export declare const EXPLORER_DATA_RESPONSE_HEADERS: Readonly<Record<string, string>>;
|
|
11
|
+
export declare const carryLoaderHeaders: HeadersFunction;
|
|
12
|
+
export declare const createMarkdownExplorer: <P>(serverConfig: MarkdownExplorerServerConfig<P>) => MarkdownExplorerServer;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { HeadersFunction } from "react-router";
|
|
2
|
+
import { MarkdownExplorerServer, MarkdownExplorerServerConfig } from "./ports.js";
|
|
3
|
+
export declare const DEFAULT_DEFERRED_TIMEOUT_MS = 4000;
|
|
4
|
+
export declare const DEFAULT_TREE_CACHE: Readonly<{
|
|
5
|
+
ttlMs: 300000;
|
|
6
|
+
maxEntries: 64;
|
|
7
|
+
maxBytes: number;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const MIN_SCOPE_SECRET_LENGTH = 32;
|
|
10
|
+
export declare const EXPLORER_DATA_RESPONSE_HEADERS: Readonly<Record<string, string>>;
|
|
11
|
+
export declare const carryLoaderHeaders: HeadersFunction;
|
|
12
|
+
export declare const createMarkdownExplorer: <P>(serverConfig: MarkdownExplorerServerConfig<P>) => MarkdownExplorerServer;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ExplorerFailure, ExplorerFailureReason } from "../core/contracts.cjs";
|
|
2
|
+
export type ExplorerOperation = "authenticate" | "tree" | "children" | "document" | "notices" | "refresh" | "boundary" | "asset";
|
|
3
|
+
export type ExplorerLogDetail = {
|
|
4
|
+
readonly operation: ExplorerOperation;
|
|
5
|
+
readonly sourceId?: string;
|
|
6
|
+
readonly path?: string;
|
|
7
|
+
readonly error: unknown;
|
|
8
|
+
};
|
|
9
|
+
export type ExplorerLogger = {
|
|
10
|
+
readonly error: (message: string, detail: ExplorerLogDetail) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare const DEFAULT_EXPLORER_LOGGER: ExplorerLogger;
|
|
13
|
+
export declare class ExplorerError extends Error {
|
|
14
|
+
readonly reason: ExplorerFailureReason;
|
|
15
|
+
readonly code: string | undefined;
|
|
16
|
+
constructor(reason: ExplorerFailureReason, options?: {
|
|
17
|
+
readonly code?: string;
|
|
18
|
+
readonly cause?: unknown;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export declare const isAbortError: (error: unknown) => boolean;
|
|
22
|
+
export declare const reportFailure: (logger: ExplorerLogger, detail: ExplorerLogDetail) => void;
|
|
23
|
+
export declare const reportDroppedEntry: (logger: ExplorerLogger, detail: ExplorerLogDetail) => void;
|
|
24
|
+
export declare const reportDeniedRequest: (logger: ExplorerLogger, detail: Omit<ExplorerLogDetail, "error">) => void;
|
|
25
|
+
export declare const observeRejection: (answer: unknown, report: (error: unknown) => void) => void;
|
|
26
|
+
export declare const failureFromError: (error: unknown, detail: Omit<ExplorerLogDetail, "error">, signal: AbortSignal, logger: ExplorerLogger) => ExplorerFailure;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ExplorerFailure, ExplorerFailureReason } from "../core/contracts.js";
|
|
2
|
+
export type ExplorerOperation = "authenticate" | "tree" | "children" | "document" | "notices" | "refresh" | "boundary" | "asset";
|
|
3
|
+
export type ExplorerLogDetail = {
|
|
4
|
+
readonly operation: ExplorerOperation;
|
|
5
|
+
readonly sourceId?: string;
|
|
6
|
+
readonly path?: string;
|
|
7
|
+
readonly error: unknown;
|
|
8
|
+
};
|
|
9
|
+
export type ExplorerLogger = {
|
|
10
|
+
readonly error: (message: string, detail: ExplorerLogDetail) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare const DEFAULT_EXPLORER_LOGGER: ExplorerLogger;
|
|
13
|
+
export declare class ExplorerError extends Error {
|
|
14
|
+
readonly reason: ExplorerFailureReason;
|
|
15
|
+
readonly code: string | undefined;
|
|
16
|
+
constructor(reason: ExplorerFailureReason, options?: {
|
|
17
|
+
readonly code?: string;
|
|
18
|
+
readonly cause?: unknown;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export declare const isAbortError: (error: unknown) => boolean;
|
|
22
|
+
export declare const reportFailure: (logger: ExplorerLogger, detail: ExplorerLogDetail) => void;
|
|
23
|
+
export declare const reportDroppedEntry: (logger: ExplorerLogger, detail: ExplorerLogDetail) => void;
|
|
24
|
+
export declare const reportDeniedRequest: (logger: ExplorerLogger, detail: Omit<ExplorerLogDetail, "error">) => void;
|
|
25
|
+
export declare const observeRejection: (answer: unknown, report: (error: unknown) => void) => void;
|
|
26
|
+
export declare const failureFromError: (error: unknown, detail: Omit<ExplorerLogDetail, "error">, signal: AbortSignal, logger: ExplorerLogger) => ExplorerFailure;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ExplorerFailureReason, ExplorerHeading } from "../core/contracts.cjs";
|
|
2
|
+
import { ExplorerPathRules } from "../core/paths.cjs";
|
|
3
|
+
import { ExplorerAssetFile, ExplorerHostDocument, ExplorerSource, ExplorerSourceContext } from "./ports.cjs";
|
|
4
|
+
export type FileSystemSkipReason = "not-nfc" | "colon" | "trailing-dot-or-space" | "control-character-or-backslash" | "path-too-long" | "symbolic-link" | "special-file" | "unreadable-directory";
|
|
5
|
+
export type FileSystemSkippedEntry = {
|
|
6
|
+
readonly sourceId: string;
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly reason: FileSystemSkipReason;
|
|
9
|
+
};
|
|
10
|
+
export type FileSystemSourceOptions = {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly root: string;
|
|
14
|
+
readonly pathPrefix: string;
|
|
15
|
+
readonly parse: (markdown: string, docPath: string) => Promise<{
|
|
16
|
+
readonly htmlContent: string;
|
|
17
|
+
readonly headings: readonly ExplorerHeading[];
|
|
18
|
+
}>;
|
|
19
|
+
readonly deny?: ExplorerPathRules | false;
|
|
20
|
+
readonly maxEntries?: number;
|
|
21
|
+
readonly maxDocumentBytes?: number;
|
|
22
|
+
readonly textPreviewBytes?: number;
|
|
23
|
+
readonly walkConcurrency?: number;
|
|
24
|
+
readonly cacheScope?: "principal" | "shared";
|
|
25
|
+
readonly reportSkipped?: (skipped: FileSystemSkippedEntry) => void;
|
|
26
|
+
};
|
|
27
|
+
export type FileSystemSource = ExplorerSource<unknown> & {
|
|
28
|
+
readonly document: (context: ExplorerSourceContext<unknown>, path: string, readPath: string) => Promise<ExplorerHostDocument>;
|
|
29
|
+
readonly asset: (context: ExplorerSourceContext<unknown>, path: string, readPath: string) => Promise<ExplorerAssetFile>;
|
|
30
|
+
};
|
|
31
|
+
export declare const FILE_SYSTEM_SOURCE_DEFAULTS: Readonly<{
|
|
32
|
+
maxEntries: 20000;
|
|
33
|
+
maxDocumentBytes: number;
|
|
34
|
+
textPreviewBytes: number;
|
|
35
|
+
walkConcurrency: 8;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const reasonOfFileError: (error: unknown) => ExplorerFailureReason | null;
|
|
38
|
+
export declare const createFileSystemSource: (options: FileSystemSourceOptions) => FileSystemSource;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ExplorerFailureReason, ExplorerHeading } from "../core/contracts.js";
|
|
2
|
+
import { ExplorerPathRules } from "../core/paths.js";
|
|
3
|
+
import { ExplorerAssetFile, ExplorerHostDocument, ExplorerSource, ExplorerSourceContext } from "./ports.js";
|
|
4
|
+
export type FileSystemSkipReason = "not-nfc" | "colon" | "trailing-dot-or-space" | "control-character-or-backslash" | "path-too-long" | "symbolic-link" | "special-file" | "unreadable-directory";
|
|
5
|
+
export type FileSystemSkippedEntry = {
|
|
6
|
+
readonly sourceId: string;
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly reason: FileSystemSkipReason;
|
|
9
|
+
};
|
|
10
|
+
export type FileSystemSourceOptions = {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly root: string;
|
|
14
|
+
readonly pathPrefix: string;
|
|
15
|
+
readonly parse: (markdown: string, docPath: string) => Promise<{
|
|
16
|
+
readonly htmlContent: string;
|
|
17
|
+
readonly headings: readonly ExplorerHeading[];
|
|
18
|
+
}>;
|
|
19
|
+
readonly deny?: ExplorerPathRules | false;
|
|
20
|
+
readonly maxEntries?: number;
|
|
21
|
+
readonly maxDocumentBytes?: number;
|
|
22
|
+
readonly textPreviewBytes?: number;
|
|
23
|
+
readonly walkConcurrency?: number;
|
|
24
|
+
readonly cacheScope?: "principal" | "shared";
|
|
25
|
+
readonly reportSkipped?: (skipped: FileSystemSkippedEntry) => void;
|
|
26
|
+
};
|
|
27
|
+
export type FileSystemSource = ExplorerSource<unknown> & {
|
|
28
|
+
readonly document: (context: ExplorerSourceContext<unknown>, path: string, readPath: string) => Promise<ExplorerHostDocument>;
|
|
29
|
+
readonly asset: (context: ExplorerSourceContext<unknown>, path: string, readPath: string) => Promise<ExplorerAssetFile>;
|
|
30
|
+
};
|
|
31
|
+
export declare const FILE_SYSTEM_SOURCE_DEFAULTS: Readonly<{
|
|
32
|
+
maxEntries: 20000;
|
|
33
|
+
maxDocumentBytes: number;
|
|
34
|
+
textPreviewBytes: number;
|
|
35
|
+
walkConcurrency: 8;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const reasonOfFileError: (error: unknown) => ExplorerFailureReason | null;
|
|
38
|
+
export declare const createFileSystemSource: (options: FileSystemSourceOptions) => FileSystemSource;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ActionFunctionArgs, data, HeadersFunction, LoaderFunctionArgs } from "react-router";
|
|
2
|
+
import { ExplorerConfig } from "../core/config.cjs";
|
|
3
|
+
import { ExplorerEntry, ExplorerFailureDocument, ExplorerHeading, ExplorerMediaType, ExplorerNotice, ExplorerPageData } from "../core/contracts.cjs";
|
|
4
|
+
import { ExplorerPathRules } from "../core/paths.cjs";
|
|
5
|
+
import { ExplorerLogger } from "./errors.cjs";
|
|
6
|
+
import { HtmlSanitizerOptions } from "./sanitizer.cjs";
|
|
7
|
+
export type ExplorerAuthentication<P> = {
|
|
8
|
+
readonly status: "signed-in";
|
|
9
|
+
readonly principal: P;
|
|
10
|
+
readonly headers?: HeadersInit;
|
|
11
|
+
} | {
|
|
12
|
+
readonly status: "signed-out";
|
|
13
|
+
readonly headers?: HeadersInit;
|
|
14
|
+
} | {
|
|
15
|
+
readonly status: "forbidden";
|
|
16
|
+
readonly headers?: HeadersInit;
|
|
17
|
+
};
|
|
18
|
+
export type ExplorerSourceContext<P> = {
|
|
19
|
+
readonly principal: P;
|
|
20
|
+
readonly request: Request;
|
|
21
|
+
readonly signal: AbortSignal;
|
|
22
|
+
readonly refresh: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type ExplorerBoundaryAnswer = {
|
|
25
|
+
readonly inside: false;
|
|
26
|
+
} | {
|
|
27
|
+
readonly inside: true;
|
|
28
|
+
readonly readPath: string;
|
|
29
|
+
};
|
|
30
|
+
export type ExplorerSourceBoundary<P> = {
|
|
31
|
+
readonly contains: (path: string) => boolean;
|
|
32
|
+
readonly confirm?: (context: ExplorerSourceContext<P>, path: string) => Promise<ExplorerBoundaryAnswer>;
|
|
33
|
+
};
|
|
34
|
+
export type ExplorerSource<P> = {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly label: string | ((principal: P) => string);
|
|
37
|
+
readonly boundary: ExplorerSourceBoundary<P>;
|
|
38
|
+
readonly tree: (context: ExplorerSourceContext<P>) => Promise<readonly ExplorerEntry[]>;
|
|
39
|
+
readonly children?: (context: ExplorerSourceContext<P>, path: string, readPath: string) => Promise<readonly ExplorerEntry[]>;
|
|
40
|
+
readonly cacheScope?: "principal" | "shared";
|
|
41
|
+
};
|
|
42
|
+
export type ExplorerByteRange = {
|
|
43
|
+
readonly offset: number;
|
|
44
|
+
readonly length: number;
|
|
45
|
+
};
|
|
46
|
+
export type ExplorerAssetFile = {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly contentType: string;
|
|
49
|
+
readonly size: number | null;
|
|
50
|
+
readonly version: string;
|
|
51
|
+
readonly open: (range: ExplorerByteRange | null) => Promise<ReadableStream<Uint8Array>>;
|
|
52
|
+
};
|
|
53
|
+
export type ExplorerHostDocument = {
|
|
54
|
+
readonly kind: "markdown";
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly htmlContent: string;
|
|
57
|
+
readonly headings: readonly ExplorerHeading[];
|
|
58
|
+
} | {
|
|
59
|
+
readonly kind: "text";
|
|
60
|
+
readonly name: string;
|
|
61
|
+
readonly text: string;
|
|
62
|
+
readonly truncated: boolean;
|
|
63
|
+
readonly version: string;
|
|
64
|
+
} | {
|
|
65
|
+
readonly kind: "image";
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly version: string;
|
|
68
|
+
} | {
|
|
69
|
+
readonly kind: "media";
|
|
70
|
+
readonly name: string;
|
|
71
|
+
readonly mediaType: ExplorerMediaType;
|
|
72
|
+
readonly version: string;
|
|
73
|
+
} | {
|
|
74
|
+
readonly kind: "file";
|
|
75
|
+
readonly name: string;
|
|
76
|
+
readonly version: string;
|
|
77
|
+
} | ExplorerFailureDocument;
|
|
78
|
+
export type MarkdownExplorerServerConfig<P> = {
|
|
79
|
+
readonly config: ExplorerConfig;
|
|
80
|
+
readonly authenticate: (request: Request) => Promise<ExplorerAuthentication<P>>;
|
|
81
|
+
readonly principalKey: (principal: P) => string;
|
|
82
|
+
readonly scopeSecret: string;
|
|
83
|
+
readonly sources: readonly ExplorerSource<P>[];
|
|
84
|
+
readonly document: (context: ExplorerSourceContext<P>, path: string, readPath: string) => Promise<ExplorerHostDocument>;
|
|
85
|
+
readonly asset: (context: ExplorerSourceContext<P>, path: string, readPath: string) => Promise<ExplorerAssetFile>;
|
|
86
|
+
readonly signedOutPage: (request: Request, headers: Headers) => Response;
|
|
87
|
+
readonly forbiddenPage?: (request: Request, headers: Headers) => Response;
|
|
88
|
+
readonly notices?: (context: ExplorerSourceContext<P>) => Promise<readonly ExplorerNotice[]>;
|
|
89
|
+
readonly deny?: ExplorerPathRules | false;
|
|
90
|
+
readonly treeCache?: {
|
|
91
|
+
readonly ttlMs?: number;
|
|
92
|
+
readonly maxEntries?: number;
|
|
93
|
+
readonly maxBytes?: number;
|
|
94
|
+
} | false;
|
|
95
|
+
readonly sanitizer?: HtmlSanitizerOptions;
|
|
96
|
+
readonly deferredTimeoutMs?: number;
|
|
97
|
+
readonly logger?: ExplorerLogger;
|
|
98
|
+
readonly onTreeRefreshed?: (key: string) => void;
|
|
99
|
+
};
|
|
100
|
+
export type ExplorerPageLoaderResult = ReturnType<typeof data<ExplorerPageData>>;
|
|
101
|
+
export type MarkdownExplorerServer = {
|
|
102
|
+
readonly loader: (args: LoaderFunctionArgs) => Promise<ExplorerPageLoaderResult>;
|
|
103
|
+
readonly headers: HeadersFunction;
|
|
104
|
+
readonly dataLoader: (args: LoaderFunctionArgs) => Promise<Response>;
|
|
105
|
+
readonly dataAction: (args: ActionFunctionArgs) => Promise<Response>;
|
|
106
|
+
readonly forgetTree: (key: string) => void;
|
|
107
|
+
};
|