@zvndev/darkmown 2.12.2 → 2.12.3
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 +13 -0
- package/README.md +9 -0
- package/SECURITY.md +9 -2
- package/docs/cli.md +10 -0
- package/package.json +2 -2
- package/site/pages/playground/index.js +51 -51
- package/src/cli.js +23 -1
- package/src/compiler/format.js +3 -2
- package/src/compiler/frontmatter.js +2 -1
- package/src/compiler/markdown.js +61 -7
- package/src/dev.js +63 -0
- package/src/feeds.js +24 -9
- package/src/headers.js +8 -2
- package/src/image-variant-resize.js +6 -1
- package/src/router.js +4 -1
- package/src/runtime.js +16 -6
- package/src/runtime.min.js +1 -1
- package/src/runtime.min.js.map +1 -1
- package/src/scaffold.js +13 -5
- package/src/statics.js +16 -1
- package/types/dev.d.ts +28 -0
- package/types/feeds.d.ts +19 -26
- package/types/headers.d.ts +6 -1
package/src/cli.js
CHANGED
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
devClientPath,
|
|
17
17
|
devClientScript,
|
|
18
18
|
devEventsPath,
|
|
19
|
-
injectDevClient
|
|
19
|
+
injectDevClient,
|
|
20
|
+
refuseLocalRequest
|
|
20
21
|
} from "./dev.js";
|
|
21
22
|
import { createDispatcher, serveMcp } from "./mcp.js";
|
|
22
23
|
import { mcpConfig } from "./mcp-tools.js";
|
|
@@ -421,6 +422,7 @@ function startDevServer({ cwd, log, warn, error }) {
|
|
|
421
422
|
|
|
422
423
|
const apiDir = path.join(cwd, "api");
|
|
423
424
|
const server = http.createServer((req, res) => {
|
|
425
|
+
if (refuseForeign(req, res, host)) return;
|
|
424
426
|
const url = req.url || "/";
|
|
425
427
|
if (url.split("?")[0] === devEventsPath) {
|
|
426
428
|
res.writeHead(200, {
|
|
@@ -486,6 +488,25 @@ function startDevServer({ cwd, log, warn, error }) {
|
|
|
486
488
|
});
|
|
487
489
|
}
|
|
488
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Answer 403 when `refuseLocalRequest` rejects the request (DNS rebinding or a
|
|
493
|
+
* cross-site call into `api/`). Shared by `dev` and `serve`.
|
|
494
|
+
* @param {import("node:http").IncomingMessage} req
|
|
495
|
+
* @param {import("node:http").ServerResponse} res
|
|
496
|
+
* @param {string} bindHost
|
|
497
|
+
* @returns {boolean} true when the request was refused and answered
|
|
498
|
+
*/
|
|
499
|
+
function refuseForeign(req, res, bindHost) {
|
|
500
|
+
const reason = refuseLocalRequest(req, {
|
|
501
|
+
bindHost,
|
|
502
|
+
allowedHosts: process.env.DARKMOWN_ALLOWED_HOSTS || ""
|
|
503
|
+
});
|
|
504
|
+
if (!reason) return false;
|
|
505
|
+
res.writeHead(403, { "content-type": "text/plain; charset=utf-8" });
|
|
506
|
+
res.end(`${reason}\n`);
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
|
|
489
510
|
/**
|
|
490
511
|
* Start the static preview server for an existing `dist/`. Throws `CliError`
|
|
491
512
|
* when there is nothing built to serve.
|
|
@@ -510,6 +531,7 @@ function startPreviewServer({ cwd, log, error }) {
|
|
|
510
531
|
}
|
|
511
532
|
const apiDir = path.join(cwd, "api");
|
|
512
533
|
const server = http.createServer((req, res) => {
|
|
534
|
+
if (refuseForeign(req, res, host)) return;
|
|
513
535
|
const acceptEncoding = req.headers["accept-encoding"];
|
|
514
536
|
// Serverless parity, exactly as in dev: run a project api/*.js function when
|
|
515
537
|
// the path matches; otherwise serve static (compressed when negotiated).
|
package/src/compiler/format.js
CHANGED
|
@@ -67,8 +67,9 @@ const dateOnlyTZ = (v) =>
|
|
|
67
67
|
export const AGGREGATES = {
|
|
68
68
|
sum: (list, f) => nums(list, f || "").reduce((a, n) => a + n, 0),
|
|
69
69
|
avg: (list, f) => (list.length ? AGGREGATES.sum(list, f) / list.length : 0),
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// reduce, not spread: `Math.max(...list)` throws RangeError past ~100k items.
|
|
71
|
+
min: (list, f) => (list.length ? nums(list, f || "").reduce((a, n) => Math.min(a, n)) : 0),
|
|
72
|
+
max: (list, f) => (list.length ? nums(list, f || "").reduce((a, n) => Math.max(a, n)) : 0),
|
|
72
73
|
count: (list) => list.length
|
|
73
74
|
};
|
|
74
75
|
|
|
@@ -56,7 +56,8 @@ export function parseFrontmatter(raw, file) {
|
|
|
56
56
|
const quotedKeys = new Set();
|
|
57
57
|
for (const line of front.split("\n")) {
|
|
58
58
|
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
59
|
-
|
|
59
|
+
// `__proto__: [a]` would swap meta's prototype rather than set a key.
|
|
60
|
+
if (!match || match[1] === "__proto__") continue;
|
|
60
61
|
const parsed = parseFrontmatterValue(match[2]);
|
|
61
62
|
meta[match[1]] = parsed.value;
|
|
62
63
|
if (parsed.quoted) quotedKeys.add(match[1]);
|
package/src/compiler/markdown.js
CHANGED
|
@@ -11,6 +11,7 @@ import { highlightCode } from "../highlight.js";
|
|
|
11
11
|
import { LOOP_META, lineOf, recordRead, wdError } from "./context.js";
|
|
12
12
|
import { applyPipeline, fmtAttr, validatePipes } from "./format.js";
|
|
13
13
|
import { imageSize } from "./image-size.js";
|
|
14
|
+
import { isAllowedInclude } from "./includes.js";
|
|
14
15
|
import {
|
|
15
16
|
escapeHtml,
|
|
16
17
|
getPath,
|
|
@@ -487,11 +488,14 @@ function buildSrcset(src, ctx) {
|
|
|
487
488
|
return null;
|
|
488
489
|
}
|
|
489
490
|
const reader = ctx.comp.reader;
|
|
491
|
+
// Same sandbox as measureImage/@include: a data-driven `/../../x.png` must not
|
|
492
|
+
// probe (or read the header of) files outside site/pages and site/_.
|
|
493
|
+
if (!isAllowedInclude(path.join(root, `${relBase}${ext}`), ctx.context, reader)) return null;
|
|
490
494
|
/** @type {{ href: string, width: number }[]} */
|
|
491
495
|
const candidates = [];
|
|
492
496
|
for (const width of VARIANT_WIDTHS) {
|
|
493
497
|
const abs = path.join(root, `${relBase}@${width}${ext}`);
|
|
494
|
-
if (!reader.exists(abs)) continue;
|
|
498
|
+
if (!isAllowedInclude(abs, ctx.context, reader) || !reader.exists(abs)) continue;
|
|
495
499
|
candidates.push({ href: `${base}@${width}${ext}`, width: measureWidth(abs, reader) ?? width });
|
|
496
500
|
}
|
|
497
501
|
// Builder-generated variants (2.9.0): resized into dist by the pre-pass, so
|
|
@@ -681,8 +685,24 @@ function rawHtmlPlugin(mdInstance) {
|
|
|
681
685
|
// happened to resolve to. Escaping is no defense here: `href` and friends
|
|
682
686
|
// resolve `javascript:` without any quote breakout, and unlike a markdown
|
|
683
687
|
// destination nothing upstream vets an author's own raw `<a href="…">`.
|
|
684
|
-
const
|
|
685
|
-
|
|
688
|
+
const URL_ATTRS = new Set([
|
|
689
|
+
"href",
|
|
690
|
+
"src",
|
|
691
|
+
"action",
|
|
692
|
+
"formaction",
|
|
693
|
+
"xlink:href",
|
|
694
|
+
"data",
|
|
695
|
+
"poster",
|
|
696
|
+
"cite",
|
|
697
|
+
"background",
|
|
698
|
+
"ping"
|
|
699
|
+
]);
|
|
700
|
+
|
|
701
|
+
// The tag a fragment is inside of, as far as it has been written (no `>` yet),
|
|
702
|
+
// and within it the attribute whose value is open: name, then the value so far
|
|
703
|
+
// as group 2 ("…), 3 ('…) or 4 (unquoted).
|
|
704
|
+
const OPEN_TAG = /<[A-Za-z][^<>]*$/;
|
|
705
|
+
const OPEN_ATTR = /\s([^\s"'<>/=]+)\s*=\s*(?:"([^"]*)|'([^']*)|([^\s"'=<>`]*))$/;
|
|
686
706
|
|
|
687
707
|
/**
|
|
688
708
|
* Warn that a value in URL position carries a scheme the browser would execute.
|
|
@@ -698,6 +718,22 @@ function warnUnsafeUrl(ctx, index, expr) {
|
|
|
698
718
|
);
|
|
699
719
|
}
|
|
700
720
|
|
|
721
|
+
/**
|
|
722
|
+
* Warn that a value landed where HTML escaping cannot contain it (an event
|
|
723
|
+
* handler, `srcdoc`, or bare tag position), so it was dropped.
|
|
724
|
+
* @param {Ctx} ctx
|
|
725
|
+
* @param {number} index 0-based line index, for `file:line`.
|
|
726
|
+
* @param {string} expr
|
|
727
|
+
* @param {string} where
|
|
728
|
+
* @returns {void}
|
|
729
|
+
*/
|
|
730
|
+
function warnUnsafeSlot(ctx, index, expr, where) {
|
|
731
|
+
ctx.comp.warnings.push(
|
|
732
|
+
`${ctx.file}:${lineOf(ctx, index)}: "{ ${expr} }" sits ${where}, where escaping cannot stop a value from running as code, so it is emitted empty. ` +
|
|
733
|
+
`Put the value in a quoted attribute such as data-*, or render it as text, and read it from a colocated .js file.`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
701
737
|
/**
|
|
702
738
|
* Substitute every `{ expr }` in a raw-HTML fragment with its escaped value.
|
|
703
739
|
*
|
|
@@ -736,14 +772,32 @@ function fillRawHtml(html, ctx, index) {
|
|
|
736
772
|
continue;
|
|
737
773
|
}
|
|
738
774
|
if (found.kind === "state") warnUnbindable(ctx, index, expr, true);
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
775
|
+
// Escaping only holds inside a quoted attribute or text. In a script-valued
|
|
776
|
+
// attribute the browser entity-decodes `'` back into a live quote, and
|
|
777
|
+
// in bare tag position (`<div { x }>`) the value IS markup.
|
|
778
|
+
const tag = OPEN_TAG.exec(out);
|
|
779
|
+
const open = tag && OPEN_ATTR.exec(tag[0]);
|
|
780
|
+
const name = open ? open[1].toLowerCase() : "";
|
|
781
|
+
if (tag && !open) {
|
|
782
|
+
warnUnsafeSlot(ctx, index, expr, "inside a tag but outside any attribute value");
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (name.startsWith("on") || name === "srcdoc") {
|
|
786
|
+
warnUnsafeSlot(ctx, index, expr, `in the ${name}= attribute`);
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const head = open ? (open[2] ?? open[3] ?? open[4]) : "";
|
|
790
|
+
if (URL_ATTRS.has(name) && unsafeUrlValue(head + found.text)) {
|
|
742
791
|
warnUnsafeUrl(ctx, index, expr);
|
|
743
792
|
out = out.slice(0, out.length - head.length); // the head belongs to the refused value
|
|
744
793
|
continue;
|
|
745
794
|
}
|
|
746
|
-
|
|
795
|
+
// An unquoted value ends at whitespace, so `x onmouseover=…` would open a
|
|
796
|
+
// new attribute. Entities keep it one value and decode back to the text.
|
|
797
|
+
out +=
|
|
798
|
+
open?.[4] !== undefined
|
|
799
|
+
? escapeHtml(found.text).replace(/[\s=`]/g, (c) => `&#${c.charCodeAt(0)};`)
|
|
800
|
+
: escapeHtml(found.text);
|
|
747
801
|
}
|
|
748
802
|
return out + html.slice(last);
|
|
749
803
|
}
|
package/src/dev.js
CHANGED
|
@@ -1,8 +1,71 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
1
2
|
import { escapeHtml } from "./compiler.js";
|
|
2
3
|
|
|
3
4
|
export const devClientPath = "/__wd/dev-client.js";
|
|
4
5
|
export const devEventsPath = "/__wd/dev-events";
|
|
5
6
|
|
|
7
|
+
/** @param {string} h */
|
|
8
|
+
const isLoopbackName = (h) =>
|
|
9
|
+
h === "localhost" || h.endsWith(".localhost") || h === "::1" || /^127\.\d+\.\d+\.\d+$/.test(h);
|
|
10
|
+
|
|
11
|
+
/** `Host`/`Origin` → bare lowercase hostname (`[::1]:5173` → `::1`), or null. @param {string} value */
|
|
12
|
+
function hostnameOf(value) {
|
|
13
|
+
try {
|
|
14
|
+
return new URL(value.includes("://") ? value : `http://${value}`).hostname
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/^\[|\]$/g, "");
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The local dev/preview servers bind to loopback, but loopback alone does not
|
|
24
|
+
* keep a website you are visiting out. Two attacks reach it from a browser:
|
|
25
|
+
*
|
|
26
|
+
* - DNS rebinding: `evil.example` re-resolves to 127.0.0.1, after which the
|
|
27
|
+
* page can READ every response (drafts, the dep map, the SSE stream, and
|
|
28
|
+
* `api/*` handlers, which run with the full `process.env`). The browser still
|
|
29
|
+
* sends `Host: evil.example`, so only hostnames that cannot be rebound pass:
|
|
30
|
+
* `localhost`/`*.localhost`, IP literals (a rebinding needs a domain), the
|
|
31
|
+
* bind address, and the `DARKMOWN_ALLOWED_HOSTS` list (a tunnel's domain).
|
|
32
|
+
* - Cross-site requests: any page can fire a simple POST at
|
|
33
|
+
* `http://127.0.0.1:5173/api/x` without reading the reply, so the handler's
|
|
34
|
+
* side effects run. `/api/*` therefore also refuses `Sec-Fetch-Site:
|
|
35
|
+
* cross-site` and an `Origin` that is not loopback, the request's own host,
|
|
36
|
+
* or allow-listed.
|
|
37
|
+
*
|
|
38
|
+
* `DARKMOWN_ALLOWED_HOSTS=*` turns the guard off.
|
|
39
|
+
* @param {{ url?: string, headers: Record<string, string | string[] | undefined> }} req
|
|
40
|
+
* @param {{ bindHost?: string, allowedHosts?: string }} [options]
|
|
41
|
+
* @returns {string | null} why the request is refused, or null to serve it
|
|
42
|
+
*/
|
|
43
|
+
export function refuseLocalRequest(req, { bindHost = "", allowedHosts = "" } = {}) {
|
|
44
|
+
const allowed = allowedHosts
|
|
45
|
+
.split(",")
|
|
46
|
+
.map((h) => hostnameOf(h.trim()) ?? "")
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
if (allowedHosts.split(",").some((h) => h.trim() === "*")) return null;
|
|
49
|
+
const bind = hostnameOf(bindHost) ?? "";
|
|
50
|
+
const host = hostnameOf(String(req.headers.host ?? ""));
|
|
51
|
+
if (!host || !(isLoopbackName(host) || isIP(host) || host === bind || allowed.includes(host))) {
|
|
52
|
+
return `Host "${req.headers.host ?? ""}" is not allowed. Set DARKMOWN_ALLOWED_HOSTS to permit it.`;
|
|
53
|
+
}
|
|
54
|
+
if (!(req.url || "/").startsWith("/api/")) return null;
|
|
55
|
+
if (req.headers["sec-fetch-site"] === "cross-site") return "Cross-site request to /api/ refused.";
|
|
56
|
+
const origin = req.headers.origin;
|
|
57
|
+
if (origin !== undefined) {
|
|
58
|
+
const from = origin === "null" ? null : hostnameOf(String(origin));
|
|
59
|
+
if (
|
|
60
|
+
!from ||
|
|
61
|
+
!(isLoopbackName(from) || from === host || from === bind || allowed.includes(from))
|
|
62
|
+
) {
|
|
63
|
+
return `Origin "${origin}" may not call /api/ on this server.`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
6
69
|
/**
|
|
7
70
|
* Serialize the dev server's rebuilds: at most ONE child build runs at a time.
|
|
8
71
|
* Every build read-modify-writes the dependency map (`dist/.wd-dev-deps.json`),
|
package/src/feeds.js
CHANGED
|
@@ -27,14 +27,23 @@ import { wdError } from "./compiler/context.js";
|
|
|
27
27
|
* @property {string} lastmod ISO date (`yyyy-mm-dd`).
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
// Characters XML 1.0 cannot carry (see escapeXml).
|
|
31
|
+
const XML_INVALID =
|
|
32
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: these are exactly the characters being removed.
|
|
33
|
+
/[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
34
|
+
|
|
30
35
|
/**
|
|
31
36
|
* XML-escape a text value for use inside an element or attribute. Covers the
|
|
32
|
-
* five predefined XML entities — the same set RSS/sitemap consumers expect
|
|
37
|
+
* five predefined XML entities — the same set RSS/sitemap consumers expect —
|
|
38
|
+
* and drops the characters XML 1.0 cannot carry at all (C0 controls other than
|
|
39
|
+
* tab/LF/CR, U+FFFE/U+FFFF, lone surrogates): one in a single post title makes
|
|
40
|
+
* the whole feed malformed, and readers reject it outright.
|
|
33
41
|
* @param {string} value
|
|
34
42
|
* @returns {string}
|
|
35
43
|
*/
|
|
36
44
|
export function escapeXml(value) {
|
|
37
45
|
return String(value)
|
|
46
|
+
.replace(XML_INVALID, "")
|
|
38
47
|
.replaceAll("&", "&")
|
|
39
48
|
.replaceAll("<", "<")
|
|
40
49
|
.replaceAll(">", ">")
|
|
@@ -174,14 +183,20 @@ export function firstParagraph(body) {
|
|
|
174
183
|
/** @type {string[]} */
|
|
175
184
|
const collected = [];
|
|
176
185
|
for (let j = i; j < lines.length && lines[j].trim(); j++) collected.push(lines[j].trim());
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
return (
|
|
187
|
+
collected
|
|
188
|
+
.join(" ")
|
|
189
|
+
.replace(/`([^`]+)`/g, "$1") // inline code → its text
|
|
190
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1") // bold
|
|
191
|
+
.replace(/\*([^*]+)\*/g, "$1") // italic
|
|
192
|
+
// [text](url) → text. The classes also exclude the OPENING bracket/paren:
|
|
193
|
+
// with `[^\]]*` a body of 200k `[` rescans to the end from every `[`
|
|
194
|
+
// (quadratic, 17 s per call), and any one collection author can write it.
|
|
195
|
+
// One level of parens is kept for URLs like `/wiki/Foo_(bar)`.
|
|
196
|
+
.replace(/\[([^[\]]*)\]\([^()]*(?:\([^()]*\)[^()]*)*\)/g, "$1")
|
|
197
|
+
.replace(/\s+/g, " ")
|
|
198
|
+
.trim()
|
|
199
|
+
);
|
|
185
200
|
}
|
|
186
201
|
|
|
187
202
|
/**
|
package/src/headers.js
CHANGED
|
@@ -147,14 +147,20 @@ export function routeCsp(reactive, embedHosts = []) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
|
-
* Baseline security headers applied to every
|
|
150
|
+
* Baseline security headers applied to every response, independent of the
|
|
151
151
|
* CSP. CSP is added per-route (static vs reactive) by the helpers below.
|
|
152
|
+
*
|
|
153
|
+
* HSTS is one year WITHOUT `includeSubDomains`/`preload`: those pin every
|
|
154
|
+
* sibling subdomain to HTTPS, which only the domain owner can safely decide.
|
|
155
|
+
* Browsers ignore the header over plain HTTP, so local `serve` is unaffected.
|
|
156
|
+
* Cloudflare custom domains send no HSTS unless the zone enables it.
|
|
152
157
|
* @type {Record<string, string>}
|
|
153
158
|
*/
|
|
154
159
|
export const BASE_SECURITY_HEADERS = {
|
|
155
160
|
"X-Content-Type-Options": "nosniff",
|
|
156
161
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
157
|
-
"X-Frame-Options": "SAMEORIGIN"
|
|
162
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
163
|
+
"Strict-Transport-Security": "max-age=31536000"
|
|
158
164
|
};
|
|
159
165
|
|
|
160
166
|
/**
|
|
@@ -33,7 +33,12 @@ export async function runJobsFile(jobsFile, resultsFile) {
|
|
|
33
33
|
/** @type {any} */
|
|
34
34
|
let sharp;
|
|
35
35
|
try {
|
|
36
|
-
|
|
36
|
+
// The env override is a test seam pointed at a stub FILE; a dynamic import
|
|
37
|
+
// needs a file URL on Windows ("D:\..." parses as a scheme). Plain module
|
|
38
|
+
// specifiers (like a deliberately nonexistent package name) pass through.
|
|
39
|
+
const spec = process.env.WD_SHARP_MODULE;
|
|
40
|
+
const target = spec && /[/\\]/.test(spec) ? pathToFileURL(spec).href : spec || "sharp";
|
|
41
|
+
sharp = (await import(target)).default;
|
|
37
42
|
} catch {
|
|
38
43
|
write({ unavailable: true, failed: [] });
|
|
39
44
|
return;
|
package/src/router.js
CHANGED
|
@@ -41,7 +41,10 @@ export function discoverRoutes(routesRoot, options = {}) {
|
|
|
41
41
|
/** @param {string} dir */
|
|
42
42
|
function walk(dir) {
|
|
43
43
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
44
|
-
|
|
44
|
+
// A symlinked page would compile (and, with md sources on, publish) a
|
|
45
|
+
// file from anywhere on the machine: CI keys, a contributor's `.env`.
|
|
46
|
+
// Assets/images/fonts already skip symlinks; pages must too.
|
|
47
|
+
if (isHiddenName(entry.name) || entry.isSymbolicLink()) continue;
|
|
45
48
|
const abs = path.join(dir, entry.name);
|
|
46
49
|
if (entry.isDirectory()) {
|
|
47
50
|
walk(abs);
|
package/src/runtime.js
CHANGED
|
@@ -158,8 +158,8 @@ window.addEventListener("popstate", () => {
|
|
|
158
158
|
window.addEventListener("storage", (event) => {
|
|
159
159
|
const name = event.key && event.key.startsWith("wd:store:") ? event.key.slice(9) : "";
|
|
160
160
|
if (!storeKeys.has(name) || event.newValue == null) return;
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
const next = jsonOr(event.newValue);
|
|
162
|
+
if (next === undefined) return;
|
|
163
163
|
if (JSON.stringify(next) === JSON.stringify(state[name])) return;
|
|
164
164
|
state[name] = next;
|
|
165
165
|
render();
|
|
@@ -225,11 +225,17 @@ function isEmpty(v) {
|
|
|
225
225
|
* Parse JSON, or undefined when the text is not JSON. `undefined` is a safe
|
|
226
226
|
* sentinel: no JSON document parses to it, so a body of literal `null` is still
|
|
227
227
|
* distinguishable from "not JSON at all".
|
|
228
|
+
*
|
|
229
|
+
* Every UNTRUSTED document (a fetch body, a `from-url` param, localStorage, a
|
|
230
|
+
* cross-tab storage event) enters through here. The reviver drops own
|
|
231
|
+
* `toString`/`valueOf` keys: `{"toString":1}` is an object `String()` cannot
|
|
232
|
+
* convert, so it would throw on every render that paints it, and a persisted
|
|
233
|
+
* copy would keep the page dead after a reload of the clean URL.
|
|
228
234
|
* @param {string} text
|
|
229
235
|
* @returns {any}
|
|
230
236
|
*/
|
|
231
237
|
function jsonOr(text) {
|
|
232
|
-
try { return JSON.parse(text); } catch { return undefined; }
|
|
238
|
+
try { return JSON.parse(text, (k, v) => (k === "toString" || k === "valueOf" ? undefined : v)); } catch { return undefined; }
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
// The characters a value must not carry into a URL, mirroring `DEST_UNSAFE`
|
|
@@ -334,8 +340,9 @@ const FMT = {
|
|
|
334
340
|
default: (v, [fb = ""]) => (v == null || v === "" || (Array.isArray(v) && !v.length) ? String(fb) : v),
|
|
335
341
|
sum: (v, [f]) => fmtNums(fmtList(v), f).reduce((a, n) => a + n, 0),
|
|
336
342
|
avg: (v, [f]) => { const l = fmtList(v); return l.length ? FMT.sum(l, [f]) / l.length : 0; },
|
|
337
|
-
|
|
338
|
-
|
|
343
|
+
// reduce, not spread: `Math.max(...list)` throws RangeError past ~100k items.
|
|
344
|
+
min: (v, [f]) => { const l = fmtList(v); return l.length ? fmtNums(l, f).reduce((a, n) => Math.min(a, n)) : 0; },
|
|
345
|
+
max: (v, [f]) => { const l = fmtList(v); return l.length ? fmtNums(l, f).reduce((a, n) => Math.max(a, n)) : 0; },
|
|
339
346
|
count: (v) => fmtList(v).length,
|
|
340
347
|
join: (v, [sep = ", ", f]) => fmtList(v).map((r) => fmtPick(r, f)).join(String(sep))
|
|
341
348
|
};
|
|
@@ -1080,7 +1087,10 @@ function startFetch(node) {
|
|
|
1080
1087
|
let missing = false;
|
|
1081
1088
|
const url = g("url").replace(/\{\s*([\w$.]+)\s*\}/g, (_, p) => {
|
|
1082
1089
|
const v = getPath(state, p);
|
|
1083
|
-
|
|
1090
|
+
// `.`/`..` survive encodeURIComponent and the URL parser resolves them, so
|
|
1091
|
+
// `?id=..` would retarget `/api/users/{ id }/x` (headers and body included)
|
|
1092
|
+
// at `/api/x`. Treat them as missing, like an empty value.
|
|
1093
|
+
if (v == null || v === "" || v === "." || v === "..") missing = true;
|
|
1084
1094
|
return encodeURIComponent(String(v ?? ""));
|
|
1085
1095
|
});
|
|
1086
1096
|
if (missing) return set("_loading", false);
|
package/src/runtime.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var a={},ft=new Set,nt=new Set,T=new Set,M={};function J(t,e){try{return e===void 0?localStorage.getItem(t):e===null?localStorage.removeItem(t):localStorage.setItem(t,e)}catch{return null}}var X=t=>t.replace(":",".");function mt(){for(let t of document.querySelectorAll("script[data-wd-state],script[data-wd-store]")){let e=t.getAttribute("data-wd-store"),n=JSON.parse(t.textContent||"null");for(let[r,o]of e?[[e,n]]:Object.entries(n||{})){if(r in a)continue;if(a[r]=structuredClone(M[r]=o),e?!t.hasAttribute("data-wd-store-ephemeral"):t.getAttribute("data-wd-persist")===r){(e?nt:ft).add(r);let c=(e?"wd:store:":"wd:")+r,i=J(c);i===null?e&&J(c,JSON.stringify(o??null)):D(i)===void 0?J(c,null):a[r]=D(i)}t.getAttribute("data-wd-url")===r&&(T.add(r),pt(!1,[r]))}}}function xt(){if(!T.size)return;let t=new URLSearchParams(location.search);for(let r of T){let o=a[r];JSON.stringify(o??null)===JSON.stringify(M[r]??null)?t.delete(X(r)):t.set(X(r),typeof o=="string"?o:JSON.stringify(o))}let e=t.toString(),n=location.pathname+(e?`?${e}`:"")+location.hash;if(n!==location.pathname+location.search+location.hash)try{history.replaceState(history.state,"",n)}catch{}}function pt(t,e){let n=new URLSearchParams(location.search);for(let r of e||T){let o=n.get(X(r)),s=M[r];o==null?t&&(a[r]=structuredClone(s)):a[r]=typeof s=="string"?o:D(o)??o}}function b(){for(let t of ft)J(`wd:${t}`,JSON.stringify(a[t]??null));for(let t of nt)J(`wd:store:${t}`,JSON.stringify(a[t]??null));xt()}mt();window.addEventListener("popstate",()=>{T.size&&(pt(!0),b(),g())});window.addEventListener("storage",t=>{let e=t.key&&t.key.startsWith("wd:store:")?t.key.slice(9):"";if(!nt.has(e)||t.newValue==null)return;let n;try{n=JSON.parse(t.newValue)}catch{return}JSON.stringify(n)!==JSON.stringify(a[e])&&(a[e]=n,g())});var Y=t=>t==="__proto__"||t==="constructor"||t==="prototype";function f(t,e){if(!e)return t;let n=t;for(let r of e.split(".")){if(n==null||Y(r))return;n=n[r]}return n}function Ct(t,e,n){let r=e.split("."),o=r.pop()||"",s=t;for(let c of r){if(Y(c))return;(s[c]==null||typeof s[c]!="object")&&(s[c]={}),s=s[c]}Y(o)||(s[o]=n)}function Jt(t){return t==null||typeof t=="object"&&(Array.isArray(t)?t.length:Object.keys(t).length)===0}function D(t){try{return JSON.parse(t)}catch{return}}var Tt=/[\u0000-\u0020()<>"'`\\\u007F]/g,I=(t,e)=>t.matches(e)?[t,...t.querySelectorAll(e)]:[...t.querySelectorAll(e)];function wt(t,e,n,r){if(!e)return;let o;try{o=JSON.parse(e)}catch{return}let s="",c="";for(let i=1;i<o.length;i++){let u=o[i];if(typeof u=="string"){s+=u,c+=u;continue}let l=String((u[0]==="s"?f(a[u[1]],u[2]):u[0]==="m"?r[u[1]]:f(n,u[1]))??"");c+=l,s+=l.replace(Tt,m=>`%${m.charCodeAt(0).toString(16).toUpperCase().padStart(2,"0")}`)}o[0]!=="href"&&o[0]!=="src"||t.setAttribute(o[0],/^(javascript|data|vbscript):/i.test(c.replace(/[\u0000-\u0020\u007F]+/g,""))?"":s)}var q=t=>{let e=typeof t=="number"?t:Number.parseFloat(t);return Number.isFinite(e)?e:0},_=t=>Array.isArray(t)?t:t==null?[]:[t],gt=(t,e)=>e!=null&&e!==""?t?.[e]:t,z=(t,e)=>t.map(n=>q(gt(n,e))),B=t=>{if(t instanceof Date)return t;if(typeof t=="number")return new Date(t);let e=String(t).trim();return/^-?\d+$/.test(e)?new Date(Number(e)):new Date(e)},W=(t,e,n)=>Number.isNaN(t.getTime())?String(n):new Intl.DateTimeFormat(void 0,e).format(t),H=t=>typeof t=="string"&&/^\d{4}-\d{2}-\d{2}$/.test(t.trim())?{timeZone:"UTC"}:void 0,F={money:(t,[e="USD",n])=>new Intl.NumberFormat(n||void 0,{style:"currency",currency:String(e||"USD")}).format(q(t)),number:(t,[e])=>new Intl.NumberFormat(void 0,e==null?{}:{minimumFractionDigits:+e,maximumFractionDigits:+e}).format(q(t)),percent:(t,[e=0])=>new Intl.NumberFormat(void 0,{style:"percent",minimumFractionDigits:+e,maximumFractionDigits:+e}).format(q(t)),round:(t,[e=0])=>{let n=10**+e;return Math.round(q(t)*n)/n},date:(t,[e="medium"])=>W(B(t),{dateStyle:e,...H(t)},t),time:(t,[e="short"])=>W(B(t),{timeStyle:e,...H(t)},t),datetime:(t,[e="medium",n="short"])=>W(B(t),{dateStyle:e,timeStyle:n,...H(t)},t),upper:t=>String(t??"").toUpperCase(),lower:t=>String(t??"").toLowerCase(),capitalize:t=>{let e=String(t??"");return e&&e[0].toUpperCase()+e.slice(1)},truncate:(t,[e=50,n="…"])=>{let r=String(t??"");return r.length>+e?r.slice(0,+e).trimEnd()+String(n):r},trim:t=>String(t??"").trim(),pluralize:(t,[e="",n])=>{let r=q(t);return`${new Intl.NumberFormat().format(r)} ${Math.abs(r)===1?String(e):n!=null?String(n):`${e}s`}`},default:(t,[e=""])=>t==null||t===""||Array.isArray(t)&&!t.length?String(e):t,sum:(t,[e])=>z(_(t),e).reduce((n,r)=>n+r,0),avg:(t,[e])=>{let n=_(t);return n.length?F.sum(n,[e])/n.length:0},min:(t,[e])=>{let n=_(t);return n.length?Math.min(...z(n,e)):0},max:(t,[e])=>{let n=_(t);return n.length?Math.max(...z(n,e)):0},count:t=>_(t).length,join:(t,[e=", ",n])=>_(t).map(r=>gt(r,n)).join(String(e))};function tt(t,e){let n=t.getAttribute("data-wd-fmt");if(!n)return e;let r;try{r=JSON.parse(n)}catch{return e}let o=e;for(let[s,c]of r)F[s]&&(o=F[s](o,c||[]));return o}var Ft=(t,e,n)=>F[t]?F[t](e,n==null?[]:[n]):void 0;async function rt(t,e){let n=await fetch(t,e),r=await n.text(),o=D(r);if(!n.ok){let s=o&&[o.error,o.message].find(i=>typeof i=="string"&&i),c=new Error(s||`HTTP ${n.status}`);throw c.status=n.status,c.body=o??null,c}return o===void 0?{status:n.status,body:r}:o}function x(t,e){a[t+"_error"]=e?e.message||String(e):null,a[t+"_error_body"]=(e&&e.body)??null}function yt(t){let e=t.__wdGen=(t.__wdGen||0)+1;return()=>e!==t.__wdGen}var O=!0;function P(t,e){let n=window.wd;n&&n.debug&&console.warn(`[wd] expression failed: ${t}`,e)}function Et(){for(let t of document.querySelectorAll("[data-wd-computed]")){let e=t.getAttribute("data-wd-computed-key")||"",n=t.getAttribute("data-wd-computed-expr")||"null";try{a[e]=ht(U(n),void 0)}catch(r){a[e]=void 0,P(n,r)}}}var It=(t,e)=>String(t??"").toLowerCase().includes(String(e??"").toLowerCase());function ht(t,e){let n=r=>{switch(r[0]){case"L":return r[1];case"S":return f(a[r[1]],r[2]??null);case"I":return f(e,r[1]??null);case"C":return It(n(r[1]),n(r[2]));case"A":return Ft(r[1],n(r[2]),r[3]);case"!":return!n(r[1]);case"u-":return-n(r[1]);case"&&":return n(r[1])&&n(r[2]);case"||":return n(r[1])||n(r[2]);case"==":return n(r[1])==n(r[2]);case"!=":return n(r[1])!=n(r[2]);case">":return n(r[1])>n(r[2]);case"<":return n(r[1])<n(r[2]);case">=":return n(r[1])>=n(r[2]);case"<=":return n(r[1])<=n(r[2]);case"+":return n(r[1])+n(r[2]);case"-":return n(r[1])-n(r[2]);case"*":return n(r[1])*n(r[2]);case"/":return n(r[1])/n(r[2]);case"%":return n(r[1])%n(r[2])}throw new Error(`wd: unknown op ${r[0]}`)};return n(t)}var it=new Map;function U(t){let e=it.get(t);if(e)return e;let n=JSON.parse(t);return it.set(t,n),n}function K(t,e){try{return!!ht(t,e)}catch(n){return P(t,n),!1}}function bt(t,e,n){if(!e)return;let r;try{r=JSON.parse(e)}catch{return}for(let[o,s]of r)t.classList.toggle(o,K(s,n))}function $t(t,e){let n=String(t&&typeof t=="object"?t.id??t.key??JSON.stringify(t):t).replace(/#/g,"##"),r=e.get(n)||0;return e.set(n,r+1),r?`${n}#${r}`:n}function St(t,e,n={}){let r=t.querySelectorAll("[data-wd-loop-item]"),o=s=>r.length&&s.closest("[data-wd-loop-item]");for(let s of t.querySelectorAll("[data-wd-each-if]")){if(o(s))continue;let c=s.getAttribute("data-wd-if-expr"),i=s.getAttribute("data-wd-meta"),u=c?K(U(c),e):i?n[i]:f(e,s.getAttribute("data-wd-path")),l=s.querySelector("[data-wd-each-if-out]");l&&(At(s,l,u,"wd-if"),St(l,e,n))}r=t.querySelectorAll("[data-wd-loop-item]");for(let s of I(t,"[data-wd-each]"))o(s)||(s.textContent=tt(s,f(e,s.getAttribute("data-wd-path")))??"");for(let s of t.querySelectorAll("[data-wd-each-meta]"))o(s)||(s.textContent=tt(s,n[s.getAttribute("data-wd-each-meta")||""])??"");for(let s of I(t,"[data-wd-each-class]"))o(s)||bt(s,s.getAttribute("data-wd-each-class"),e);for(let s of I(t,"[data-wd-each-attr]"))o(s)||wt(s,s.getAttribute("data-wd-each-attr"),e,n);for(let s of r)Ot(s,f(e,s.getAttribute("data-wd-loop-item")))}function at(t,e){let n=t.getAttribute(e);return n==null?null:n.startsWith("key:")?Number(a[n.slice(4)]??0):Number(n)}function Dt(t,e){let n=e.getAttribute("data-wd-loop-sort");if(n!=null){let s=n.startsWith("key:"),c=s?String(a[n.slice(4)]??""):n;if(!s||c){let i=e.getAttribute("data-wd-loop-sort-dir")||"asc";i.startsWith("key:")&&(i=String(a[i.slice(4)]??"asc"));let u=i==="desc"?-1:1;t=t.map((l,m)=>({value:l,index:m})).sort((l,m)=>{let h=f(l.value,c||null),d=f(m.value,c||null),p=typeof h=="number"&&typeof d=="number"?h-d:String(h).localeCompare(String(d));return p?p*u:l.index-m.index}).map(l=>l.value)}}e.hasAttribute("data-wd-loop-reverse")&&t.reverse();let r=at(e,"data-wd-loop-offset");r&&(t=t.slice(r));let o=at(e,"data-wd-loop-limit");return o!=null&&(t=t.slice(0,o)),t}function At(t,e,n,r){let o=String(!!n);if(t.getAttribute("data-wd-if-active")===o)return!1;t.setAttribute("data-wd-if-active",o);let s=t.querySelector(`template[data-${r}-${o}]`);return $(e),e.innerHTML=s?.innerHTML||"",O=!0}function Nt(t){let e=t.getAttribute("data-wd-if-expr"),n=e?K(U(e),void 0):f(a[t.getAttribute("data-wd-if")||""],t.getAttribute("data-wd-path")),r=t.querySelector("[data-wd-if-out]");if(!(!r||!At(t,r,n,"wd")))for(let o of r.querySelectorAll("[data-wd-if]"))Nt(o)}function Ot(t,e){let n=t.querySelector("template[data-wd-loop-template]"),r=t.querySelector("[data-wd-loop-out]");if(!n||!r)return;let o=Array.isArray(e)?e.slice():[],s=t.getAttribute("data-wd-loop-where");if(s){let d=U(s);o=o.filter(p=>K(d,p))}o=Dt(o,t);let c=t.querySelector("template[data-wd-loop-empty]");if(!o.length&&c){if(r.getAttribute("data-wd-empty")!=="1"){$(r),r.textContent="";for(let d of[...c.content.children])r.appendChild(d.cloneNode(!0));r.setAttribute("data-wd-empty","1"),O=!0}return}r.getAttribute("data-wd-empty")==="1"&&($(r),r.textContent="",r.removeAttribute("data-wd-empty"));let i=new Map;for(let d of[...r.children])i.set(d.getAttribute("data-wd-loop-key")||"",d);let u=new Map,l=new Set,m=o.length,h=!1;for(let d=0;d<m;d++){let p=o[d],S=$t(p,u),w=i.get(S);if(!w||l.has(S)){if(w=n.content.firstElementChild?.cloneNode(!0),!w)continue;w.setAttribute("data-wd-loop-key",S),O=!0}l.add(S),St(w,p,{index:d,number:d+1,first:d===0,last:d===m-1,count:m}),w.__wdItem=p,(h||r.children[d]!==w)&&(h=!0,r.appendChild(w))}for(let[d,p]of i)l.has(d)||($(p),p.remove())}var ct=0;function R(){for(let t=0;t<3;t++){O&&Pt(),O=!1,Et();for(let e of document.querySelectorAll("[data-wd-if]"))Nt(e);for(let e of document.querySelectorAll("[data-wd-class]"))bt(e,e.getAttribute("data-wd-class"),void 0);for(let e of document.querySelectorAll("[data-wd-loop]")){let n=e.getAttribute("data-wd-loop"),r=e.getAttribute("data-wd-loop-data"),o=n?n.indexOf("."):-1;Ot(e,n?o<0?a[n]:f(a[n.slice(0,o)],n.slice(o+1)):r?JSON.parse(r):[])}if(!O)break}O&&++ct<10?(window.wd?.debug&&console.warn("[wd] render did not settle; scheduling another pass"),g()):ct=0;for(let t of document.querySelectorAll("[data-wd-bind]"))t.textContent=tt(t,f(a[t.getAttribute("data-wd-bind")||""],t.getAttribute("data-wd-path")))??"";for(let t of document.querySelectorAll("[data-wd-attr]"))wt(t,t.getAttribute("data-wd-attr"),void 0,{});for(let t of document.querySelectorAll("[data-wd-bind-input]")){let e=a[t.getAttribute("data-wd-bind-input")||""]??"";t.type==="checkbox"?t.checked=!!e:t.type==="radio"?t.checked=t.value===String(e):document.activeElement!==t&&(t.value=e)}Lt(),vt(),j.size&&Kt()}function Lt(){for(let t of document.querySelectorAll("[data-wd-theme]")){let e=a[t.getAttribute("data-wd-theme")||""],n=document.documentElement;e==null||e===""||e==="auto"?n.removeAttribute("data-theme"):n.setAttribute("data-theme",String(e))}}var L=[],V=0;function vt(){if(!L.length)return;let t=!1;for(let e of L){let n=JSON.stringify(f(a,e.watch)??null);n!==e.last&&(e.last=n,kt(e.actions),t=!0)}if(!t){V=0;return}if(b(),++V>=10){V=0,console.warn("effect did not settle");return}R()}var jt=typeof requestAnimationFrame=="function"?requestAnimationFrame:queueMicrotask,Z=!1;function g(){Z||(Z=!0,jt(()=>{Z=!1,R()}))}document.addEventListener("input",t=>{let e=t.target?.closest("[data-wd-bind-input]");e&&(a[e.getAttribute("data-wd-bind-input")||""]=e.type==="range"?e.valueAsNumber:e.type==="checkbox"?e.checked:e.value,b(),g(),G())});function ut(t){if(!t)return null;let e=t.closest("[data-wd-loop-key]"),n=t.closest("[data-wd-loop]");return!e||!n?null:{srcKey:n.getAttribute("data-wd-loop"),item:e.__wdItem}}function et(t,e,n,r){let o=i=>Ct(a,n,i),s=f(a,n),c=()=>Array.isArray(s)?s:[];if(e==="inc"&&o(Number(s??0)+1),e==="dec"&&o(Number(s??0)-1),e==="add"&&o(Number(s??0)+Number(r)),e==="sub"&&o(Number(s??0)-Number(r)),e==="toggle"&&o(!s),e==="set"&&o(r),e==="append"&&o([...c(),r]),e==="prepend"&&o([r,...c()]),e==="member-toggle"&&o(c().includes(r)?c().filter(i=>i!==r):[...c(),r]),e==="remove-value"&&o(c().filter(i=>i!==r)),e==="clear"&&o(Array.isArray(s)?[]:{}),e==="merge"&&o({...s&&typeof s=="object"?s:{},...typeof r=="string"?f(a,r):r}),e==="delete"&&s&&typeof s=="object"&&(delete s[r],o(s)),e==="reset"&&o(structuredClone(f(M,n))),e==="refetch"){let i=document.querySelector(`[data-wd-fetch-key="${n}"]`);i&&v(i)}if(e==="remove"){let i=ut(t);i&&i.srcKey&&(a[i.srcKey]=(Array.isArray(a[i.srcKey])?a[i.srcKey]:[]).filter(u=>u!==i.item))}if(e==="append-row"){let i=ut(t);if(i&&i.item!==void 0){let u=i.item&&typeof i.item=="object"?structuredClone(i.item):i.item;o([...c(),u])}}}var kt=t=>{for(let e of t)et(null,e.op,e.target||"",structuredClone(e.value))};document.addEventListener("click",t=>{let e=t.target?.closest("[data-wd-action],[data-wd-actions]");if(!e)return;let n=e.getAttribute("data-wd-actions");if(n)for(let r of JSON.parse(n))et(e,r.op,r.target||"",r.value);else{let r=e.getAttribute("data-wd-value");et(e,e.getAttribute("data-wd-action")||"",e.getAttribute("data-wd-target")||"",r===null?void 0:JSON.parse(r))}b(),g(),G()});document.addEventListener("submit",t=>{let e=t.target?.closest("[data-wd-form]");if(!e)return;t.preventDefault();let n=e.getAttribute("data-wd-form")||"",r=e.getAttribute("action");if(!r){let i=new FormData(e),u=Object.fromEntries(i);for(let l of e.querySelectorAll("[data-wd-multi]")){let m=l.getAttribute("data-wd-multi");m&&(u[m]=i.getAll(m))}a[n]=u,b(),g();return}let o=yt(e);x(n),g();let s=new FormData(e);for(let i of e.querySelectorAll("[data-wd-bind-input]")){let u=i.getAttribute("data-wd-bind-input");u&&!i.getAttribute("name")&&!s.has(u)&&s.append(u,a[u]??"")}let c=e.querySelector("input[type=file]");rt(r,{method:e.getAttribute("method")||"post",headers:c?void 0:{"content-type":"application/x-www-form-urlencoded"},body:c?s:new URLSearchParams(s).toString()}).then(i=>{o()||(a[n]=i,x(n),b(),g())}).catch(i=>{o()||(x(n,i),g())})});function v(t){let e=yt(t),n=y=>t.getAttribute("data-wd-fetch-"+y)||"",r=n("key"),o=(y,A)=>{a[r+y]=A,g()},s=!1,c=n("url").replace(/\{\s*([\w$.]+)\s*\}/g,(y,A)=>{let k=f(a,A);return(k==null||k==="")&&(s=!0),encodeURIComponent(String(k??""))});if(s)return o("_loading",!1);let i=n("headers"),u=n("body"),l=n("refresh"),m=n("method")||"GET",h=Number(n("timeout")),d=Number(n("retry")),p=!1;x(r),o("_loading",!0);let S=y=>{x(r,y),o("_loading",!1)},w=()=>{let y={method:m};i&&(y.headers=a[i]||{}),u&&m!=="GET"&&(y.body=JSON.stringify(a[u]??null));let A=typeof AbortController=="function"?new AbortController:null;A&&(y.signal=A.signal);let k=h&&A?setTimeout(()=>A.abort(),h):0;rt(c,y).then(N=>{clearTimeout(k),!e()&&(a[r]=N,a[r+"_empty"]=Jt(N),x(r),o("_loading",!1))}).catch(N=>{if(clearTimeout(k),e())return;let st=N&&N.status===401;if(st&&l&&i&&!p)return p=!0,void Mt(l,i).then(qt=>{e()||(qt?w():S(N))});if(st&&p)return void S(N);if(d-- >0)return void setTimeout(w,200);S(N)})};w()}var Q=new Map;function Mt(t,e){let n=Q.get(t);if(n)return n;let r=rt(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(a[e]??{})}).then(o=>o&&typeof o=="object"?(a[e]=o,b(),!0):!1).catch(()=>!1).finally(()=>Q.delete(t));return Q.set(t,r),r}var ot=[],_t=t=>(t.getAttribute("data-wd-fetch-deps")||"").split(",").filter(Boolean).map(e=>JSON.stringify(f(a,e))).join("|"),dt=0,C=new Set;function G(){for(let t of ot){let e=_t(t);e&&e!==t.__wdSnap&&(t.__wdSnap=e,C.add(t))}C.size&&(clearTimeout(dt),dt=setTimeout(()=>{for(let t of C)v(t);C.clear()},150))}function $(t){for(let e of I(t,"[data-wd-fetch],[data-wd-effect],[data-wd-every]")){C.delete(e);for(let n of[ot,L,E])for(let r=n.length-1;r>=0;r--)(n[r].node||n[r])===e&&(n[r].t&&clearInterval(n[r].t),n.splice(r,1))}}function Pt(){mt();for(let t of document.querySelectorAll("[data-wd-fetch],[data-wd-effect],[data-wd-every]"))if(!t.__wdReg){if(t.__wdReg=1,t.hasAttribute("data-wd-effect")){let e=JSON.parse(t.textContent||"{}");L.push({node:t,watch:e.watch,actions:e.actions||[],last:JSON.stringify(f(a,e.watch)??null)})}if(t.hasAttribute("data-wd-every")&&(E.length||document.addEventListener("visibilitychange",()=>document.hidden?Ut():lt()),E.push({...JSON.parse(t.textContent||"{}"),node:t}),lt()),t.hasAttribute("data-wd-fetch"))if(ot.push(t),t.__wdSnap=_t(t),t.getAttribute("data-wd-fetch-when")==="visible"&&"IntersectionObserver"in window){let e=new IntersectionObserver(n=>{n.some(r=>r.isIntersecting)&&(e.disconnect(),v(t))});e.observe(t)}else v(t)}}var E=[];function lt(){if(!document.hidden)for(let t of E)t.t||!t.ms||!t.actions||(t.t=setInterval(()=>{kt(t.actions),b(),g(),G()},t.ms))}function Ut(){for(let t of E)clearInterval(t.t),t.t=0}var j=new Map;function Kt(){for(let[t,e]of j){let n=JSON.stringify(a[t]??null);if(n!==e.last){e.last=n;for(let r of e.cbs)try{r(a[t])}catch(o){P(`subscribe ${t}`,o)}}}}window.wd={state:a,debug:!1,get:t=>a[t],set:(t,e)=>{a[t]=e,b(),g(),G()},subscribe:(t,e)=>{let n=j.get(t);n||(n={cbs:new Set,last:JSON.stringify(a[t]??null)},j.set(t,n)),n.cbs.add(e);try{e(a[t])}catch(r){P(`subscribe ${t}`,r)}return()=>n.cbs.delete(e)},render:R};R();
|
|
1
|
+
var a={},ft=new Set,nt=new Set,T=new Set,M={};function J(t,e){try{return e===void 0?localStorage.getItem(t):e===null?localStorage.removeItem(t):localStorage.setItem(t,e)}catch{return null}}var X=t=>t.replace(":",".");function mt(){for(let t of document.querySelectorAll("script[data-wd-state],script[data-wd-store]")){let e=t.getAttribute("data-wd-store"),n=JSON.parse(t.textContent||"null");for(let[r,o]of e?[[e,n]]:Object.entries(n||{})){if(r in a)continue;if(a[r]=structuredClone(M[r]=o),e?!t.hasAttribute("data-wd-store-ephemeral"):t.getAttribute("data-wd-persist")===r){(e?nt:ft).add(r);let c=(e?"wd:store:":"wd:")+r,i=J(c);i===null?e&&J(c,JSON.stringify(o??null)):F(i)===void 0?J(c,null):a[r]=F(i)}t.getAttribute("data-wd-url")===r&&(T.add(r),pt(!1,[r]))}}}function xt(){if(!T.size)return;let t=new URLSearchParams(location.search);for(let r of T){let o=a[r];JSON.stringify(o??null)===JSON.stringify(M[r]??null)?t.delete(X(r)):t.set(X(r),typeof o=="string"?o:JSON.stringify(o))}let e=t.toString(),n=location.pathname+(e?`?${e}`:"")+location.hash;if(n!==location.pathname+location.search+location.hash)try{history.replaceState(history.state,"",n)}catch{}}function pt(t,e){let n=new URLSearchParams(location.search);for(let r of e||T){let o=n.get(X(r)),s=M[r];o==null?t&&(a[r]=structuredClone(s)):a[r]=typeof s=="string"?o:F(o)??o}}function b(){for(let t of ft)J(`wd:${t}`,JSON.stringify(a[t]??null));for(let t of nt)J(`wd:store:${t}`,JSON.stringify(a[t]??null));xt()}mt();window.addEventListener("popstate",()=>{T.size&&(pt(!0),b(),g())});window.addEventListener("storage",t=>{let e=t.key&&t.key.startsWith("wd:store:")?t.key.slice(9):"";if(!nt.has(e)||t.newValue==null)return;let n=F(t.newValue);n!==void 0&&JSON.stringify(n)!==JSON.stringify(a[e])&&(a[e]=n,g())});var Y=t=>t==="__proto__"||t==="constructor"||t==="prototype";function f(t,e){if(!e)return t;let n=t;for(let r of e.split(".")){if(n==null||Y(r))return;n=n[r]}return n}function Ct(t,e,n){let r=e.split("."),o=r.pop()||"",s=t;for(let c of r){if(Y(c))return;(s[c]==null||typeof s[c]!="object")&&(s[c]={}),s=s[c]}Y(o)||(s[o]=n)}function Jt(t){return t==null||typeof t=="object"&&(Array.isArray(t)?t.length:Object.keys(t).length)===0}function F(t){try{return JSON.parse(t,(e,n)=>e==="toString"||e==="valueOf"?void 0:n)}catch{return}}var Tt=/[\u0000-\u0020()<>"'`\\\u007F]/g,$=(t,e)=>t.matches(e)?[t,...t.querySelectorAll(e)]:[...t.querySelectorAll(e)];function wt(t,e,n,r){if(!e)return;let o;try{o=JSON.parse(e)}catch{return}let s="",c="";for(let i=1;i<o.length;i++){let u=o[i];if(typeof u=="string"){s+=u,c+=u;continue}let l=String((u[0]==="s"?f(a[u[1]],u[2]):u[0]==="m"?r[u[1]]:f(n,u[1]))??"");c+=l,s+=l.replace(Tt,m=>`%${m.charCodeAt(0).toString(16).toUpperCase().padStart(2,"0")}`)}o[0]!=="href"&&o[0]!=="src"||t.setAttribute(o[0],/^(javascript|data|vbscript):/i.test(c.replace(/[\u0000-\u0020\u007F]+/g,""))?"":s)}var q=t=>{let e=typeof t=="number"?t:Number.parseFloat(t);return Number.isFinite(e)?e:0},k=t=>Array.isArray(t)?t:t==null?[]:[t],gt=(t,e)=>e!=null&&e!==""?t?.[e]:t,z=(t,e)=>t.map(n=>q(gt(n,e))),B=t=>{if(t instanceof Date)return t;if(typeof t=="number")return new Date(t);let e=String(t).trim();return/^-?\d+$/.test(e)?new Date(Number(e)):new Date(e)},W=(t,e,n)=>Number.isNaN(t.getTime())?String(n):new Intl.DateTimeFormat(void 0,e).format(t),H=t=>typeof t=="string"&&/^\d{4}-\d{2}-\d{2}$/.test(t.trim())?{timeZone:"UTC"}:void 0,E={money:(t,[e="USD",n])=>new Intl.NumberFormat(n||void 0,{style:"currency",currency:String(e||"USD")}).format(q(t)),number:(t,[e])=>new Intl.NumberFormat(void 0,e==null?{}:{minimumFractionDigits:+e,maximumFractionDigits:+e}).format(q(t)),percent:(t,[e=0])=>new Intl.NumberFormat(void 0,{style:"percent",minimumFractionDigits:+e,maximumFractionDigits:+e}).format(q(t)),round:(t,[e=0])=>{let n=10**+e;return Math.round(q(t)*n)/n},date:(t,[e="medium"])=>W(B(t),{dateStyle:e,...H(t)},t),time:(t,[e="short"])=>W(B(t),{timeStyle:e,...H(t)},t),datetime:(t,[e="medium",n="short"])=>W(B(t),{dateStyle:e,timeStyle:n,...H(t)},t),upper:t=>String(t??"").toUpperCase(),lower:t=>String(t??"").toLowerCase(),capitalize:t=>{let e=String(t??"");return e&&e[0].toUpperCase()+e.slice(1)},truncate:(t,[e=50,n="…"])=>{let r=String(t??"");return r.length>+e?r.slice(0,+e).trimEnd()+String(n):r},trim:t=>String(t??"").trim(),pluralize:(t,[e="",n])=>{let r=q(t);return`${new Intl.NumberFormat().format(r)} ${Math.abs(r)===1?String(e):n!=null?String(n):`${e}s`}`},default:(t,[e=""])=>t==null||t===""||Array.isArray(t)&&!t.length?String(e):t,sum:(t,[e])=>z(k(t),e).reduce((n,r)=>n+r,0),avg:(t,[e])=>{let n=k(t);return n.length?E.sum(n,[e])/n.length:0},min:(t,[e])=>{let n=k(t);return n.length?z(n,e).reduce((r,o)=>Math.min(r,o)):0},max:(t,[e])=>{let n=k(t);return n.length?z(n,e).reduce((r,o)=>Math.max(r,o)):0},count:t=>k(t).length,join:(t,[e=", ",n])=>k(t).map(r=>gt(r,n)).join(String(e))};function tt(t,e){let n=t.getAttribute("data-wd-fmt");if(!n)return e;let r;try{r=JSON.parse(n)}catch{return e}let o=e;for(let[s,c]of r)E[s]&&(o=E[s](o,c||[]));return o}var Ft=(t,e,n)=>E[t]?E[t](e,n==null?[]:[n]):void 0;async function rt(t,e){let n=await fetch(t,e),r=await n.text(),o=F(r);if(!n.ok){let s=o&&[o.error,o.message].find(i=>typeof i=="string"&&i),c=new Error(s||`HTTP ${n.status}`);throw c.status=n.status,c.body=o??null,c}return o===void 0?{status:n.status,body:r}:o}function x(t,e){a[t+"_error"]=e?e.message||String(e):null,a[t+"_error_body"]=(e&&e.body)??null}function yt(t){let e=t.__wdGen=(t.__wdGen||0)+1;return()=>e!==t.__wdGen}var _=!0;function P(t,e){let n=window.wd;n&&n.debug&&console.warn(`[wd] expression failed: ${t}`,e)}function Et(){for(let t of document.querySelectorAll("[data-wd-computed]")){let e=t.getAttribute("data-wd-computed-key")||"",n=t.getAttribute("data-wd-computed-expr")||"null";try{a[e]=ht(U(n),void 0)}catch(r){a[e]=void 0,P(n,r)}}}var It=(t,e)=>String(t??"").toLowerCase().includes(String(e??"").toLowerCase());function ht(t,e){let n=r=>{switch(r[0]){case"L":return r[1];case"S":return f(a[r[1]],r[2]??null);case"I":return f(e,r[1]??null);case"C":return It(n(r[1]),n(r[2]));case"A":return Ft(r[1],n(r[2]),r[3]);case"!":return!n(r[1]);case"u-":return-n(r[1]);case"&&":return n(r[1])&&n(r[2]);case"||":return n(r[1])||n(r[2]);case"==":return n(r[1])==n(r[2]);case"!=":return n(r[1])!=n(r[2]);case">":return n(r[1])>n(r[2]);case"<":return n(r[1])<n(r[2]);case">=":return n(r[1])>=n(r[2]);case"<=":return n(r[1])<=n(r[2]);case"+":return n(r[1])+n(r[2]);case"-":return n(r[1])-n(r[2]);case"*":return n(r[1])*n(r[2]);case"/":return n(r[1])/n(r[2]);case"%":return n(r[1])%n(r[2])}throw new Error(`wd: unknown op ${r[0]}`)};return n(t)}var it=new Map;function U(t){let e=it.get(t);if(e)return e;let n=JSON.parse(t);return it.set(t,n),n}function K(t,e){try{return!!ht(t,e)}catch(n){return P(t,n),!1}}function bt(t,e,n){if(!e)return;let r;try{r=JSON.parse(e)}catch{return}for(let[o,s]of r)t.classList.toggle(o,K(s,n))}function $t(t,e){let n=String(t&&typeof t=="object"?t.id??t.key??JSON.stringify(t):t).replace(/#/g,"##"),r=e.get(n)||0;return e.set(n,r+1),r?`${n}#${r}`:n}function St(t,e,n={}){let r=t.querySelectorAll("[data-wd-loop-item]"),o=s=>r.length&&s.closest("[data-wd-loop-item]");for(let s of t.querySelectorAll("[data-wd-each-if]")){if(o(s))continue;let c=s.getAttribute("data-wd-if-expr"),i=s.getAttribute("data-wd-meta"),u=c?K(U(c),e):i?n[i]:f(e,s.getAttribute("data-wd-path")),l=s.querySelector("[data-wd-each-if-out]");l&&(At(s,l,u,"wd-if"),St(l,e,n))}r=t.querySelectorAll("[data-wd-loop-item]");for(let s of $(t,"[data-wd-each]"))o(s)||(s.textContent=tt(s,f(e,s.getAttribute("data-wd-path")))??"");for(let s of t.querySelectorAll("[data-wd-each-meta]"))o(s)||(s.textContent=tt(s,n[s.getAttribute("data-wd-each-meta")||""])??"");for(let s of $(t,"[data-wd-each-class]"))o(s)||bt(s,s.getAttribute("data-wd-each-class"),e);for(let s of $(t,"[data-wd-each-attr]"))o(s)||wt(s,s.getAttribute("data-wd-each-attr"),e,n);for(let s of r)Ot(s,f(e,s.getAttribute("data-wd-loop-item")))}function at(t,e){let n=t.getAttribute(e);return n==null?null:n.startsWith("key:")?Number(a[n.slice(4)]??0):Number(n)}function vt(t,e){let n=e.getAttribute("data-wd-loop-sort");if(n!=null){let s=n.startsWith("key:"),c=s?String(a[n.slice(4)]??""):n;if(!s||c){let i=e.getAttribute("data-wd-loop-sort-dir")||"asc";i.startsWith("key:")&&(i=String(a[i.slice(4)]??"asc"));let u=i==="desc"?-1:1;t=t.map((l,m)=>({value:l,index:m})).sort((l,m)=>{let h=f(l.value,c||null),d=f(m.value,c||null),p=typeof h=="number"&&typeof d=="number"?h-d:String(h).localeCompare(String(d));return p?p*u:l.index-m.index}).map(l=>l.value)}}e.hasAttribute("data-wd-loop-reverse")&&t.reverse();let r=at(e,"data-wd-loop-offset");r&&(t=t.slice(r));let o=at(e,"data-wd-loop-limit");return o!=null&&(t=t.slice(0,o)),t}function At(t,e,n,r){let o=String(!!n);if(t.getAttribute("data-wd-if-active")===o)return!1;t.setAttribute("data-wd-if-active",o);let s=t.querySelector(`template[data-${r}-${o}]`);return v(e),e.innerHTML=s?.innerHTML||"",_=!0}function Nt(t){let e=t.getAttribute("data-wd-if-expr"),n=e?K(U(e),void 0):f(a[t.getAttribute("data-wd-if")||""],t.getAttribute("data-wd-path")),r=t.querySelector("[data-wd-if-out]");if(!(!r||!At(t,r,n,"wd")))for(let o of r.querySelectorAll("[data-wd-if]"))Nt(o)}function Ot(t,e){let n=t.querySelector("template[data-wd-loop-template]"),r=t.querySelector("[data-wd-loop-out]");if(!n||!r)return;let o=Array.isArray(e)?e.slice():[],s=t.getAttribute("data-wd-loop-where");if(s){let d=U(s);o=o.filter(p=>K(d,p))}o=vt(o,t);let c=t.querySelector("template[data-wd-loop-empty]");if(!o.length&&c){if(r.getAttribute("data-wd-empty")!=="1"){v(r),r.textContent="";for(let d of[...c.content.children])r.appendChild(d.cloneNode(!0));r.setAttribute("data-wd-empty","1"),_=!0}return}r.getAttribute("data-wd-empty")==="1"&&(v(r),r.textContent="",r.removeAttribute("data-wd-empty"));let i=new Map;for(let d of[...r.children])i.set(d.getAttribute("data-wd-loop-key")||"",d);let u=new Map,l=new Set,m=o.length,h=!1;for(let d=0;d<m;d++){let p=o[d],S=$t(p,u),w=i.get(S);if(!w||l.has(S)){if(w=n.content.firstElementChild?.cloneNode(!0),!w)continue;w.setAttribute("data-wd-loop-key",S),_=!0}l.add(S),St(w,p,{index:d,number:d+1,first:d===0,last:d===m-1,count:m}),w.__wdItem=p,(h||r.children[d]!==w)&&(h=!0,r.appendChild(w))}for(let[d,p]of i)l.has(d)||(v(p),p.remove())}var ct=0;function R(){for(let t=0;t<3;t++){_&&Pt(),_=!1,Et();for(let e of document.querySelectorAll("[data-wd-if]"))Nt(e);for(let e of document.querySelectorAll("[data-wd-class]"))bt(e,e.getAttribute("data-wd-class"),void 0);for(let e of document.querySelectorAll("[data-wd-loop]")){let n=e.getAttribute("data-wd-loop"),r=e.getAttribute("data-wd-loop-data"),o=n?n.indexOf("."):-1;Ot(e,n?o<0?a[n]:f(a[n.slice(0,o)],n.slice(o+1)):r?JSON.parse(r):[])}if(!_)break}_&&++ct<10?(window.wd?.debug&&console.warn("[wd] render did not settle; scheduling another pass"),g()):ct=0;for(let t of document.querySelectorAll("[data-wd-bind]"))t.textContent=tt(t,f(a[t.getAttribute("data-wd-bind")||""],t.getAttribute("data-wd-path")))??"";for(let t of document.querySelectorAll("[data-wd-attr]"))wt(t,t.getAttribute("data-wd-attr"),void 0,{});for(let t of document.querySelectorAll("[data-wd-bind-input]")){let e=a[t.getAttribute("data-wd-bind-input")||""]??"";t.type==="checkbox"?t.checked=!!e:t.type==="radio"?t.checked=t.value===String(e):document.activeElement!==t&&(t.value=e)}Dt(),Lt(),j.size&&Kt()}function Dt(){for(let t of document.querySelectorAll("[data-wd-theme]")){let e=a[t.getAttribute("data-wd-theme")||""],n=document.documentElement;e==null||e===""||e==="auto"?n.removeAttribute("data-theme"):n.setAttribute("data-theme",String(e))}}var D=[],V=0;function Lt(){if(!D.length)return;let t=!1;for(let e of D){let n=JSON.stringify(f(a,e.watch)??null);n!==e.last&&(e.last=n,_t(e.actions),t=!0)}if(!t){V=0;return}if(b(),++V>=10){V=0,console.warn("effect did not settle");return}R()}var jt=typeof requestAnimationFrame=="function"?requestAnimationFrame:queueMicrotask,Z=!1;function g(){Z||(Z=!0,jt(()=>{Z=!1,R()}))}document.addEventListener("input",t=>{let e=t.target?.closest("[data-wd-bind-input]");e&&(a[e.getAttribute("data-wd-bind-input")||""]=e.type==="range"?e.valueAsNumber:e.type==="checkbox"?e.checked:e.value,b(),g(),G())});function ut(t){if(!t)return null;let e=t.closest("[data-wd-loop-key]"),n=t.closest("[data-wd-loop]");return!e||!n?null:{srcKey:n.getAttribute("data-wd-loop"),item:e.__wdItem}}function et(t,e,n,r){let o=i=>Ct(a,n,i),s=f(a,n),c=()=>Array.isArray(s)?s:[];if(e==="inc"&&o(Number(s??0)+1),e==="dec"&&o(Number(s??0)-1),e==="add"&&o(Number(s??0)+Number(r)),e==="sub"&&o(Number(s??0)-Number(r)),e==="toggle"&&o(!s),e==="set"&&o(r),e==="append"&&o([...c(),r]),e==="prepend"&&o([r,...c()]),e==="member-toggle"&&o(c().includes(r)?c().filter(i=>i!==r):[...c(),r]),e==="remove-value"&&o(c().filter(i=>i!==r)),e==="clear"&&o(Array.isArray(s)?[]:{}),e==="merge"&&o({...s&&typeof s=="object"?s:{},...typeof r=="string"?f(a,r):r}),e==="delete"&&s&&typeof s=="object"&&(delete s[r],o(s)),e==="reset"&&o(structuredClone(f(M,n))),e==="refetch"){let i=document.querySelector(`[data-wd-fetch-key="${n}"]`);i&&L(i)}if(e==="remove"){let i=ut(t);i&&i.srcKey&&(a[i.srcKey]=(Array.isArray(a[i.srcKey])?a[i.srcKey]:[]).filter(u=>u!==i.item))}if(e==="append-row"){let i=ut(t);if(i&&i.item!==void 0){let u=i.item&&typeof i.item=="object"?structuredClone(i.item):i.item;o([...c(),u])}}}var _t=t=>{for(let e of t)et(null,e.op,e.target||"",structuredClone(e.value))};document.addEventListener("click",t=>{let e=t.target?.closest("[data-wd-action],[data-wd-actions]");if(!e)return;let n=e.getAttribute("data-wd-actions");if(n)for(let r of JSON.parse(n))et(e,r.op,r.target||"",r.value);else{let r=e.getAttribute("data-wd-value");et(e,e.getAttribute("data-wd-action")||"",e.getAttribute("data-wd-target")||"",r===null?void 0:JSON.parse(r))}b(),g(),G()});document.addEventListener("submit",t=>{let e=t.target?.closest("[data-wd-form]");if(!e)return;t.preventDefault();let n=e.getAttribute("data-wd-form")||"",r=e.getAttribute("action");if(!r){let i=new FormData(e),u=Object.fromEntries(i);for(let l of e.querySelectorAll("[data-wd-multi]")){let m=l.getAttribute("data-wd-multi");m&&(u[m]=i.getAll(m))}a[n]=u,b(),g();return}let o=yt(e);x(n),g();let s=new FormData(e);for(let i of e.querySelectorAll("[data-wd-bind-input]")){let u=i.getAttribute("data-wd-bind-input");u&&!i.getAttribute("name")&&!s.has(u)&&s.append(u,a[u]??"")}let c=e.querySelector("input[type=file]");rt(r,{method:e.getAttribute("method")||"post",headers:c?void 0:{"content-type":"application/x-www-form-urlencoded"},body:c?s:new URLSearchParams(s).toString()}).then(i=>{o()||(a[n]=i,x(n),b(),g())}).catch(i=>{o()||(x(n,i),g())})});function L(t){let e=yt(t),n=y=>t.getAttribute("data-wd-fetch-"+y)||"",r=n("key"),o=(y,A)=>{a[r+y]=A,g()},s=!1,c=n("url").replace(/\{\s*([\w$.]+)\s*\}/g,(y,A)=>{let N=f(a,A);return(N==null||N===""||N==="."||N==="..")&&(s=!0),encodeURIComponent(String(N??""))});if(s)return o("_loading",!1);let i=n("headers"),u=n("body"),l=n("refresh"),m=n("method")||"GET",h=Number(n("timeout")),d=Number(n("retry")),p=!1;x(r),o("_loading",!0);let S=y=>{x(r,y),o("_loading",!1)},w=()=>{let y={method:m};i&&(y.headers=a[i]||{}),u&&m!=="GET"&&(y.body=JSON.stringify(a[u]??null));let A=typeof AbortController=="function"?new AbortController:null;A&&(y.signal=A.signal);let N=h&&A?setTimeout(()=>A.abort(),h):0;rt(c,y).then(O=>{clearTimeout(N),!e()&&(a[r]=O,a[r+"_empty"]=Jt(O),x(r),o("_loading",!1))}).catch(O=>{if(clearTimeout(N),e())return;let st=O&&O.status===401;if(st&&l&&i&&!p)return p=!0,void Mt(l,i).then(qt=>{e()||(qt?w():S(O))});if(st&&p)return void S(O);if(d-- >0)return void setTimeout(w,200);S(O)})};w()}var Q=new Map;function Mt(t,e){let n=Q.get(t);if(n)return n;let r=rt(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(a[e]??{})}).then(o=>o&&typeof o=="object"?(a[e]=o,b(),!0):!1).catch(()=>!1).finally(()=>Q.delete(t));return Q.set(t,r),r}var ot=[],kt=t=>(t.getAttribute("data-wd-fetch-deps")||"").split(",").filter(Boolean).map(e=>JSON.stringify(f(a,e))).join("|"),dt=0,C=new Set;function G(){for(let t of ot){let e=kt(t);e&&e!==t.__wdSnap&&(t.__wdSnap=e,C.add(t))}C.size&&(clearTimeout(dt),dt=setTimeout(()=>{for(let t of C)L(t);C.clear()},150))}function v(t){for(let e of $(t,"[data-wd-fetch],[data-wd-effect],[data-wd-every]")){C.delete(e);for(let n of[ot,D,I])for(let r=n.length-1;r>=0;r--)(n[r].node||n[r])===e&&(n[r].t&&clearInterval(n[r].t),n.splice(r,1))}}function Pt(){mt();for(let t of document.querySelectorAll("[data-wd-fetch],[data-wd-effect],[data-wd-every]"))if(!t.__wdReg){if(t.__wdReg=1,t.hasAttribute("data-wd-effect")){let e=JSON.parse(t.textContent||"{}");D.push({node:t,watch:e.watch,actions:e.actions||[],last:JSON.stringify(f(a,e.watch)??null)})}if(t.hasAttribute("data-wd-every")&&(I.length||document.addEventListener("visibilitychange",()=>document.hidden?Ut():lt()),I.push({...JSON.parse(t.textContent||"{}"),node:t}),lt()),t.hasAttribute("data-wd-fetch"))if(ot.push(t),t.__wdSnap=kt(t),t.getAttribute("data-wd-fetch-when")==="visible"&&"IntersectionObserver"in window){let e=new IntersectionObserver(n=>{n.some(r=>r.isIntersecting)&&(e.disconnect(),L(t))});e.observe(t)}else L(t)}}var I=[];function lt(){if(!document.hidden)for(let t of I)t.t||!t.ms||!t.actions||(t.t=setInterval(()=>{_t(t.actions),b(),g(),G()},t.ms))}function Ut(){for(let t of I)clearInterval(t.t),t.t=0}var j=new Map;function Kt(){for(let[t,e]of j){let n=JSON.stringify(a[t]??null);if(n!==e.last){e.last=n;for(let r of e.cbs)try{r(a[t])}catch(o){P(`subscribe ${t}`,o)}}}}window.wd={state:a,debug:!1,get:t=>a[t],set:(t,e)=>{a[t]=e,b(),g(),G()},subscribe:(t,e)=>{let n=j.get(t);n||(n={cbs:new Set,last:JSON.stringify(a[t]??null)},j.set(t,n)),n.cbs.add(e);try{e(a[t])}catch(r){P(`subscribe ${t}`,r)}return()=>n.cbs.delete(e)},render:R};R();
|
|
2
2
|
//# sourceMappingURL=runtime.js.map
|