@dirwell/theme-ledger 0.0.0-stage → 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,605 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
3
+ import { copyJsonObject, escapeHtml, projectListingEntry } from "@dirwell/core";
4
+ import path from "node:path";
5
+ import { render } from "svelte/server";
6
+ import * as $ from "svelte/internal/server";
7
+ import { onMount, untrack } from "svelte";
8
+ import { ListingDataError, loadListing, parseListingEntries } from "@dirwell/core/client";
9
+ //#region ../shared-theme/src/page.ts
10
+ const pageAsset = (theme, directory, page = 1) => `${theme}-page-${createHash("sha256").update(directory).digest("hex").slice(0, 24)}-${page}.json`;
11
+ function folderDataHref(entry, context, theme) {
12
+ if (entry.exitsExplorer || entry.href === null || !(entry.kind === "directory" || entry.link?.targetKind === "directory")) return null;
13
+ const fake = "https://dirwell.invalid/";
14
+ const base = new URL(context.documentBaseHref ?? `${context.directoryPath ? context.directoryPath + "/" : ""}`, fake);
15
+ const root = new URL(context.hrefForDirectory(""), base);
16
+ const target = new URL(entry.href, base);
17
+ const rootPath = root.pathname.endsWith("/") ? root.pathname : root.pathname.slice(0, root.pathname.lastIndexOf("/") + 1);
18
+ const directoryPath = target.pathname.endsWith("/") ? target.pathname : target.pathname.slice(0, target.pathname.lastIndexOf("/") + 1);
19
+ const logical = directoryPath.slice(rootPath.length).split("/").filter(Boolean).map(decodeURIComponent).join("/");
20
+ const filename = pageAsset(theme, logical);
21
+ if (context.pageDataHref !== void 0) return context.pageDataHref(theme, logical, 1);
22
+ if (context.mode === "mpa") return context.assetHref(filename);
23
+ const href = new URL(filename, new URL(directoryPath, target));
24
+ if (href.origin !== new URL(fake).origin) return href.href;
25
+ if (context.documentBaseHref === null && !entry.href.startsWith("/")) return path.posix.relative(context.directoryPath || ".", path.posix.join(logical, filename)).split("/").map((segment) => segment === ".." || segment === "." ? segment : encodeURIComponent(segment)).join("/");
26
+ return href.pathname;
27
+ }
28
+ function createThemePage(context, theme, title) {
29
+ const directoryPath = context.directory.current.relativePath;
30
+ const safeContext = {
31
+ ...context.pageDataHref === void 0 ? {} : { pageDataHref: context.pageDataHref },
32
+ mode: context.mode,
33
+ outputName: context.outputName,
34
+ assetHref: context.assetHref,
35
+ hrefForDirectory: context.hrefForDirectory,
36
+ documentBaseHref: context.documentBaseHref,
37
+ directoryPath
38
+ };
39
+ const dataForDirectory = (logicalPath) => folderDataHref({
40
+ id: logicalPath,
41
+ name: logicalPath,
42
+ kind: "directory",
43
+ href: context.hrefForDirectory(logicalPath),
44
+ exitsExplorer: false,
45
+ size: 0,
46
+ modifiedAt: "",
47
+ link: null
48
+ }, safeContext, theme.name);
49
+ const entries = context.directory.entries.map((entry) => {
50
+ const record = projectListingEntry(entry, context);
51
+ return theme.entryData === void 0 ? record : {
52
+ ...record,
53
+ presentation: copyJsonObject(theme.entryData(record, safeContext))
54
+ };
55
+ });
56
+ return {
57
+ version: 1,
58
+ ...context.metadata === void 0 ? {} : { metadataHead: context.metadata.head },
59
+ theme: theme.name,
60
+ title: context.metadata?.title ?? (directoryPath ? `${directoryPath} · ${title}` : title),
61
+ path: directoryPath,
62
+ rootHref: context.hrefForDirectory(""),
63
+ parentHref: context.directory.parent === null ? null : context.hrefForDirectory(context.directory.parent.relativePath),
64
+ rootDataHref: dataForDirectory(""),
65
+ parentDataHref: context.directory.parent === null ? null : dataForDirectory(context.directory.parent.relativePath),
66
+ dataHref: context.pageDataHref?.(theme.name, directoryPath, context.listing?.page.number ?? 1) ?? context.assetHref(pageAsset(theme.name, directoryPath, context.listing?.page.number ?? 1)),
67
+ manifestHref: context.listing?.manifestHref ?? null,
68
+ ...context.listing ? { listingRevision: context.listing.revision } : {},
69
+ updatesHref: context.updatesHref ?? null,
70
+ total: context.listing?.page.total ?? entries.length,
71
+ entries,
72
+ sort: context.sort,
73
+ page: context.listing?.page ?? null,
74
+ breadcrumbs: directoryPath.split("/").filter(Boolean).map((name, index) => ({
75
+ name,
76
+ dataHref: dataForDirectory(directoryPath.split("/").slice(0, index + 1).join("/")),
77
+ href: context.hrefForDirectory(directoryPath.split("/").slice(0, index + 1).join("/"))
78
+ }))
79
+ };
80
+ }
81
+ //#endregion
82
+ //#region ../shared-theme/src/icons.ts
83
+ const paths = {
84
+ directory: "<path d=\"M3 7V5h6l2 3h10v12H3Z\"/><path d=\"M3 10h18\"/>",
85
+ file: "<path d=\"M5 3h9l5 5v13H5ZM14 3v5h5M8 12h8M8 16h6\"/>",
86
+ link: "<path d=\"m9 15 6-6M8 10l-3 3a4 4 0 0 0 6 6l3-3M10 8l3-3a4 4 0 0 1 6 6l-3 3\"/>"
87
+ };
88
+ /** Fixed authored geometry; source names and paths never enter SVG markup. */
89
+ function entryIcon(kind) {
90
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.45" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths[kind]}</svg>`;
91
+ }
92
+ //#endregion
93
+ //#region ../shared-theme/src/navigation.ts
94
+ /** Folder snapshots resolve relative URLs before History changes the document URL. */
95
+ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false, attempt = 0) {
96
+ const source = new URL(dataHref, document.baseURI);
97
+ if (source.origin !== location.origin) throw new ListingDataError("Folder leaves the theme origin");
98
+ const response = await fetch(source, {
99
+ signal,
100
+ cache: fresh ? "no-store" : "default"
101
+ });
102
+ if (!response.ok) throw new ListingDataError("Unavailable folder");
103
+ const value = await response.json();
104
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new ListingDataError("Invalid folder snapshot");
105
+ const data = value;
106
+ if (data.version !== 1 || data.theme !== theme || typeof data.title !== "string" || typeof data.path !== "string" || !Number.isSafeInteger(data.total) || Number(data.total) < 0 || !Array.isArray(data.breadcrumbs)) throw new ListingDataError("Unsupported folder snapshot");
107
+ if (data.metadataHead !== void 0 && typeof data.metadataHead !== "string") throw new ListingDataError("Invalid folder metadata");
108
+ const base = document.querySelector("base")?.href ?? pageHref;
109
+ const link = (raw) => {
110
+ if (raw === null) return null;
111
+ if (typeof raw !== "string") throw new ListingDataError("Invalid folder link");
112
+ const url = new URL(raw, base);
113
+ if (!["http:", "https:"].includes(url.protocol) || url.origin !== location.origin) throw new ListingDataError("Unsupported folder link");
114
+ return url.href;
115
+ };
116
+ const entries = parseListingEntries(data.entries, base).map((entry) => ({
117
+ ...entry,
118
+ ...entry.presentation ? { presentation: {
119
+ ...entry.presentation,
120
+ folderDataHref: link(entry.presentation.folderDataHref ?? null)
121
+ } } : {}
122
+ }));
123
+ if (entries.length > Number(data.total)) throw new ListingDataError("Invalid folder count");
124
+ const result = {
125
+ ...data,
126
+ updatesHref: link(data.updatesHref ?? null),
127
+ entries,
128
+ dataHref: source.href,
129
+ manifestHref: link(data.manifestHref),
130
+ rootHref: link(data.rootHref),
131
+ rootDataHref: link(data.rootDataHref),
132
+ parentHref: link(data.parentHref),
133
+ parentDataHref: link(data.parentDataHref),
134
+ breadcrumbs: data.breadcrumbs.map((raw) => {
135
+ if (typeof raw !== "object" || raw === null || !("name" in raw) || !("href" in raw) || !("dataHref" in raw) || typeof raw.name !== "string") throw new ListingDataError("Invalid breadcrumb");
136
+ return {
137
+ name: raw.name,
138
+ href: link(raw.href),
139
+ dataHref: link(raw.dataHref)
140
+ };
141
+ })
142
+ };
143
+ if (fresh && result.manifestHref) {
144
+ const loaded = await loadListing(result.manifestHref, {
145
+ signal,
146
+ cache: "no-store"
147
+ });
148
+ if (loaded.manifest.total !== data.total || data.listingRevision !== void 0 && loaded.manifest.revision !== data.listingRevision) {
149
+ if (attempt < 2) return fetchThemePage(dataHref, pageHref, theme, signal, true, attempt + 1);
150
+ throw new ListingDataError("Folder changed while refreshing");
151
+ }
152
+ result.entries = [...loaded.entries];
153
+ }
154
+ return result;
155
+ }
156
+ const hotModels = /* @__PURE__ */ new WeakMap();
157
+ function restoreThemePage(initial, hot) {
158
+ if (!hot || typeof document === "undefined") return initial;
159
+ const root = document.querySelector("[data-dirwell-theme]");
160
+ if (!root) return initial;
161
+ const snapshot = hotModels.get(root) ?? initial;
162
+ hotModels.set(root, snapshot);
163
+ return snapshot;
164
+ }
165
+ /** Copy only inert metadata elements; never insert snapshot HTML or event attributes. */
166
+ function applyThemeMetadata(page, pageHref) {
167
+ const root = document.querySelector("[data-dirwell-theme]");
168
+ if (root && hotModels.has(root)) hotModels.set(root, page);
169
+ document.title = typeof page.title === "string" ? page.title : "";
170
+ const template = document.createElement("template");
171
+ template.innerHTML = typeof page.metadataHead === "string" ? page.metadataHead : "";
172
+ document.head.querySelectorAll("[data-dirwell-metadata]").forEach((element) => element.remove());
173
+ const base = document.querySelector("base")?.href ?? pageHref;
174
+ const url = (value) => {
175
+ try {
176
+ const target = new URL(value, base);
177
+ return ["http:", "https:"].includes(target.protocol) ? target.href : null;
178
+ } catch {
179
+ return null;
180
+ }
181
+ };
182
+ for (const element of template.content.children) {
183
+ if (!element.hasAttribute("data-dirwell-metadata")) continue;
184
+ let next;
185
+ if (element.tagName === "META") {
186
+ const attribute = element.hasAttribute("property") ? "property" : "name";
187
+ const key = element.getAttribute(attribute) ?? "";
188
+ if (!/^(?:description|og:[a-z_:]+|twitter:[a-z_:]+)$/i.test(key)) continue;
189
+ let content = element.getAttribute("content") ?? "";
190
+ if ([
191
+ "og:image",
192
+ "og:url",
193
+ "twitter:image"
194
+ ].includes(key)) {
195
+ const resolved = url(content);
196
+ if (resolved === null) continue;
197
+ content = resolved;
198
+ }
199
+ next = document.createElement("meta");
200
+ next.setAttribute(attribute, key);
201
+ next.setAttribute("content", content);
202
+ } else if (element.tagName === "LINK" && element.getAttribute("rel") === "canonical") {
203
+ const href = url(element.getAttribute("href") ?? "");
204
+ if (href === null) continue;
205
+ next = document.createElement("link");
206
+ next.setAttribute("rel", "canonical");
207
+ next.setAttribute("href", href);
208
+ } else continue;
209
+ next.setAttribute("data-dirwell-metadata", "");
210
+ document.head.append(next);
211
+ }
212
+ }
213
+ //#endregion
214
+ //#region ../shared-theme/src/records.ts
215
+ const directoryLike = (entry) => entry.kind === "directory" || entry.link?.targetKind === "directory";
216
+ const entryType = (entry) => entry.kind === "symlink" ? "link" : directoryLike(entry) ? "directory" : "file";
217
+ function fuzzy(query, value) {
218
+ let score = 0, start = 0, last = -2;
219
+ for (const character of query.trim().toLocaleLowerCase()) {
220
+ const index = value.toLocaleLowerCase().indexOf(character, start);
221
+ if (index < 0) return null;
222
+ if (index === last + 1) score += 8;
223
+ if (index === 0 || /[\s/_.-]/.test(value[index - 1] ?? "")) score += 5;
224
+ score += Math.max(0, 4 - index * .05);
225
+ last = index;
226
+ start = index + 1;
227
+ }
228
+ return score - value.length * .01;
229
+ }
230
+ function visibleRecords(entries, query, types, sort) {
231
+ return entries.map((entry) => ({
232
+ entry,
233
+ score: fuzzy(query, `${entry.name} ${entry.link?.target ?? ""}`)
234
+ })).filter((item) => types.includes(entryType(item.entry)) && item.score !== null).sort((left, right) => {
235
+ if (query.trim() && left.score !== right.score) return (right.score ?? 0) - (left.score ?? 0);
236
+ const a = left.entry, b = right.entry;
237
+ if (sort.directoriesFirst && directoryLike(a) !== directoryLike(b)) return directoryLike(a) ? -1 : 1;
238
+ const name = () => sort.nameMode === "unicode" ? compareUnicode(a.name, b.name) : a.name.localeCompare(b.name, void 0, {
239
+ numeric: sort.nameMode === "natural",
240
+ sensitivity: "base"
241
+ });
242
+ const primary = (sort.field === "name" ? name() : sort.field === "size" ? (a.kind === "directory" ? 0 : a.size) - (b.kind === "directory" ? 0 : b.size) : a.modifiedAt.localeCompare(b.modifiedAt)) || name();
243
+ return (sort.direction === "asc" ? primary : -primary) || compareUnicode(a.id, b.id);
244
+ }).map((item) => item.entry);
245
+ }
246
+ function compareUnicode(left, right) {
247
+ const a = [...left], b = [...right];
248
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
249
+ const delta = (a[i]?.codePointAt(0) ?? 0) - (b[i]?.codePointAt(0) ?? 0);
250
+ if (delta) return delta;
251
+ }
252
+ return a.length - b.length;
253
+ }
254
+ function size(bytes) {
255
+ const units = [
256
+ "B",
257
+ "KiB",
258
+ "MiB",
259
+ "GiB",
260
+ "TiB"
261
+ ];
262
+ let value = bytes, index = 0;
263
+ while (value >= 1024 && index < 4) {
264
+ value /= 1024;
265
+ index++;
266
+ }
267
+ return `${value.toFixed(index === 0 || value >= 100 ? 0 : 1)} ${units[index]}`;
268
+ }
269
+ function modified(value, local = false) {
270
+ const date = new Date(value);
271
+ if (Number.isNaN(date.getTime())) return "Unknown";
272
+ return local ? date.toLocaleString(void 0, {
273
+ dateStyle: "short",
274
+ timeStyle: "short"
275
+ }) : `${date.toISOString().slice(0, 16).replace("T", " ")} UTC`;
276
+ }
277
+ //#endregion
278
+ //#region .generated/App-server.mjs
279
+ function Ledger($$renderer, $$props) {
280
+ $$renderer.component(($$renderer) => {
281
+ let { model: initialModel, header, row, footer } = $$props;
282
+ const snapshot = untrack(() => restoreThemePage(initialModel, Boolean(import.meta.hot)));
283
+ let model = snapshot;
284
+ let entries = snapshot.entries;
285
+ let query = "";
286
+ let types = [
287
+ "directory",
288
+ "file",
289
+ "link"
290
+ ];
291
+ let sort = snapshot.sort;
292
+ let scheme = "system";
293
+ let selected = null;
294
+ let ready = false;
295
+ let busy = false;
296
+ let error = "";
297
+ let scrollTop = 0;
298
+ let heights = /* @__PURE__ */ new Map();
299
+ $.derived(() => ready && model.updatesHref ? new URL(model.updatesHref, document.baseURI).href : null);
300
+ let complete = $.derived(() => entries.length === model.total);
301
+ let rows = $.derived(() => visibleRecords(entries, query, types, sort));
302
+ let virtual = $.derived(() => ready && rows().length > 500);
303
+ let offsets = $.derived(() => {
304
+ const values = [0];
305
+ for (const entry of rows()) values.push(values.at(-1) + (heights.get(entry.id) ?? 76));
306
+ return values;
307
+ });
308
+ let start = $.derived(() => {
309
+ if (!virtual()) return 0;
310
+ let low = 0, high = rows().length;
311
+ while (low < high) {
312
+ const mid = low + high >> 1;
313
+ if (offsets()[mid + 1] <= scrollTop) low = mid + 1;
314
+ else high = mid;
315
+ }
316
+ return Math.max(0, low - 5);
317
+ });
318
+ let end = $.derived(() => virtual() ? Math.min(rows().length, start() + 35) : rows().length);
319
+ let navigation = 0;
320
+ let abort;
321
+ const isDirectory = directoryLike;
322
+ function safeHref(record) {
323
+ if (typeof location !== "undefined" && location.protocol === "file:" && isDirectory(record) && !record.exitsExplorer && record.href?.endsWith("/")) return record.href + "index.html";
324
+ return record.href;
325
+ }
326
+ async function hydrateData() {
327
+ if (complete() || !model.manifestHref || location.protocol === "file:") return;
328
+ const local = abort, token = navigation;
329
+ busy = true;
330
+ try {
331
+ const loaded = await loadListing(new URL(model.manifestHref, document.baseURI).href, {
332
+ signal: local.signal,
333
+ cache: model.updatesHref ? "no-store" : "default"
334
+ });
335
+ if (token === navigation && !local.signal.aborted) entries = loaded.entries;
336
+ } catch (cause) {
337
+ if (token === navigation && !local.signal.aborted) error = "Could not load complete directory data. Native page links remain available.";
338
+ } finally {
339
+ if (token === navigation) busy = false;
340
+ }
341
+ }
342
+ async function openFolder(dataHref, url, push, refresh = false) {
343
+ const token = ++navigation;
344
+ abort?.abort();
345
+ const local = abort = new AbortController();
346
+ busy = true;
347
+ error = "";
348
+ try {
349
+ const next = await fetchThemePage(dataHref, url, "ledger", local.signal, refresh);
350
+ let nextEntries = next.entries;
351
+ if (refresh && next.manifestHref && nextEntries.length !== next.total) nextEntries = (await loadListing(next.manifestHref, {
352
+ signal: local.signal,
353
+ cache: "no-store"
354
+ })).entries;
355
+ if (token !== navigation || local.signal.aborted) return;
356
+ model = next;
357
+ entries = nextEntries;
358
+ if (!refresh) {
359
+ query = "";
360
+ selected = null;
361
+ scrollTop = 0;
362
+ }
363
+ if (push) history.pushState({ dataHref: next.dataHref }, "", url);
364
+ applyThemeMetadata(next, url);
365
+ await hydrateData();
366
+ if (!refresh) window.scrollTo({ top: 0 });
367
+ } catch (cause) {
368
+ if (token === navigation && !local.signal.aborted) error = "Could not open this folder. Open its native link or try again.";
369
+ } finally {
370
+ if (token === navigation) busy = false;
371
+ }
372
+ }
373
+ function updateScheme(value) {
374
+ scheme = value;
375
+ try {
376
+ localStorage.setItem("dirwell-color-scheme", value);
377
+ } catch {}
378
+ }
379
+ onMount(() => {
380
+ ready = true;
381
+ abort = new AbortController();
382
+ try {
383
+ const stored = localStorage.getItem("dirwell-color-scheme");
384
+ if ([
385
+ "system",
386
+ "light",
387
+ "dark"
388
+ ].includes(stored)) scheme = stored;
389
+ } catch {}
390
+ history.replaceState({ dataHref: new URL(model.dataHref, document.baseURI).href }, "");
391
+ const onPop = (event) => {
392
+ if (event.state?.dataHref) openFolder(event.state.dataHref, location.href, false);
393
+ else location.reload();
394
+ };
395
+ window.addEventListener("popstate", onPop);
396
+ hydrateData();
397
+ return () => {
398
+ abort?.abort();
399
+ window.removeEventListener("popstate", onPop);
400
+ };
401
+ });
402
+ $$renderer.push(`<header class="page-header"><div class="wordmark">`);
403
+ if (header) {
404
+ $$renderer.push("<!--[0-->");
405
+ header($$renderer, { model });
406
+ $$renderer.push(`<!---->`);
407
+ } else $$renderer.push(`<!--[-1--><h1>${$.escape(model.title)}</h1> <span>File archive</span>`);
408
+ $$renderer.push(`<!--]--></div> <nav aria-label="Breadcrumb"><a${$.attr("href", model.rootHref)}>Home</a><!--[-->`);
409
+ const each_array = $.ensure_array_like(model.breadcrumbs);
410
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
411
+ let crumb = each_array[$$index];
412
+ $$renderer.push(`<!---->/ <a${$.attr("href", crumb.href)}>${$.escape(crumb.name)}</a>`);
413
+ }
414
+ $$renderer.push(`<!--]--></nav></header> <main data-explorer="" data-framework="svelte"${$.attr("aria-busy", busy)}><div class="toolbar"><label class="search-field">Search this directory<input type="search" placeholder="Search files"${$.attr("value", query)}${$.attr("disabled", !ready || !complete(), true)}/></label> <fieldset${$.attr("disabled", !ready || !complete(), true)}><legend>Types</legend><!--[-->`);
415
+ const each_array_1 = $.ensure_array_like([
416
+ "directory",
417
+ "file",
418
+ "link"
419
+ ]);
420
+ for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
421
+ let type = each_array_1[$$index_1];
422
+ $$renderer.push(`<label><input type="checkbox"${$.attr("checked", types.includes(type), true)}/>${$.escape(type)}</label>`);
423
+ }
424
+ $$renderer.push(`<!--]--></fieldset> <label>Sort`);
425
+ $$renderer.select({
426
+ value: sort.field,
427
+ disabled: !ready || !complete(),
428
+ onchange: (event) => sort = {
429
+ ...sort,
430
+ field: event.currentTarget.value
431
+ }
432
+ }, ($$renderer) => {
433
+ $$renderer.option({ value: "name" }, ($$renderer) => {
434
+ $$renderer.push(`Name`);
435
+ });
436
+ $$renderer.option({ value: "size" }, ($$renderer) => {
437
+ $$renderer.push(`Size`);
438
+ });
439
+ $$renderer.option({ value: "modified" }, ($$renderer) => {
440
+ $$renderer.push(`Modified`);
441
+ });
442
+ });
443
+ $$renderer.push(`</label> <label>Names`);
444
+ $$renderer.select({
445
+ value: sort.nameMode,
446
+ disabled: !ready || !complete(),
447
+ onchange: (event) => sort = {
448
+ ...sort,
449
+ nameMode: event.currentTarget.value
450
+ }
451
+ }, ($$renderer) => {
452
+ $$renderer.option({ value: "natural" }, ($$renderer) => {
453
+ $$renderer.push(`Natural`);
454
+ });
455
+ $$renderer.option({ value: "locale" }, ($$renderer) => {
456
+ $$renderer.push(`Locale`);
457
+ });
458
+ $$renderer.option({ value: "unicode" }, ($$renderer) => {
459
+ $$renderer.push(`Unicode`);
460
+ });
461
+ });
462
+ $$renderer.push(`</label> <label>Order`);
463
+ $$renderer.select({
464
+ value: sort.direction,
465
+ disabled: !ready || !complete(),
466
+ onchange: (event) => sort = {
467
+ ...sort,
468
+ direction: event.currentTarget.value
469
+ }
470
+ }, ($$renderer) => {
471
+ $$renderer.option({ value: "asc" }, ($$renderer) => {
472
+ $$renderer.push(`Ascending`);
473
+ });
474
+ $$renderer.option({ value: "desc" }, ($$renderer) => {
475
+ $$renderer.push(`Descending`);
476
+ });
477
+ });
478
+ $$renderer.push(`</label> <label><input type="checkbox"${$.attr("checked", sort.directoriesFirst, true)}${$.attr("disabled", !ready || !complete(), true)}/>Folders first</label> <label>Appearance`);
479
+ $$renderer.select({
480
+ value: scheme,
481
+ disabled: !ready,
482
+ onchange: (event) => updateScheme(event.currentTarget.value)
483
+ }, ($$renderer) => {
484
+ $$renderer.option({ value: "system" }, ($$renderer) => {
485
+ $$renderer.push(`System`);
486
+ });
487
+ $$renderer.option({ value: "light" }, ($$renderer) => {
488
+ $$renderer.push(`Light`);
489
+ });
490
+ $$renderer.option({ value: "dark" }, ($$renderer) => {
491
+ $$renderer.push(`Dark`);
492
+ });
493
+ });
494
+ $$renderer.push(`</label> <button${$.attr("disabled", !ready || !model.searchIndexHref, true)}>Search archive</button></div> <section class="listing-card"><div class="listing-summary"><span data-visible-count="">${$.escape(complete() ? rows().length : entries.length)}</span> entries`);
495
+ if (!complete()) $$renderer.push(`<!--[0-->on this page · ${$.escape(model.total)} in directory`);
496
+ else $$renderer.push("<!--[-1-->");
497
+ $$renderer.push(`<!--]--><span role="status" aria-live="polite">${$.escape(busy ? "Loading…" : error)}</span></div> <div class="entry-head"><span>Name</span><span>Size</span><span>Modified</span></div> <ul class="entry-list">`);
498
+ if (model.parentHref) $$renderer.push(`<!--[0--><li class="entry"><a${$.attr("href", model.parentHref)}>../</a></li>`);
499
+ else $$renderer.push("<!--[-1-->");
500
+ $$renderer.push(`<!--]--> `);
501
+ if (start()) $$renderer.push(`<!--[0--><li aria-hidden="true"${$.attr_style("", { height: offsets()[start()] + "px" })}></li>`);
502
+ else $$renderer.push("<!--[-1-->");
503
+ $$renderer.push(`<!--]--> <!--[-->`);
504
+ const each_array_2 = $.ensure_array_like(rows().slice(start(), end()));
505
+ for (let index = 0, $$length = each_array_2.length; index < $$length; index++) {
506
+ let entry = each_array_2[index];
507
+ $$renderer.push(`<li class="entry" data-entry=""${$.attr("data-id", entry.id)}${$.attr("data-order", start() + index)}${$.attr("data-active", selected === entry.id)}><span class="identity">`);
508
+ if (entry.href) {
509
+ $$renderer.push(`<!--[0--><a class="name"${$.attr("href", safeHref(entry))}${$.attr("target", entry.exitsExplorer ? "_blank" : void 0)}${$.attr("rel", entry.exitsExplorer ? "noopener" : void 0)}>`);
510
+ if (row) {
511
+ $$renderer.push("<!--[0-->");
512
+ row($$renderer, {
513
+ model,
514
+ entry,
515
+ selected: selected === entry.id
516
+ });
517
+ $$renderer.push(`<!---->`);
518
+ } else $$renderer.push(`<!--[-1--><span class="file-icon" aria-hidden="true">${$.html(entryIcon(entryType(entry)))}</span> ${$.escape(entry.name)}${$.escape(isDirectory(entry) ? "/" : "")}`);
519
+ $$renderer.push(`<!--]--></a>`);
520
+ } else {
521
+ $$renderer.push(`<!--[-1--><span>`);
522
+ if (row) {
523
+ $$renderer.push("<!--[0-->");
524
+ row($$renderer, {
525
+ model,
526
+ entry,
527
+ selected: selected === entry.id
528
+ });
529
+ $$renderer.push(`<!---->`);
530
+ } else $$renderer.push(`<!--[-1-->${$.escape(entry.name)}`);
531
+ $$renderer.push(`<!--]--></span>`);
532
+ }
533
+ $$renderer.push(`<!--]-->`);
534
+ if (entry.link) $$renderer.push(`<!--[0--><small class="target">${$.escape(entry.link.unavailable ? "Target unavailable · " : "→ ")}${$.escape(entry.link.target)}</small>`);
535
+ else $$renderer.push("<!--[-1-->");
536
+ $$renderer.push(`<!--]--></span> <span class="kind">${$.escape(isDirectory(entry) ? "Folder" : size(entry.size))}</span> <button class="timestamp-button"${$.attr("disabled", !ready, true)}><time${$.attr("datetime", entry.modifiedAt)}>${$.escape(modified(entry.modifiedAt, ready))}</time></button></li>`);
537
+ }
538
+ $$renderer.push(`<!--]--> `);
539
+ if (end() < rows().length) $$renderer.push(`<!--[0--><li aria-hidden="true"${$.attr_style("", { height: offsets()[rows().length] - offsets()[end()] + "px" })}></li>`);
540
+ else $$renderer.push("<!--[-1-->");
541
+ $$renderer.push(`<!--]--></ul> `);
542
+ if (rows().length === 0) $$renderer.push(`<!--[0--><p>No matching entries.</p>`);
543
+ else $$renderer.push("<!--[-1-->");
544
+ $$renderer.push(`<!--]--></section> `);
545
+ if (model.page?.count > 1 && !complete()) {
546
+ $$renderer.push(`<!--[0--><nav aria-label="Pagination">`);
547
+ if (model.page.previousHref) $$renderer.push(`<!--[0--><a rel="prev"${$.attr("href", model.page.previousHref)}>Previous</a>`);
548
+ else $$renderer.push("<!--[-1-->");
549
+ $$renderer.push(`<!--]--> Page ${$.escape(model.page.number)} of ${$.escape(model.page.count)} `);
550
+ if (model.page.nextHref) $$renderer.push(`<!--[0--><a rel="next"${$.attr("href", model.page.nextHref)}>Next</a>`);
551
+ else $$renderer.push("<!--[-1-->");
552
+ $$renderer.push(`<!--]--></nav>`);
553
+ } else $$renderer.push("<!--[-1-->");
554
+ $$renderer.push(`<!--]--> `);
555
+ if (footer) {
556
+ $$renderer.push("<!--[0-->");
557
+ footer($$renderer, { model });
558
+ $$renderer.push(`<!---->`);
559
+ } else $$renderer.push(`<!--[-1--><p class="keyboard-help">Arrow keys select files · Home / End jump · / searches · Backspace opens
560
+ parent</p>`);
561
+ $$renderer.push(`<!--]--></main> `);
562
+ $$renderer.push("<!--[-1-->");
563
+ $$renderer.push(`<!--]--> `);
564
+ $$renderer.push("<!--[-1-->");
565
+ $$renderer.push(`<!--]-->`);
566
+ });
567
+ }
568
+ //#endregion
569
+ //#region render-generated.mjs
570
+ const renderBody = (model) => render(Ledger, { props: { model } }).body;
571
+ //#endregion
572
+ //#region src/index.ts
573
+ function createLedgerTheme(options = {}, renderer) {
574
+ let resources;
575
+ const loadResources = () => resources ??= Promise.all([renderer?.clientScript ?? readFile(new URL("./client.js", import.meta.url), "utf8"), readFile(new URL("./styles.css", import.meta.url), "utf8")]).then(([script, styles]) => ({
576
+ script,
577
+ styles,
578
+ scriptName: `ledger-client-${createHash("sha256").update(script).digest("hex").slice(0, 16)}.js`
579
+ }));
580
+ const theme = {
581
+ name: "ledger",
582
+ listing: { pagination: true },
583
+ entryData: (entry, context) => ({ folderDataHref: folderDataHref(entry, context, "ledger") }),
584
+ async render(context) {
585
+ const model = {
586
+ ...createThemePage(context, theme, options.title ?? "Dirwell"),
587
+ searchIndexHref: context.searchIndexHref
588
+ };
589
+ const { script, scriptName, styles } = await loadResources();
590
+ const json = JSON.stringify(model).replaceAll("<", "\\u003c");
591
+ return {
592
+ html: `<!doctype html><html lang="en" data-theme="system"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">${context.documentBaseHref === null ? "" : `<base href="${escapeHtml(context.documentBaseHref)}">`}${context.metadata?.head ?? `<title>${escapeHtml(model.title)}</title>`}<style>${styles}${renderer?.styles ?? ""}</style></head><body><div data-dirwell-theme="ledger">${await (renderer?.renderBody ?? renderBody)(model)}</div><script type="application/json" id="dirwell-page-data">${json}<\/script><script ${renderer?.clientType === "module" ? "type=\"module\"" : "defer"} src="${escapeHtml(context.assetHref(scriptName))}"><\/script></body></html>`,
593
+ assets: {
594
+ [scriptName]: script,
595
+ [pageAsset("ledger", model.path, model.page?.number ?? 1)]: JSON.stringify(model)
596
+ }
597
+ };
598
+ }
599
+ };
600
+ return theme;
601
+ }
602
+ //#endregion
603
+ export { createLedgerTheme };
604
+
605
+ //# sourceMappingURL=index.mjs.map