@zvndev/darkmown 2.12.3 → 2.13.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/src/builder.js CHANGED
@@ -1343,7 +1343,12 @@ function match(pathname) {
1343
1343
 
1344
1344
  export default {
1345
1345
  async fetch(request, env, ctx) {
1346
- const matched = match(new URL(request.url).pathname);
1346
+ let matched;
1347
+ try {
1348
+ matched = match(new URL(request.url).pathname);
1349
+ } catch {
1350
+ return Response.json({ ok: false, error: "Malformed URL encoding" }, { status: 400 });
1351
+ }
1347
1352
  if (!matched) return env.ASSETS.fetch(request);
1348
1353
  try {
1349
1354
  const response = await matched.handler(request, { params: matched.params, env });
@@ -64,6 +64,8 @@
64
64
  * @property {Assets} assets
65
65
  * @property {Map<string, unknown>} state Declared state keys → initial values.
66
66
  * @property {Set<string>} stores Page-global store names (a subset of state keys).
67
+ * @property {{ name: string, url: string, at: string }[]} fetchPlaceholders Every
68
+ * `{ name }` a `:fetch` URL reads, checked once the whole page has declared its state.
67
69
  * @property {Set<string>} fetchKeys Base state keys declared by `:fetch`, so
68
70
  * bare `:if <key>_loading` / `:if <key>_error` regions get compile-time
69
71
  * `role`/`aria-live` announcements.
@@ -320,6 +322,7 @@ export function createCompilation(reader) {
320
322
  state: new Map(),
321
323
  stores: new Set(),
322
324
  fetchKeys: new Set(),
325
+ fetchPlaceholders: [],
323
326
  warnings: [],
324
327
  sectionCounter: 0,
325
328
  headingSlugs: new Map(),
@@ -117,6 +117,7 @@ export function handleFetch(line, ctx, index) {
117
117
  validatePath(dep[1], ctx, FETCH_USE);
118
118
  const headKey = dep[1].split(".")[0];
119
119
  if (!deps.includes(headKey)) deps.push(headKey);
120
+ ctx.comp.fetchPlaceholders.push({ name: headKey, url, at: at(ctx, index) });
120
121
  }
121
122
 
122
123
  // Auto-declare the lifecycle keys. `name` is declared via declareState
@@ -38,6 +38,8 @@ export { unsafeUrlValue };
38
38
  // instances share it so `html: false` pages highlight too.
39
39
  const md = new MarkdownIt({ html: false, highlight: highlightCode });
40
40
  md.use(bindingPlugin);
41
+ md.use(unresolvedPlugin);
42
+ md.use(taskListPlugin);
41
43
  md.use(attrsPlugin);
42
44
  md.use(attrBindPlugin);
43
45
  md.use(imageVariantsPlugin);
@@ -63,6 +65,8 @@ export function selectMd(meta) {
63
65
  if (!mdRawHtml) {
64
66
  mdRawHtml = new MarkdownIt({ html: true, highlight: highlightCode });
65
67
  mdRawHtml.use(bindingPlugin);
68
+ mdRawHtml.use(unresolvedPlugin);
69
+ mdRawHtml.use(taskListPlugin);
66
70
  mdRawHtml.use(attrsPlugin);
67
71
  mdRawHtml.use(attrBindPlugin);
68
72
  mdRawHtml.use(imageVariantsPlugin);
@@ -145,6 +149,61 @@ function warnUnbindable(ctx, index, expr, painted) {
145
149
  );
146
150
  }
147
151
 
152
+ /**
153
+ * Names in reach of `head`, for the "did you mean" in {@link warnUnresolved}.
154
+ * @param {Ctx} ctx
155
+ * @returns {string[]}
156
+ */
157
+ function knownNames(ctx) {
158
+ const names = new Set([...ctx.comp.state.keys()].map((key) => key.split(":").pop() ?? key));
159
+ /** @type {typeof ctx.scope | null} */
160
+ let scope = ctx.scope;
161
+ for (; scope; scope = scope.parent) {
162
+ for (const name of Object.keys(scope.vars)) names.add(name);
163
+ }
164
+ return [...names];
165
+ }
166
+
167
+ /**
168
+ * Levenshtein distance, stopping the moment it can no longer be 2 or less.
169
+ * @param {string} a
170
+ * @param {string} b
171
+ * @returns {number}
172
+ */
173
+ function editDistance(a, b) {
174
+ if (Math.abs(a.length - b.length) > 2) return 3;
175
+ let row = Array.from({ length: b.length + 1 }, (_, i) => i);
176
+ for (let i = 1; i <= a.length; i++) {
177
+ const next = [i];
178
+ for (let j = 1; j <= b.length; j++) {
179
+ next[j] = Math.min(row[j] + 1, next[j - 1] + 1, row[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
180
+ }
181
+ row = next;
182
+ }
183
+ return row[b.length];
184
+ }
185
+
186
+ /**
187
+ * Warn once that `{ expr }` matched nothing, so it ships as the literal braces.
188
+ * A real page has no reason to show them, and `\{ name }` says it on purpose.
189
+ * @param {Ctx} ctx
190
+ * @param {number} index 0-based line index, for `file:line`.
191
+ * @param {string} expr
192
+ * @returns {void}
193
+ */
194
+ function warnUnresolved(ctx, index, expr) {
195
+ const head = expr.split(/[.|\s]/)[0];
196
+ const near = knownNames(ctx)
197
+ .map((name) => /** @type {[string, number]} */ ([name, editDistance(head, name)]))
198
+ .filter(([, distance]) => distance <= 2)
199
+ .sort((a, b) => a[1] - b[1])[0];
200
+ const hint = near ? `Did you mean "${near[0]}"? ` : "";
201
+ const message =
202
+ `${ctx.file}:${lineOf(ctx, index)}: "{ ${expr} }" matches no :state, include argument or in-scope value, so it ships as literal text. ` +
203
+ `${hint}Declare it above this line, or write \\{ ${expr} } if the braces are meant to show.`;
204
+ if (!ctx.comp.warnings.includes(message)) ctx.comp.warnings.push(message);
205
+ }
206
+
148
207
  // ---------------------------------------------------------------------------
149
208
  // REACTIVE ATTRIBUTE BINDING (a link/image destination).
150
209
  //
@@ -268,7 +327,10 @@ function resolveDestinationBindings(text, ctx, attrs, index) {
268
327
  /\{\s*([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\}/g,
269
328
  (/** @type {string} */ brace, /** @type {string} */ expr) => {
270
329
  const found = resolveBinding(expr, ctx);
271
- if (!found) return brace; // not in scope — leave the author's text alone
330
+ if (!found) {
331
+ warnUnresolved(ctx, index + i, expr);
332
+ return brace; // not in scope — leave the author's text alone
333
+ }
272
334
  if (found.kind === "static") {
273
335
  // The scheme test runs on the RAW value, before encoding, because
274
336
  // that is the string the runtime's guard sees. Encoding first would
@@ -695,14 +757,118 @@ const URL_ATTRS = new Set([
695
757
  "poster",
696
758
  "cite",
697
759
  "background",
698
- "ping"
760
+ "ping",
761
+ "to",
762
+ "from",
763
+ "by"
699
764
  ]);
700
765
 
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"'=<>`]*))$/;
766
+ // `{ path }` plus an optional `| pipe:arg` chain, as the inline binding rule reads it.
767
+ const BINDING_BRACE = /\{\s*([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*\s*(?:\|[^}]*)?)\}/g;
768
+
769
+ /** @param {string | undefined} c */
770
+ const isHtmlSpace = (c) => c === " " || c === "\t" || c === "\n" || c === "\f" || c === "\r";
771
+ const TEXT_CONTEXT = /** @type {RawContext} */ ({ kind: "text" });
772
+
773
+ /**
774
+ * @typedef {{ kind: "text" }
775
+ * | { kind: "tag" }
776
+ * | { kind: "value", name: string, head: string, quoted: boolean }
777
+ * | { kind: "rawtext", tag: string, type: string }} RawContext
778
+ */
779
+
780
+ /**
781
+ * Where the end of a raw-HTML fragment sits, as the browser's tokenizer would
782
+ * read it: in text, inside a tag but between attributes, inside an attribute
783
+ * value (with the value so far), or inside a `<script>`/`<style>` body. It walks
784
+ * quoted values rather than pattern-matching back from the end, because a `>`
785
+ * inside `title=">"` does not close the tag.
786
+ * @param {string} html
787
+ * @returns {RawContext}
788
+ */
789
+ function rawHtmlContext(html) {
790
+ const n = html.length;
791
+ let i = 0;
792
+ for (;;) {
793
+ const lt = html.indexOf("<", i);
794
+ if (lt < 0) return TEXT_CONTEXT;
795
+ const next = html[lt + 1];
796
+ // `<` then the value: whatever the value starts with decides the tag name.
797
+ if (next === undefined) return { kind: "tag" };
798
+ if (html.startsWith("<!--", lt)) {
799
+ const end = html.indexOf("-->", lt + 4);
800
+ if (end < 0) return TEXT_CONTEXT;
801
+ i = end + 3;
802
+ continue;
803
+ }
804
+ if (next === "/" && lt + 2 === n) return { kind: "tag" };
805
+ const closing = next === "/" && /[A-Za-z]/.test(html[lt + 2]);
806
+ if (!closing && !/[A-Za-z]/.test(next)) {
807
+ if (next !== "!" && next !== "?" && next !== "/") {
808
+ i = lt + 1;
809
+ continue;
810
+ }
811
+ // A bogus comment runs to the next `>`, and an escaped value cannot hold one.
812
+ const end = html.indexOf(">", lt + 2);
813
+ if (end < 0) return TEXT_CONTEXT;
814
+ i = end + 1;
815
+ continue;
816
+ }
817
+ let j = lt + (closing ? 2 : 1);
818
+ const nameStart = j;
819
+ while (j < n && !isHtmlSpace(html[j]) && html[j] !== "/" && html[j] !== ">") j++;
820
+ const tag = html.slice(nameStart, j).toLowerCase();
821
+ let type = "";
822
+ for (;;) {
823
+ while (j < n && (isHtmlSpace(html[j]) || html[j] === "/")) j++;
824
+ if (j >= n) return { kind: "tag" };
825
+ if (html[j] === ">") {
826
+ j++;
827
+ break;
828
+ }
829
+ const attrStart = j++;
830
+ while (j < n && !isHtmlSpace(html[j]) && !"/>=".includes(html[j])) j++;
831
+ const name = html.slice(attrStart, j).toLowerCase();
832
+ while (j < n && isHtmlSpace(html[j])) j++;
833
+ if (j >= n) return { kind: "tag" };
834
+ if (html[j] !== "=") continue;
835
+ j++;
836
+ while (j < n && isHtmlSpace(html[j])) j++;
837
+ if (j >= n) return { kind: "value", name, head: "", quoted: false };
838
+ let value;
839
+ const quote = html[j];
840
+ if (quote === '"' || quote === "'") {
841
+ const end = html.indexOf(quote, j + 1);
842
+ if (end < 0) return { kind: "value", name, head: html.slice(j + 1), quoted: true };
843
+ value = html.slice(j + 1, end);
844
+ j = end + 1;
845
+ } else {
846
+ const valueStart = j;
847
+ while (j < n && !isHtmlSpace(html[j]) && html[j] !== ">") j++;
848
+ value = html.slice(valueStart, j);
849
+ if (j >= n) return { kind: "value", name, head: value, quoted: false };
850
+ }
851
+ if (name === "type") type = value.trim().toLowerCase();
852
+ }
853
+ if (!closing && (tag === "script" || tag === "style")) {
854
+ const end = html.slice(j).search(new RegExp(`</${tag}[\\s/>]`, "i"));
855
+ if (end < 0) return { kind: "rawtext", tag, type };
856
+ i = j + end + 2;
857
+ continue;
858
+ }
859
+ i = j;
860
+ }
861
+ }
862
+
863
+ /**
864
+ * A JSON `<script>` is data the browser never runs, and an escaped value cannot
865
+ * close it, so a binding there is safe. Every other script or style body is code.
866
+ * @param {string} type
867
+ * @returns {boolean}
868
+ */
869
+ function isDataScript(type) {
870
+ return /^application\/(?:[\w.-]+\+)?json$/.test(type);
871
+ }
706
872
 
707
873
  /**
708
874
  * Warn that a value in URL position carries a scheme the browser would execute.
@@ -752,7 +918,7 @@ function fillRawHtml(html, ctx, index) {
752
918
  // The capture mirrors the inline `wd_binding` rule: path plus an optional
753
919
  // `| pipe:arg` chain. A pipe-only divergence here left `{ d | date }` literal
754
920
  // in raw HTML while the same expression folded in prose.
755
- const brace = /\{\s*([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*\s*(?:\|[^}]*)?)\}/g;
921
+ const brace = new RegExp(BINDING_BRACE);
756
922
  let out = "";
757
923
  let last = 0;
758
924
  /** @type {RegExpExecArray | null} */
@@ -762,7 +928,10 @@ function fillRawHtml(html, ctx, index) {
762
928
  last = hit.index + hit[0].length;
763
929
  const expr = hit[1].trim();
764
930
  const found = resolveBinding(expr, ctx);
931
+ const context = rawHtmlContext(out);
765
932
  if (!found) {
933
+ // Inside a script or style, `{ x }` is usually the language's own braces.
934
+ if (context.kind !== "rawtext") warnUnresolved(ctx, index, expr);
766
935
  out += hit[0];
767
936
  continue;
768
937
  }
@@ -775,33 +944,100 @@ function fillRawHtml(html, ctx, index) {
775
944
  // Escaping only holds inside a quoted attribute or text. In a script-valued
776
945
  // attribute the browser entity-decodes `&#39;` back into a live quote, and
777
946
  // 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) {
947
+ if (context.kind === "tag") {
782
948
  warnUnsafeSlot(ctx, index, expr, "inside a tag but outside any attribute value");
783
949
  continue;
784
950
  }
951
+ if (context.kind === "rawtext") {
952
+ if (context.tag === "style" || !isDataScript(context.type)) {
953
+ warnUnsafeSlot(ctx, index, expr, `in a <${context.tag}> body`);
954
+ continue;
955
+ }
956
+ out += escapeHtml(found.text);
957
+ continue;
958
+ }
959
+ if (context.kind === "text") {
960
+ out += escapeHtml(found.text);
961
+ continue;
962
+ }
963
+ const { name, head, quoted } = context;
785
964
  if (name.startsWith("on") || name === "srcdoc") {
786
965
  warnUnsafeSlot(ctx, index, expr, `in the ${name}= attribute`);
787
966
  continue;
788
967
  }
789
- const head = open ? (open[2] ?? open[3] ?? open[4]) : "";
790
- if (URL_ATTRS.has(name) && unsafeUrlValue(head + found.text)) {
968
+ // SVG `values` is a `;`-separated list, and any entry can animate a link's href.
969
+ const assembled = head + found.text;
970
+ const unsafe =
971
+ name === "values"
972
+ ? assembled.split(";").some(unsafeUrlValue)
973
+ : URL_ATTRS.has(name) && unsafeUrlValue(assembled);
974
+ if (unsafe) {
791
975
  warnUnsafeUrl(ctx, index, expr);
792
976
  out = out.slice(0, out.length - head.length); // the head belongs to the refused value
793
977
  continue;
794
978
  }
795
979
  // An unquoted value ends at whitespace, so `x onmouseover=…` would open a
796
980
  // 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);
981
+ out += quoted
982
+ ? escapeHtml(found.text)
983
+ : escapeHtml(found.text).replace(/[\s=`]/g, (c) => `&#${c.charCodeAt(0)};`);
801
984
  }
802
985
  return out + html.slice(last);
803
986
  }
804
987
 
988
+ /**
989
+ * markdown-it plugin (core rule): a `{ name }` still in a text token after the
990
+ * inline pass is one {@link bindingPlugin} could not resolve. Runs before
991
+ * `text_join`, which would merge an escaped `\{` back in and make it look unresolved.
992
+ * @param {MarkdownIt} mdInstance
993
+ * @returns {void}
994
+ */
995
+ function unresolvedPlugin(mdInstance) {
996
+ mdInstance.core.ruler.after("inline", "wd_unresolved", (state) => {
997
+ const wd = state.env?.wd;
998
+ if (!wd) return false;
999
+ for (const block of state.tokens) {
1000
+ const line = wd.index + (block.map ? block.map[0] : 0);
1001
+ let offset = 0;
1002
+ for (const child of block.children ?? []) {
1003
+ if (child.type === "softbreak" || child.type === "hardbreak") offset++;
1004
+ if (child.type !== "text") continue;
1005
+ for (const hit of child.content.matchAll(BINDING_BRACE)) {
1006
+ warnUnresolved(wd.ctx, line + offset, hit[1].trim());
1007
+ }
1008
+ }
1009
+ }
1010
+ return false;
1011
+ });
1012
+ }
1013
+
1014
+ /**
1015
+ * markdown-it plugin (core rule): `- [ ] item` in a `.wd` renders a read-only
1016
+ * checkbox, so a checklist survives a markdown document becoming a `.wd`. `.md`
1017
+ * stays strict CommonMark: it renders with no `wd` env, and this rule is a no-op
1018
+ * without one. Static output, so a static page stays `runtime: false`. A checkbox
1019
+ * the reader can tick is `:checkbox`.
1020
+ * @param {MarkdownIt} mdInstance
1021
+ * @returns {void}
1022
+ */
1023
+ function taskListPlugin(mdInstance) {
1024
+ mdInstance.core.ruler.after("wd_unresolved", "wd_task_list", (state) => {
1025
+ if (!state.env?.wd) return false;
1026
+ state.tokens.forEach((block, i) => {
1027
+ if (block.type !== "inline" || state.tokens[i - 2]?.type !== "list_item_open") return;
1028
+ const first = block.children?.[0];
1029
+ const box = first?.type === "text" ? /^\[([ xX])\](?=\s|$)\s*/.exec(first.content) : null;
1030
+ if (!first || !box) return;
1031
+ first.content = first.content.slice(box[0].length);
1032
+ const input = new state.Token("html_inline", "", 0);
1033
+ input.content = `<input type="checkbox" disabled${box[1] === " " ? "" : " checked"}> `;
1034
+ input.meta = { wdText: "" };
1035
+ block.children?.unshift(input);
1036
+ });
1037
+ return false;
1038
+ });
1039
+ }
1040
+
805
1041
  /**
806
1042
  * markdown-it plugin: turns `{ name.path }` into an inline `html_inline` token
807
1043
  * whose content comes from the env's `resolveBinding` callback.
@@ -489,6 +489,15 @@ export function compileDocument(
489
489
  0,
490
490
  null
491
491
  );
492
+ // Checked after the whole page, not at the :fetch line: the runtime reads state
493
+ // when the request fires, so a :state further down still fills the placeholder.
494
+ for (const { name, url, at } of comp.fetchPlaceholders) {
495
+ if (!comp.state.has(name)) {
496
+ comp.warnings.push(
497
+ `${at}: :fetch "${url}" reads "{ ${name} }", but the page declares no :state ${name}, so the request never fires until something sets it. Declare it with :state ${name} = …, or set it from a colocated .js.`
498
+ );
499
+ }
500
+ }
492
501
  // Shared skins (frontmatter `skins:`) join the page's asset set through the
493
502
  // exact same pipeline as colocated skins (compile through compileSkin at emit,
494
503
  // scoping honored), and the CSS cascade is then ordered so the page always
package/src/dev.js CHANGED
@@ -51,7 +51,7 @@ export function refuseLocalRequest(req, { bindHost = "", allowedHosts = "" } = {
51
51
  if (!host || !(isLoopbackName(host) || isIP(host) || host === bind || allowed.includes(host))) {
52
52
  return `Host "${req.headers.host ?? ""}" is not allowed. Set DARKMOWN_ALLOWED_HOSTS to permit it.`;
53
53
  }
54
- if (!(req.url || "/").startsWith("/api/")) return null;
54
+ if (!isApiPath(req.url || "/")) return null;
55
55
  if (req.headers["sec-fetch-site"] === "cross-site") return "Cross-site request to /api/ refused.";
56
56
  const origin = req.headers.origin;
57
57
  if (origin !== undefined) {
@@ -66,6 +66,22 @@ export function refuseLocalRequest(req, { bindHost = "", allowedHosts = "" } = {
66
66
  return null;
67
67
  }
68
68
 
69
+ /**
70
+ * Whether a request reaches the `api/` runner: the same URL parse and the same
71
+ * test as `handleApiRequest`, so `/api`, `/api?x` and `/./api/x` are all guarded.
72
+ * A URL it cannot parse gets the stricter answer.
73
+ * @param {string} url
74
+ * @returns {boolean}
75
+ */
76
+ function isApiPath(url) {
77
+ try {
78
+ const { pathname } = new URL(url, "http://localhost");
79
+ return pathname === "/api" || pathname.startsWith("/api/");
80
+ } catch {
81
+ return true;
82
+ }
83
+ }
84
+
69
85
  /**
70
86
  * Serialize the dev server's rebuilds: at most ONE child build runs at a time.
71
87
  * Every build read-modify-writes the dependency map (`dist/.wd-dev-deps.json`),
package/src/runtime.js CHANGED
@@ -1090,8 +1090,10 @@ function startFetch(node) {
1090
1090
  // `.`/`..` survive encodeURIComponent and the URL parser resolves them, so
1091
1091
  // `?id=..` would retarget `/api/users/{ id }/x` (headers and body included)
1092
1092
  // at `/api/x`. Treat them as missing, like an empty value.
1093
- if (v == null || v === "" || v === "." || v === "..") missing = true;
1094
- return encodeURIComponent(String(v ?? ""));
1093
+ // String() first: `?id=[".."]` seeds an array, which stringifies to `..`.
1094
+ const text = String(v ?? "");
1095
+ if (text === "" || text === "." || text === "..") missing = true;
1096
+ return encodeURIComponent(text);
1095
1097
  });
1096
1098
  if (missing) return set("_loading", false);
1097
1099
 
@@ -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)):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();
1
+ var a={},ft=new Set,nt=new Set,J=new Set,M={};function C(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=C(c);i===null?e&&C(c,JSON.stringify(o??null)):T(i)===void 0?C(c,null):a[r]=T(i)}t.getAttribute("data-wd-url")===r&&(J.add(r),pt(!1,[r]))}}}function qt(){if(!J.size)return;let t=new URLSearchParams(location.search);for(let r of J){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||J){let o=n.get(X(r)),s=M[r];o==null?t&&(a[r]=structuredClone(s)):a[r]=typeof s=="string"?o:T(o)??o}}function S(){for(let t of ft)C(`wd:${t}`,JSON.stringify(a[t]??null));for(let t of nt)C(`wd:store:${t}`,JSON.stringify(a[t]??null));qt()}mt();window.addEventListener("popstate",()=>{J.size&&(pt(!0),S(),y())});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=T(t.newValue);n!==void 0&&JSON.stringify(n)!==JSON.stringify(a[e])&&(a[e]=n,y())});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 T(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 k=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=>k(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(k(t)),number:(t,[e])=>new Intl.NumberFormat(void 0,e==null?{}:{minimumFractionDigits:+e,maximumFractionDigits:+e}).format(k(t)),percent:(t,[e=0])=>new Intl.NumberFormat(void 0,{style:"percent",minimumFractionDigits:+e,maximumFractionDigits:+e}).format(k(t)),round:(t,[e=0])=>{let n=10**+e;return Math.round(k(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=k(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?z(n,e).reduce((r,o)=>Math.min(r,o)):0},max:(t,[e])=>{let n=_(t);return n.length?z(n,e).reduce((r,o)=>Math.max(r,o)):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=T(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 $(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 b=f(l.value,c||null),d=f(m.value,c||null),p=typeof b=="number"&&typeof d=="number"?b-d:String(b).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||"",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=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"),O=!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,b=!1;for(let d=0;d<m;d++){let p=o[d],A=$t(p,u),g=i.get(A);if(!g||l.has(A)){if(g=n.content.firstElementChild?.cloneNode(!0),!g)continue;g.setAttribute("data-wd-loop-key",A),O=!0}l.add(A),St(g,p,{index:d,number:d+1,first:d===0,last:d===m-1,count:m}),g.__wdItem=p,(b||r.children[d]!==g)&&(b=!0,r.appendChild(g))}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++){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"),y()):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(S(),++V>=10){V=0,console.warn("effect did not settle");return}R()}var jt=typeof requestAnimationFrame=="function"?requestAnimationFrame:queueMicrotask,Z=!1;function y(){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,S(),y(),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))}S(),y(),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,S(),y();return}let o=yt(e);x(n),y();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),S(),y())}).catch(i=>{o()||(x(n,i),y())})});function L(t){let e=yt(t),n=h=>t.getAttribute("data-wd-fetch-"+h)||"",r=n("key"),o=(h,N)=>{a[r+h]=N,y()},s=!1,c=n("url").replace(/\{\s*([\w$.]+)\s*\}/g,(h,N)=>{let I=f(a,N),w=String(I??"");return(w===""||w==="."||w==="..")&&(s=!0),encodeURIComponent(w)});if(s)return o("_loading",!1);let i=n("headers"),u=n("body"),l=n("refresh"),m=n("method")||"GET",b=Number(n("timeout")),d=Number(n("retry")),p=!1;x(r),o("_loading",!0);let A=h=>{x(r,h),o("_loading",!1)},g=()=>{let h={method:m};i&&(h.headers=a[i]||{}),u&&m!=="GET"&&(h.body=JSON.stringify(a[u]??null));let N=typeof AbortController=="function"?new AbortController:null;N&&(h.signal=N.signal);let I=b&&N?setTimeout(()=>N.abort(),b):0;rt(c,h).then(w=>{clearTimeout(I),!e()&&(a[r]=w,a[r+"_empty"]=Jt(w),x(r),o("_loading",!1))}).catch(w=>{if(clearTimeout(I),e())return;let st=w&&w.status===401;if(st&&l&&i&&!p)return p=!0,void Mt(l,i).then(xt=>{e()||(xt?g():A(w))});if(st&&p)return void A(w);if(d-- >0)return void setTimeout(g,200);A(w)})};g()}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,S(),!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,q=new Set;function G(){for(let t of ot){let e=kt(t);e&&e!==t.__wdSnap&&(t.__wdSnap=e,q.add(t))}q.size&&(clearTimeout(dt),dt=setTimeout(()=>{for(let t of q)L(t);q.clear()},150))}function v(t){for(let e of $(t,"[data-wd-fetch],[data-wd-effect],[data-wd-every]")){q.delete(e);for(let n of[ot,D,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||"{}");D.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=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 E=[];function lt(){if(!document.hidden)for(let t of E)t.t||!t.ms||!t.actions||(t.t=setInterval(()=>{_t(t.actions),S(),y(),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,S(),y(),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