@dirwell/theme-ledger 0.0.1-alpha.1 → 0.0.2
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/README.md +34 -2
- package/dist/client.js +27 -28
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +626 -83
- package/dist/index.mjs.map +1 -1
- package/dist/model-BgCmoksr.d.mts +167 -0
- package/dist/model.d.mts +1 -1
- package/dist/source/Ledger.svelte +493 -156
- package/dist/source/component.d.mts +1 -1
- package/dist/source/shared/customization.mjs +255 -0
- package/dist/source/shared/live-search.mjs +28 -0
- package/dist/source/shared/navigation.mjs +197 -2
- package/dist/source/shared/view-state.mjs +128 -0
- package/dist/styles.css +6 -0
- package/package.json +7 -3
- package/dist/model-K7UzS9YB.d.mts +0 -46
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { copyJsonObject, escapeHtml, projectListingEntry } from "@dirwell/core";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { render } from "svelte/server";
|
|
6
6
|
import * as $ from "svelte/internal/server";
|
|
7
|
-
import { onMount, untrack } from "svelte";
|
|
7
|
+
import { onMount, tick, untrack } from "svelte";
|
|
8
8
|
import { ListingDataError, loadListing, parseListingEntries } from "@dirwell/core/client";
|
|
9
9
|
//#region ../shared-theme/src/page.ts
|
|
10
10
|
const pageAsset = (theme, directory, page = 1) => `${theme}-page-${createHash("sha256").update(directory).digest("hex").slice(0, 24)}-${page}.json`;
|
|
@@ -36,7 +36,7 @@ function createThemePage(context, theme, title) {
|
|
|
36
36
|
documentBaseHref: context.documentBaseHref,
|
|
37
37
|
directoryPath
|
|
38
38
|
};
|
|
39
|
-
const dataForDirectory = (logicalPath) => folderDataHref({
|
|
39
|
+
const dataForDirectory = (logicalPath) => context.hasPageForDirectory?.(logicalPath) === false ? null : folderDataHref({
|
|
40
40
|
id: logicalPath,
|
|
41
41
|
name: logicalPath,
|
|
42
42
|
kind: "directory",
|
|
@@ -67,6 +67,10 @@ function createThemePage(context, theme, title) {
|
|
|
67
67
|
manifestHref: context.listing?.manifestHref ?? null,
|
|
68
68
|
...context.listing ? { listingRevision: context.listing.revision } : {},
|
|
69
69
|
updatesHref: context.updatesHref ?? null,
|
|
70
|
+
...context.searchQueryHref ? {
|
|
71
|
+
searchQueryHref: context.searchQueryHref,
|
|
72
|
+
searchRefreshIntervalMs: context.searchRefreshIntervalMs
|
|
73
|
+
} : {},
|
|
70
74
|
total: context.listing?.page.total ?? entries.length,
|
|
71
75
|
entries,
|
|
72
76
|
sort: context.sort,
|
|
@@ -79,6 +83,139 @@ function createThemePage(context, theme, title) {
|
|
|
79
83
|
};
|
|
80
84
|
}
|
|
81
85
|
//#endregion
|
|
86
|
+
//#region ../shared-theme/src/view-state.ts
|
|
87
|
+
const supportedTypes = [
|
|
88
|
+
"directory",
|
|
89
|
+
"file",
|
|
90
|
+
"link"
|
|
91
|
+
];
|
|
92
|
+
function validSort(value) {
|
|
93
|
+
if (typeof value !== "object" || value === null) return false;
|
|
94
|
+
const sort = value;
|
|
95
|
+
return [
|
|
96
|
+
"name",
|
|
97
|
+
"size",
|
|
98
|
+
"modified"
|
|
99
|
+
].includes(String(sort.field)) && ["asc", "desc"].includes(String(sort.direction)) && [
|
|
100
|
+
"natural",
|
|
101
|
+
"locale",
|
|
102
|
+
"unicode"
|
|
103
|
+
].includes(String(sort.nameMode)) && typeof sort.directoriesFirst === "boolean";
|
|
104
|
+
}
|
|
105
|
+
function position(value) {
|
|
106
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(value, 1e9)) : 0;
|
|
107
|
+
}
|
|
108
|
+
/** URL controls are shareable; selection and scroll belong only to one history entry. */
|
|
109
|
+
function readView(href, theme, path, defaults, categories = [], historyState = null) {
|
|
110
|
+
const params = new URL(href).searchParams;
|
|
111
|
+
const result = {
|
|
112
|
+
...defaults,
|
|
113
|
+
query: (params.get("dw.q") ?? "").slice(0, 4096),
|
|
114
|
+
selected: null,
|
|
115
|
+
windowY: 0,
|
|
116
|
+
listTop: 0
|
|
117
|
+
};
|
|
118
|
+
if (theme === "ledger") {
|
|
119
|
+
const parts = params.get("dw.sort")?.split(":");
|
|
120
|
+
if (parts?.length === 4) {
|
|
121
|
+
const sort = {
|
|
122
|
+
field: parts[0],
|
|
123
|
+
direction: parts[1],
|
|
124
|
+
nameMode: parts[2],
|
|
125
|
+
directoriesFirst: parts[3] === "folders"
|
|
126
|
+
};
|
|
127
|
+
if (["folders", "mixed"].includes(parts[3]) && validSort(sort)) result.sort = sort;
|
|
128
|
+
}
|
|
129
|
+
if (params.has("dw.types")) {
|
|
130
|
+
const types = params.get("dw.types").split(",").filter(Boolean);
|
|
131
|
+
if (types.every((type) => supportedTypes.includes(type))) result.types = [...new Set(types)];
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
const category = params.get("dw.category");
|
|
135
|
+
if (category !== null && categories.includes(category)) result.category = category;
|
|
136
|
+
}
|
|
137
|
+
if (typeof historyState === "object" && historyState !== null && "dirwell" in historyState) {
|
|
138
|
+
const state = historyState.dirwell;
|
|
139
|
+
if (typeof state === "object" && state !== null && "version" in state && state.version === 1 && "theme" in state && state.theme === theme && "path" in state && state.path === path && "view" in state && typeof state.view === "object" && state.view !== null) {
|
|
140
|
+
const view = state.view;
|
|
141
|
+
result.selected = typeof view.selected === "string" && view.selected.length <= 4096 ? view.selected : null;
|
|
142
|
+
result.windowY = position(view.windowY);
|
|
143
|
+
result.listTop = position(view.listTop);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
function viewHref(href, theme, view, defaults) {
|
|
149
|
+
const url = new URL(href);
|
|
150
|
+
for (const key of [
|
|
151
|
+
"dw.q",
|
|
152
|
+
"dw.sort",
|
|
153
|
+
"dw.types",
|
|
154
|
+
"dw.category"
|
|
155
|
+
]) url.searchParams.delete(key);
|
|
156
|
+
if (view.query) url.searchParams.set("dw.q", view.query.slice(0, 4096));
|
|
157
|
+
if (theme === "ledger") {
|
|
158
|
+
if (view.sort && [
|
|
159
|
+
"field",
|
|
160
|
+
"direction",
|
|
161
|
+
"nameMode",
|
|
162
|
+
"directoriesFirst"
|
|
163
|
+
].some((key) => view.sort[key] !== defaults.sort?.[key])) {
|
|
164
|
+
const sort = view.sort;
|
|
165
|
+
url.searchParams.set("dw.sort", `${sort.field}:${sort.direction}:${sort.nameMode}:${sort.directoriesFirst ? "folders" : "mixed"}`);
|
|
166
|
+
}
|
|
167
|
+
if (view.types && supportedTypes.some((type) => view.types.includes(type) !== (defaults.types ?? supportedTypes).includes(type))) url.searchParams.set("dw.types", view.types.join(","));
|
|
168
|
+
} else if (view.category && view.category !== defaults.category) url.searchParams.set("dw.category", view.category);
|
|
169
|
+
return url.href;
|
|
170
|
+
}
|
|
171
|
+
/** History failures never require discarding a successfully loaded view. */
|
|
172
|
+
function createViewHistory(theme) {
|
|
173
|
+
let committedHref = location.href;
|
|
174
|
+
let committedState = history.state;
|
|
175
|
+
const stateFor = (dataHref, path, view) => ({
|
|
176
|
+
...typeof history.state === "object" && history.state !== null ? history.state : {},
|
|
177
|
+
dataHref,
|
|
178
|
+
dirwell: {
|
|
179
|
+
version: 1,
|
|
180
|
+
theme,
|
|
181
|
+
path,
|
|
182
|
+
...view === void 0 ? {} : { view: {
|
|
183
|
+
query: view.query,
|
|
184
|
+
...view.sort === void 0 ? {} : { sort: { ...view.sort } },
|
|
185
|
+
...view.types === void 0 ? {} : { types: [...view.types] },
|
|
186
|
+
category: view.category ?? null,
|
|
187
|
+
selected: view.selected ?? null,
|
|
188
|
+
windowY: view.windowY ?? 0,
|
|
189
|
+
listTop: view.listTop ?? 0
|
|
190
|
+
} }
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
save(dataHref, path, view, defaults) {
|
|
195
|
+
if (location.href !== committedHref) return;
|
|
196
|
+
const href = viewHref(committedHref, theme, view, defaults);
|
|
197
|
+
const state = stateFor(dataHref, path, view);
|
|
198
|
+
try {
|
|
199
|
+
history.replaceState(state, "", href);
|
|
200
|
+
committedHref = href;
|
|
201
|
+
committedState = state;
|
|
202
|
+
} catch {}
|
|
203
|
+
},
|
|
204
|
+
commit(dataHref, path, href, push) {
|
|
205
|
+
const state = stateFor(dataHref, path);
|
|
206
|
+
if (push) history.pushState(state, "", href);
|
|
207
|
+
else history.replaceState(state, "", href);
|
|
208
|
+
committedHref = href;
|
|
209
|
+
committedState = state;
|
|
210
|
+
},
|
|
211
|
+
rollback() {
|
|
212
|
+
if (location.href !== committedHref) try {
|
|
213
|
+
history.replaceState(committedState, "", committedHref);
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
82
219
|
//#region ../shared-theme/src/icons.ts
|
|
83
220
|
const paths = {
|
|
84
221
|
directory: "<path d=\"M3 7V5h6l2 3h10v12H3Z\"/><path d=\"M3 10h18\"/>",
|
|
@@ -90,6 +227,250 @@ function entryIcon(kind) {
|
|
|
90
227
|
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
228
|
}
|
|
92
229
|
//#endregion
|
|
230
|
+
//#region ../shared-theme/src/customization.ts
|
|
231
|
+
/** Small serializable inputs shared by official themes; layout stays theme-owned. */
|
|
232
|
+
const defaultLabels = {
|
|
233
|
+
other: "other",
|
|
234
|
+
crosswaveKeyboardHelp: "Arrow keys navigate · Enter confirms · Backspace returns",
|
|
235
|
+
brokenLink: "broken link",
|
|
236
|
+
unavailableLink: "unavailable link",
|
|
237
|
+
cycleLink: "cycle link",
|
|
238
|
+
azure: "azure",
|
|
239
|
+
violet: "violet",
|
|
240
|
+
amber: "amber",
|
|
241
|
+
rose: "rose",
|
|
242
|
+
jade: "jade",
|
|
243
|
+
graphite: "graphite",
|
|
244
|
+
allFiles: "All files",
|
|
245
|
+
categoryFolders: "Folders",
|
|
246
|
+
categoryPhotos: "Photos",
|
|
247
|
+
categoryMusic: "Music",
|
|
248
|
+
categoryVideos: "Videos",
|
|
249
|
+
categoryDocuments: "Documents",
|
|
250
|
+
categoryOther: "Other",
|
|
251
|
+
indexOf: "Index of {path}",
|
|
252
|
+
home: "Home",
|
|
253
|
+
breadcrumb: "Breadcrumb",
|
|
254
|
+
archive: "File archive",
|
|
255
|
+
searchDirectory: "Search this directory",
|
|
256
|
+
searchFiles: "Search files",
|
|
257
|
+
search: "Search",
|
|
258
|
+
types: "Types",
|
|
259
|
+
directory: "directory",
|
|
260
|
+
file: "file",
|
|
261
|
+
link: "link",
|
|
262
|
+
folder: "Folder",
|
|
263
|
+
sort: "Sort",
|
|
264
|
+
name: "Name",
|
|
265
|
+
size: "Size",
|
|
266
|
+
modified: "Modified",
|
|
267
|
+
names: "Names",
|
|
268
|
+
natural: "Natural",
|
|
269
|
+
locale: "Locale",
|
|
270
|
+
unicode: "Unicode",
|
|
271
|
+
order: "Order",
|
|
272
|
+
ascending: "Ascending",
|
|
273
|
+
descending: "Descending",
|
|
274
|
+
directoriesFirst: "Folders first",
|
|
275
|
+
appearance: "Appearance",
|
|
276
|
+
system: "System",
|
|
277
|
+
light: "Light",
|
|
278
|
+
dark: "Dark",
|
|
279
|
+
searchArchive: "Search archive",
|
|
280
|
+
archiveSearchInput: "Search archive files",
|
|
281
|
+
entries: "{count} entries",
|
|
282
|
+
pageEntries: "{total} entries · {count} on this page",
|
|
283
|
+
loading: "Loading…",
|
|
284
|
+
noMatches: "No matching entries.",
|
|
285
|
+
empty: "This directory is empty.",
|
|
286
|
+
pagination: "Pagination",
|
|
287
|
+
previous: "Previous",
|
|
288
|
+
next: "Next",
|
|
289
|
+
page: "Page {page} of {pages}",
|
|
290
|
+
keyboardHelp: "Arrow keys select files · Home / End jump · / searches · Backspace opens parent",
|
|
291
|
+
modifiedTime: "Modified time",
|
|
292
|
+
close: "Close",
|
|
293
|
+
unknown: "Unknown",
|
|
294
|
+
unavailable: "Target unavailable",
|
|
295
|
+
beginArchive: "Enter a search to begin.",
|
|
296
|
+
searchingArchive: "Searching archive…",
|
|
297
|
+
archiveResults: "{count} results",
|
|
298
|
+
truncatedResults: "Showing {shown} of {count} results",
|
|
299
|
+
archiveError: "Could not load archive search. Try again.",
|
|
300
|
+
listingError: "Could not load complete directory data. Native page links remain available.",
|
|
301
|
+
folderError: "Could not open the folder. Its native link remains available.",
|
|
302
|
+
updatesError: "Live updates disconnected. Refresh to retry.",
|
|
303
|
+
retry: "Retry",
|
|
304
|
+
openNative: "Open folder directly",
|
|
305
|
+
categories: "File categories",
|
|
306
|
+
items: "{count} items",
|
|
307
|
+
filesIn: "Files in {path}",
|
|
308
|
+
controller: "Controller · Confirm / Back",
|
|
309
|
+
color: "Color",
|
|
310
|
+
pauseMotion: "Pause motion",
|
|
311
|
+
resumeMotion: "Resume motion",
|
|
312
|
+
copyLink: "Copy link",
|
|
313
|
+
copyPath: "Copy path",
|
|
314
|
+
download: "Download",
|
|
315
|
+
copied: "Copied.",
|
|
316
|
+
copyError: "Could not copy. Use the native link or select the path.",
|
|
317
|
+
repository: "Repository",
|
|
318
|
+
author: "by",
|
|
319
|
+
target: "Target",
|
|
320
|
+
newTab: "opens in a new tab"
|
|
321
|
+
};
|
|
322
|
+
function resolveCustomization(options = {}) {
|
|
323
|
+
const language = options.language === void 0 ? "en" : options.language, locale = options.locale === void 0 ? "en-US" : options.locale, timeZone = options.timeZone === void 0 ? "UTC" : options.timeZone;
|
|
324
|
+
for (const [key, value] of Object.entries({
|
|
325
|
+
language,
|
|
326
|
+
locale,
|
|
327
|
+
timeZone
|
|
328
|
+
})) if (typeof value !== "string" || !value) throw new TypeError(`${key} must be a non-empty string`);
|
|
329
|
+
new Intl.DateTimeFormat(locale, { timeZone });
|
|
330
|
+
const direction = options.direction ?? "ltr";
|
|
331
|
+
if (direction !== "ltr" && direction !== "rtl") throw new TypeError("direction must be ltr or rtl");
|
|
332
|
+
const labels = { ...defaultLabels };
|
|
333
|
+
if (options.labels !== void 0) {
|
|
334
|
+
if (typeof options.labels !== "object" || options.labels === null || Array.isArray(options.labels)) throw new TypeError("labels must be an object");
|
|
335
|
+
for (const [key, value] of Object.entries(options.labels)) {
|
|
336
|
+
if (!Object.hasOwn(labels, key) || typeof value !== "string") throw new TypeError(`Unsupported label or value: ${key}`);
|
|
337
|
+
Object.assign(labels, { [key]: value });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const brand = options.brand ?? {};
|
|
341
|
+
if (typeof brand !== "object" || brand === null || Array.isArray(brand)) throw new TypeError("brand must be an object");
|
|
342
|
+
for (const [key, value] of Object.entries(brand)) {
|
|
343
|
+
if (![
|
|
344
|
+
"name",
|
|
345
|
+
"logoUrl",
|
|
346
|
+
"logoAlt",
|
|
347
|
+
"footerText",
|
|
348
|
+
"footerUrl",
|
|
349
|
+
"faviconUrl"
|
|
350
|
+
].includes(key) || typeof value !== "string") throw new TypeError(`Unsupported brand option or value: ${key}`);
|
|
351
|
+
if (key.endsWith("Url") && !/^https?:\/\//i.test(value) && !(value.startsWith("/") && !value.startsWith("//"))) throw new TypeError(`brand.${key} must be an HTTP(S) or root-relative URL`);
|
|
352
|
+
}
|
|
353
|
+
const columns = options.columns ?? [
|
|
354
|
+
"name",
|
|
355
|
+
"size",
|
|
356
|
+
"modified"
|
|
357
|
+
];
|
|
358
|
+
if (!Array.isArray(columns) || !columns.includes("name")) throw new TypeError("columns must contain name and unique supported columns");
|
|
359
|
+
const columnIds = /* @__PURE__ */ new Set();
|
|
360
|
+
for (const column of columns) {
|
|
361
|
+
let id;
|
|
362
|
+
if (typeof column === "string") {
|
|
363
|
+
if (![
|
|
364
|
+
"name",
|
|
365
|
+
"size",
|
|
366
|
+
"modified"
|
|
367
|
+
].includes(column)) throw new TypeError("Unsupported column");
|
|
368
|
+
id = column;
|
|
369
|
+
} else {
|
|
370
|
+
if (typeof column !== "object" || column === null || Object.keys(column).some((key) => !["dataKey", "label"].includes(key)) || typeof column.dataKey !== "string" || !column.dataKey || typeof column.label !== "string" || !column.label) throw new TypeError("Data columns require dataKey and label");
|
|
371
|
+
id = "data:" + column.dataKey;
|
|
372
|
+
}
|
|
373
|
+
if (columnIds.has(id)) throw new TypeError("columns must contain unique supported columns");
|
|
374
|
+
columnIds.add(id);
|
|
375
|
+
}
|
|
376
|
+
const actions = options.actions ?? [];
|
|
377
|
+
if (!Array.isArray(actions) || new Set(actions).size !== actions.length || actions.some((key) => ![
|
|
378
|
+
"copy-link",
|
|
379
|
+
"copy-path",
|
|
380
|
+
"download"
|
|
381
|
+
].includes(key))) throw new TypeError("actions must contain unique supported actions");
|
|
382
|
+
const cssVariables = options.cssVariables ?? {};
|
|
383
|
+
if (typeof cssVariables !== "object" || cssVariables === null || Array.isArray(cssVariables)) throw new TypeError("cssVariables must be an object");
|
|
384
|
+
for (const [key, value] of Object.entries(cssVariables)) if (!/^--[a-zA-Z][a-zA-Z0-9_-]*$/.test(key) || typeof value !== "string" || /[{};<>]/.test(value)) throw new TypeError(`Invalid CSS variable: ${key}`);
|
|
385
|
+
return {
|
|
386
|
+
language,
|
|
387
|
+
dateOptions: {
|
|
388
|
+
...options.locale === void 0 ? {} : { locale: options.locale },
|
|
389
|
+
...options.timeZone === void 0 ? {} : { timeZone: options.timeZone }
|
|
390
|
+
},
|
|
391
|
+
direction,
|
|
392
|
+
locale,
|
|
393
|
+
timeZone,
|
|
394
|
+
labels,
|
|
395
|
+
brand: { ...brand },
|
|
396
|
+
columns: columns.map((column) => typeof column === "string" ? column : { ...column }),
|
|
397
|
+
actions: [...actions],
|
|
398
|
+
cssVariables: { ...cssVariables }
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
resolveCustomization();
|
|
402
|
+
function label(ui, key, values = {}) {
|
|
403
|
+
return ui.labels[key].replace(/\{([a-zA-Z]+)\}/g, (match, name) => Object.hasOwn(values, name) ? String(values[name]) : match);
|
|
404
|
+
}
|
|
405
|
+
const dateFormats = /* @__PURE__ */ new Map();
|
|
406
|
+
function formattedDate(value, ui, local = false) {
|
|
407
|
+
const date = new Date(value);
|
|
408
|
+
if (Number.isNaN(date.getTime())) return ui.labels.unknown;
|
|
409
|
+
if (ui.dateOptions.locale === void 0 && ui.dateOptions.timeZone === void 0) return local ? date.toLocaleString(void 0, {
|
|
410
|
+
dateStyle: "short",
|
|
411
|
+
timeStyle: "short"
|
|
412
|
+
}) : date.toISOString().slice(0, 16).replace("T", " ") + " UTC";
|
|
413
|
+
const locale = ui.dateOptions.locale, timeZone = ui.dateOptions.timeZone ?? (local ? void 0 : "UTC");
|
|
414
|
+
const key = String(locale) + "\0" + String(timeZone);
|
|
415
|
+
let formatter = dateFormats.get(key);
|
|
416
|
+
if (formatter === void 0) {
|
|
417
|
+
formatter = new Intl.DateTimeFormat(locale, {
|
|
418
|
+
timeZone,
|
|
419
|
+
dateStyle: "short",
|
|
420
|
+
timeStyle: "short"
|
|
421
|
+
});
|
|
422
|
+
if (dateFormats.size >= 32) dateFormats.delete(dateFormats.keys().next().value);
|
|
423
|
+
dateFormats.set(key, formatter);
|
|
424
|
+
}
|
|
425
|
+
return formatter.format(date);
|
|
426
|
+
}
|
|
427
|
+
function customizationCss(ui) {
|
|
428
|
+
const declarations = Object.entries(ui.cssVariables).map(([key, value]) => `${key}:${value}`).join(";");
|
|
429
|
+
return declarations ? `:root:root{${declarations}}${ui.cssVariables["--dw-font-family"] ? "body{font-family:var(--dw-font-family)}" : ""}` : "";
|
|
430
|
+
}
|
|
431
|
+
/** Publish only supplied customization instead of repeating defaults in every page. */
|
|
432
|
+
function pickCustomization(options) {
|
|
433
|
+
const result = {};
|
|
434
|
+
for (const key of [
|
|
435
|
+
"language",
|
|
436
|
+
"direction",
|
|
437
|
+
"locale",
|
|
438
|
+
"timeZone",
|
|
439
|
+
"labels",
|
|
440
|
+
"brand",
|
|
441
|
+
"columns",
|
|
442
|
+
"actions",
|
|
443
|
+
"cssVariables"
|
|
444
|
+
]) if (options[key] !== void 0) {
|
|
445
|
+
const value = options[key];
|
|
446
|
+
Object.assign(result, { [key]: Array.isArray(value) ? value.map((item) => typeof item === "object" ? { ...item } : item) : typeof value === "object" && value !== null ? { ...value } : value });
|
|
447
|
+
}
|
|
448
|
+
return Object.keys(result).length ? result : void 0;
|
|
449
|
+
}
|
|
450
|
+
/** Built-in factories fail on typos instead of silently ignoring configuration. */
|
|
451
|
+
function assertThemeOptions(value, extraKeys) {
|
|
452
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Theme options must be an object");
|
|
453
|
+
const allowed = [
|
|
454
|
+
"language",
|
|
455
|
+
"direction",
|
|
456
|
+
"locale",
|
|
457
|
+
"timeZone",
|
|
458
|
+
"labels",
|
|
459
|
+
"brand",
|
|
460
|
+
"columns",
|
|
461
|
+
"actions",
|
|
462
|
+
"cssVariables",
|
|
463
|
+
...extraKeys
|
|
464
|
+
];
|
|
465
|
+
for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new TypeError(`Unsupported theme option: ${key}`);
|
|
466
|
+
}
|
|
467
|
+
function columnLabel(column, ui) {
|
|
468
|
+
return typeof column === "string" ? ui.labels[column] : column.label;
|
|
469
|
+
}
|
|
470
|
+
function publicDataValue(value) {
|
|
471
|
+
return value === void 0 || value === null ? "" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
472
|
+
}
|
|
473
|
+
//#endregion
|
|
93
474
|
//#region ../shared-theme/src/navigation.ts
|
|
94
475
|
/** Folder snapshots resolve relative URLs before History changes the document URL. */
|
|
95
476
|
async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false, attempt = 0) {
|
|
@@ -105,6 +486,25 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
105
486
|
const data = value;
|
|
106
487
|
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
488
|
if (data.metadataHead !== void 0 && typeof data.metadataHead !== "string") throw new ListingDataError("Invalid folder metadata");
|
|
489
|
+
const sort = data.sort;
|
|
490
|
+
if (!sort || typeof sort !== "object" || ![
|
|
491
|
+
"name",
|
|
492
|
+
"size",
|
|
493
|
+
"modified"
|
|
494
|
+
].includes(String(sort.field)) || !["asc", "desc"].includes(String(sort.direction)) || ![
|
|
495
|
+
"natural",
|
|
496
|
+
"locale",
|
|
497
|
+
"unicode"
|
|
498
|
+
].includes(String(sort.nameMode)) || typeof sort.directoriesFirst !== "boolean") throw new ListingDataError("Invalid folder order");
|
|
499
|
+
if (theme === "crosswave") {
|
|
500
|
+
const categories = data.categories;
|
|
501
|
+
const ids = /* @__PURE__ */ new Set();
|
|
502
|
+
if (!Array.isArray(categories) || categories.length === 0 || categories.some((category) => {
|
|
503
|
+
if (typeof category !== "object" || category === null || typeof category.id !== "string" || !category.id || ids.has(category.id) || typeof category.label !== "string" || typeof category.svg !== "string") return true;
|
|
504
|
+
ids.add(category.id);
|
|
505
|
+
return false;
|
|
506
|
+
})) throw new ListingDataError("Invalid folder categories");
|
|
507
|
+
}
|
|
108
508
|
const base = document.querySelector("base")?.href ?? pageHref;
|
|
109
509
|
const link = (raw) => {
|
|
110
510
|
if (raw === null) return null;
|
|
@@ -121,8 +521,20 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
121
521
|
} } : {}
|
|
122
522
|
}));
|
|
123
523
|
if (entries.length > Number(data.total)) throw new ListingDataError("Invalid folder count");
|
|
524
|
+
if (data.page !== null) {
|
|
525
|
+
const page = data.page;
|
|
526
|
+
if (!page || typeof page !== "object" || !Number.isSafeInteger(page.number) || Number(page.number) < 1 || !Number.isSafeInteger(page.count) || Number(page.count) < Number(page.number) || !Number.isSafeInteger(page.offset) || Number(page.offset) < 0 || Number(page.offset) > Number(data.total) || page.total !== data.total) throw new ListingDataError("Invalid folder pagination");
|
|
527
|
+
data.page = {
|
|
528
|
+
...page,
|
|
529
|
+
href: link(page.href),
|
|
530
|
+
previousHref: link(page.previousHref),
|
|
531
|
+
nextHref: link(page.nextHref)
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
if (data.ui !== void 0) resolveCustomization(data.ui);
|
|
124
535
|
const result = {
|
|
125
536
|
...data,
|
|
537
|
+
searchQueryHref: link(data.searchQueryHref ?? null),
|
|
126
538
|
updatesHref: link(data.updatesHref ?? null),
|
|
127
539
|
entries,
|
|
128
540
|
dataHref: source.href,
|
|
@@ -140,10 +552,10 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
140
552
|
};
|
|
141
553
|
})
|
|
142
554
|
};
|
|
143
|
-
if (
|
|
555
|
+
if (result.manifestHref) {
|
|
144
556
|
const loaded = await loadListing(result.manifestHref, {
|
|
145
557
|
signal,
|
|
146
|
-
cache: "no-store"
|
|
558
|
+
cache: fresh ? "no-store" : "default"
|
|
147
559
|
});
|
|
148
560
|
if (loaded.manifest.total !== data.total || data.listingRevision !== void 0 && loaded.manifest.revision !== data.listingRevision) {
|
|
149
561
|
if (attempt < 2) return fetchThemePage(dataHref, pageHref, theme, signal, true, attempt + 1);
|
|
@@ -266,19 +678,11 @@ function size(bytes) {
|
|
|
266
678
|
}
|
|
267
679
|
return `${value.toFixed(index === 0 || value >= 100 ? 0 : 1)} ${units[index]}`;
|
|
268
680
|
}
|
|
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
681
|
//#endregion
|
|
278
682
|
//#region .generated/App-server.mjs
|
|
279
683
|
function Ledger($$renderer, $$props) {
|
|
280
684
|
$$renderer.component(($$renderer) => {
|
|
281
|
-
let { model: initialModel, header, row, footer } = $$props;
|
|
685
|
+
let { model: initialModel, header, row, actions, footer } = $$props;
|
|
282
686
|
const snapshot = untrack(() => restoreThemePage(initialModel, Boolean(import.meta.hot)));
|
|
283
687
|
let model = snapshot;
|
|
284
688
|
let entries = snapshot.entries;
|
|
@@ -294,7 +698,15 @@ function Ledger($$renderer, $$props) {
|
|
|
294
698
|
let ready = false;
|
|
295
699
|
let busy = false;
|
|
296
700
|
let error = "";
|
|
701
|
+
let failure = null;
|
|
297
702
|
let scrollTop = 0;
|
|
703
|
+
let viewportHeight = 800;
|
|
704
|
+
resolveCustomization(snapshot.ui).labels.beginArchive;
|
|
705
|
+
let ui = $.derived(() => resolveCustomization(model.ui));
|
|
706
|
+
let countParts = $.derived(() => label(ui(), complete() ? "entries" : "pageEntries", {
|
|
707
|
+
count: "__COUNT__",
|
|
708
|
+
total: model.total
|
|
709
|
+
}).split("__COUNT__"));
|
|
298
710
|
let heights = /* @__PURE__ */ new Map();
|
|
299
711
|
$.derived(() => ready && model.updatesHref ? new URL(model.updatesHref, document.baseURI).href : null);
|
|
300
712
|
let complete = $.derived(() => entries.length === model.total);
|
|
@@ -315,9 +727,40 @@ function Ledger($$renderer, $$props) {
|
|
|
315
727
|
}
|
|
316
728
|
return Math.max(0, low - 5);
|
|
317
729
|
});
|
|
318
|
-
let end = $.derived(() =>
|
|
730
|
+
let end = $.derived(() => {
|
|
731
|
+
if (!virtual()) return rows().length;
|
|
732
|
+
let low = 0, high = rows().length;
|
|
733
|
+
while (low < high) {
|
|
734
|
+
const middle = low + high >> 1;
|
|
735
|
+
if (offsets()[middle + 1] <= scrollTop + viewportHeight) low = middle + 1;
|
|
736
|
+
else high = middle;
|
|
737
|
+
}
|
|
738
|
+
return Math.min(rows().length, Math.max(start() + 35, low + 5));
|
|
739
|
+
});
|
|
319
740
|
let navigation = 0;
|
|
320
741
|
let abort;
|
|
742
|
+
let viewHistory;
|
|
743
|
+
let retryRequest = null;
|
|
744
|
+
const viewDefaults = (page) => ({
|
|
745
|
+
query: "",
|
|
746
|
+
sort: page.sort,
|
|
747
|
+
types: [
|
|
748
|
+
"directory",
|
|
749
|
+
"file",
|
|
750
|
+
"link"
|
|
751
|
+
]
|
|
752
|
+
});
|
|
753
|
+
function saveView() {
|
|
754
|
+
if (!viewHistory) return;
|
|
755
|
+
viewHistory.save(new URL(model.dataHref, document.baseURI).href, model.path, {
|
|
756
|
+
query,
|
|
757
|
+
types,
|
|
758
|
+
sort,
|
|
759
|
+
selected,
|
|
760
|
+
windowY: window.scrollY,
|
|
761
|
+
listTop: scrollTop
|
|
762
|
+
}, viewDefaults(model));
|
|
763
|
+
}
|
|
321
764
|
const isDirectory = directoryLike;
|
|
322
765
|
function safeHref(record) {
|
|
323
766
|
if (typeof location !== "undefined" && location.protocol === "file:" && isDirectory(record) && !record.exitsExplorer && record.href?.endsWith("/")) return record.href + "index.html";
|
|
@@ -334,38 +777,64 @@ function Ledger($$renderer, $$props) {
|
|
|
334
777
|
});
|
|
335
778
|
if (token === navigation && !local.signal.aborted) entries = loaded.entries;
|
|
336
779
|
} catch (cause) {
|
|
337
|
-
if (token === navigation && !local.signal.aborted)
|
|
780
|
+
if (token === navigation && !local.signal.aborted) {
|
|
781
|
+
error = ui().labels.listingError;
|
|
782
|
+
failure = "listing";
|
|
783
|
+
}
|
|
338
784
|
} finally {
|
|
339
785
|
if (token === navigation) busy = false;
|
|
340
786
|
}
|
|
341
787
|
}
|
|
342
|
-
async function openFolder(dataHref, url, push, refresh = false) {
|
|
788
|
+
async function openFolder(dataHref, url, push, refresh = false, restoreState = null, fresh = false) {
|
|
789
|
+
if (push) saveView();
|
|
343
790
|
const token = ++navigation;
|
|
344
791
|
abort?.abort();
|
|
345
792
|
const local = abort = new AbortController();
|
|
346
793
|
busy = true;
|
|
347
794
|
error = "";
|
|
795
|
+
failure = null;
|
|
348
796
|
try {
|
|
349
|
-
const next = await fetchThemePage(dataHref, url, "ledger", local.signal, refresh);
|
|
797
|
+
const next = await fetchThemePage(dataHref, url, "ledger", local.signal, refresh || fresh);
|
|
350
798
|
let nextEntries = next.entries;
|
|
351
799
|
if (refresh && next.manifestHref && nextEntries.length !== next.total) nextEntries = (await loadListing(next.manifestHref, {
|
|
352
800
|
signal: local.signal,
|
|
353
801
|
cache: "no-store"
|
|
354
802
|
})).entries;
|
|
355
803
|
if (token !== navigation || local.signal.aborted) return;
|
|
804
|
+
if (!refresh) viewHistory.commit(next.dataHref, next.path, url, push);
|
|
356
805
|
model = next;
|
|
357
806
|
entries = nextEntries;
|
|
807
|
+
retryRequest = null;
|
|
358
808
|
if (!refresh) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
809
|
+
const view = readView(url, "ledger", next.path, viewDefaults(next), [], restoreState);
|
|
810
|
+
query = view.query;
|
|
811
|
+
types = view.types;
|
|
812
|
+
sort = view.sort;
|
|
813
|
+
selected = view.selected;
|
|
814
|
+
scrollTop = view.listTop;
|
|
362
815
|
}
|
|
363
|
-
if (push) history.pushState({ dataHref: next.dataHref }, "", url);
|
|
364
816
|
applyThemeMetadata(next, url);
|
|
365
817
|
await hydrateData();
|
|
366
|
-
if (!refresh)
|
|
818
|
+
if (!refresh) {
|
|
819
|
+
const view = readView(url, "ledger", next.path, viewDefaults(next), [], restoreState);
|
|
820
|
+
await tick();
|
|
821
|
+
if (selected && !rows().some((entry) => entry.id === selected)) selected = null;
|
|
822
|
+
window.scrollTo({ top: view.windowY });
|
|
823
|
+
if (restoreState && selected);
|
|
824
|
+
}
|
|
367
825
|
} catch (cause) {
|
|
368
|
-
if (token === navigation && !local.signal.aborted)
|
|
826
|
+
if (token === navigation && !local.signal.aborted) {
|
|
827
|
+
error = ui().labels.folderError;
|
|
828
|
+
failure = "folder";
|
|
829
|
+
retryRequest = [
|
|
830
|
+
dataHref,
|
|
831
|
+
url,
|
|
832
|
+
push,
|
|
833
|
+
refresh,
|
|
834
|
+
restoreState
|
|
835
|
+
];
|
|
836
|
+
viewHistory?.rollback();
|
|
837
|
+
}
|
|
369
838
|
} finally {
|
|
370
839
|
if (token === navigation) busy = false;
|
|
371
840
|
}
|
|
@@ -377,6 +846,7 @@ function Ledger($$renderer, $$props) {
|
|
|
377
846
|
} catch {}
|
|
378
847
|
}
|
|
379
848
|
onMount(() => {
|
|
849
|
+
viewportHeight = innerHeight;
|
|
380
850
|
ready = true;
|
|
381
851
|
abort = new AbortController();
|
|
382
852
|
try {
|
|
@@ -387,9 +857,16 @@ function Ledger($$renderer, $$props) {
|
|
|
387
857
|
"dark"
|
|
388
858
|
].includes(stored)) scheme = stored;
|
|
389
859
|
} catch {}
|
|
390
|
-
|
|
860
|
+
viewHistory = createViewHistory("ledger");
|
|
861
|
+
const view = readView(location.href, "ledger", model.path, viewDefaults(model), [], history.state);
|
|
862
|
+
query = view.query;
|
|
863
|
+
types = view.types;
|
|
864
|
+
sort = view.sort;
|
|
865
|
+
selected = view.selected;
|
|
866
|
+
scrollTop = view.listTop;
|
|
867
|
+
saveView();
|
|
391
868
|
const onPop = (event) => {
|
|
392
|
-
if (event.state?.dataHref) openFolder(event.state.dataHref, location.href, false);
|
|
869
|
+
if (event.state?.dataHref) openFolder(event.state.dataHref, location.href, false, false, event.state);
|
|
393
870
|
else location.reload();
|
|
394
871
|
};
|
|
395
872
|
window.addEventListener("popstate", onPop);
|
|
@@ -404,14 +881,19 @@ function Ledger($$renderer, $$props) {
|
|
|
404
881
|
$$renderer.push("<!--[0-->");
|
|
405
882
|
header($$renderer, { model });
|
|
406
883
|
$$renderer.push(`<!---->`);
|
|
407
|
-
} else
|
|
408
|
-
|
|
884
|
+
} else {
|
|
885
|
+
$$renderer.push("<!--[-1-->");
|
|
886
|
+
if (ui().brand.logoUrl) $$renderer.push(`<!--[0--><img${$.attr("src", ui().brand.logoUrl)}${$.attr("alt", ui().brand.logoAlt ?? "")} width="40" height="40"/>`);
|
|
887
|
+
else $$renderer.push("<!--[-1-->");
|
|
888
|
+
$$renderer.push(`<!--]--> <h1>${$.escape(ui().brand.name ?? model.title)}</h1> <span>${$.escape(ui().labels.archive)}</span>`);
|
|
889
|
+
}
|
|
890
|
+
$$renderer.push(`<!--]--></div> <nav${$.attr("aria-label", ui().labels.breadcrumb)}><a${$.attr("href", model.rootHref)}>${$.escape(ui().labels.home)}</a><!--[-->`);
|
|
409
891
|
const each_array = $.ensure_array_like(model.breadcrumbs);
|
|
410
892
|
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
411
893
|
let crumb = each_array[$$index];
|
|
412
894
|
$$renderer.push(`<!---->/ <a${$.attr("href", crumb.href)}>${$.escape(crumb.name)}</a>`);
|
|
413
895
|
}
|
|
414
|
-
$$renderer.push(`<!--]--></nav></header> <main data-explorer="" data-framework="svelte"${$.attr("aria-busy", busy)}><div class="toolbar"><label class="search-field"
|
|
896
|
+
$$renderer.push(`<!--]--></nav></header> <main data-explorer="" data-framework="svelte"${$.attr("aria-busy", busy)}><div class="toolbar"><label class="search-field">${$.escape(ui().labels.searchDirectory)}<input type="search"${$.attr("placeholder", ui().labels.searchFiles)}${$.attr("value", query)}${$.attr("disabled", !ready || !complete(), true)}/></label> <fieldset${$.attr("disabled", !ready || !complete(), true)}><legend>${$.escape(ui().labels.types)}</legend><!--[-->`);
|
|
415
897
|
const each_array_1 = $.ensure_array_like([
|
|
416
898
|
"directory",
|
|
417
899
|
"file",
|
|
@@ -419,9 +901,9 @@ function Ledger($$renderer, $$props) {
|
|
|
419
901
|
]);
|
|
420
902
|
for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
|
|
421
903
|
let type = each_array_1[$$index_1];
|
|
422
|
-
$$renderer.push(`<label><input type="checkbox"${$.attr("checked", types.includes(type), true)}/>${$.escape(type)}</label>`);
|
|
904
|
+
$$renderer.push(`<label><input type="checkbox"${$.attr("checked", types.includes(type), true)}/>${$.escape(ui().labels[type])}</label>`);
|
|
423
905
|
}
|
|
424
|
-
$$renderer.push(`<!--]--></fieldset> <label
|
|
906
|
+
$$renderer.push(`<!--]--></fieldset> <label>${$.escape(ui().labels.sort)}`);
|
|
425
907
|
$$renderer.select({
|
|
426
908
|
value: sort.field,
|
|
427
909
|
disabled: !ready || !complete(),
|
|
@@ -431,16 +913,16 @@ function Ledger($$renderer, $$props) {
|
|
|
431
913
|
}
|
|
432
914
|
}, ($$renderer) => {
|
|
433
915
|
$$renderer.option({ value: "name" }, ($$renderer) => {
|
|
434
|
-
$$renderer.push(`
|
|
916
|
+
$$renderer.push(`${$.escape(ui().labels.name)}`);
|
|
435
917
|
});
|
|
436
918
|
$$renderer.option({ value: "size" }, ($$renderer) => {
|
|
437
|
-
$$renderer.push(`
|
|
919
|
+
$$renderer.push(`${$.escape(ui().labels.size)}`);
|
|
438
920
|
});
|
|
439
921
|
$$renderer.option({ value: "modified" }, ($$renderer) => {
|
|
440
|
-
$$renderer.push(`
|
|
922
|
+
$$renderer.push(`${$.escape(ui().labels.modified)}`);
|
|
441
923
|
});
|
|
442
924
|
});
|
|
443
|
-
$$renderer.push(`</label> <label
|
|
925
|
+
$$renderer.push(`</label> <label>${$.escape(ui().labels.names)}`);
|
|
444
926
|
$$renderer.select({
|
|
445
927
|
value: sort.nameMode,
|
|
446
928
|
disabled: !ready || !complete(),
|
|
@@ -450,16 +932,16 @@ function Ledger($$renderer, $$props) {
|
|
|
450
932
|
}
|
|
451
933
|
}, ($$renderer) => {
|
|
452
934
|
$$renderer.option({ value: "natural" }, ($$renderer) => {
|
|
453
|
-
$$renderer.push(`
|
|
935
|
+
$$renderer.push(`${$.escape(ui().labels.natural)}`);
|
|
454
936
|
});
|
|
455
937
|
$$renderer.option({ value: "locale" }, ($$renderer) => {
|
|
456
|
-
$$renderer.push(`
|
|
938
|
+
$$renderer.push(`${$.escape(ui().labels.locale)}`);
|
|
457
939
|
});
|
|
458
940
|
$$renderer.option({ value: "unicode" }, ($$renderer) => {
|
|
459
|
-
$$renderer.push(`
|
|
941
|
+
$$renderer.push(`${$.escape(ui().labels.unicode)}`);
|
|
460
942
|
});
|
|
461
943
|
});
|
|
462
|
-
$$renderer.push(`</label> <label
|
|
944
|
+
$$renderer.push(`</label> <label>${$.escape(ui().labels.order)}`);
|
|
463
945
|
$$renderer.select({
|
|
464
946
|
value: sort.direction,
|
|
465
947
|
disabled: !ready || !complete(),
|
|
@@ -469,85 +951,134 @@ function Ledger($$renderer, $$props) {
|
|
|
469
951
|
}
|
|
470
952
|
}, ($$renderer) => {
|
|
471
953
|
$$renderer.option({ value: "asc" }, ($$renderer) => {
|
|
472
|
-
$$renderer.push(`
|
|
954
|
+
$$renderer.push(`${$.escape(ui().labels.ascending)}`);
|
|
473
955
|
});
|
|
474
956
|
$$renderer.option({ value: "desc" }, ($$renderer) => {
|
|
475
|
-
$$renderer.push(`
|
|
957
|
+
$$renderer.push(`${$.escape(ui().labels.descending)}`);
|
|
476
958
|
});
|
|
477
959
|
});
|
|
478
|
-
$$renderer.push(`</label> <label><input type="checkbox"${$.attr("checked", sort.directoriesFirst, true)}${$.attr("disabled", !ready || !complete(), true)}
|
|
960
|
+
$$renderer.push(`</label> <label><input type="checkbox"${$.attr("checked", sort.directoriesFirst, true)}${$.attr("disabled", !ready || !complete(), true)}/>${$.escape(ui().labels.directoriesFirst)}</label> <label>${$.escape(ui().labels.appearance)}`);
|
|
479
961
|
$$renderer.select({
|
|
480
962
|
value: scheme,
|
|
481
963
|
disabled: !ready,
|
|
482
964
|
onchange: (event) => updateScheme(event.currentTarget.value)
|
|
483
965
|
}, ($$renderer) => {
|
|
484
966
|
$$renderer.option({ value: "system" }, ($$renderer) => {
|
|
485
|
-
$$renderer.push(`
|
|
967
|
+
$$renderer.push(`${$.escape(ui().labels.system)}`);
|
|
486
968
|
});
|
|
487
969
|
$$renderer.option({ value: "light" }, ($$renderer) => {
|
|
488
|
-
$$renderer.push(`
|
|
970
|
+
$$renderer.push(`${$.escape(ui().labels.light)}`);
|
|
489
971
|
});
|
|
490
972
|
$$renderer.option({ value: "dark" }, ($$renderer) => {
|
|
491
|
-
$$renderer.push(`
|
|
973
|
+
$$renderer.push(`${$.escape(ui().labels.dark)}`);
|
|
492
974
|
});
|
|
493
975
|
});
|
|
494
|
-
$$renderer.push(`</label>
|
|
495
|
-
if (
|
|
976
|
+
$$renderer.push(`</label> `);
|
|
977
|
+
if (model.searchIndexHref || model.searchQueryHref) $$renderer.push(`<!--[0--><button${$.attr("disabled", !ready, true)}>${$.escape(ui().labels.searchArchive)}</button>`);
|
|
978
|
+
else $$renderer.push("<!--[-1-->");
|
|
979
|
+
$$renderer.push(`<!--]--></div> <section class="listing-card"><div class="listing-summary">${$.escape(countParts()[0])}<span data-visible-count="">${$.escape(complete() ? rows().length : entries.length)}</span>${$.escape(countParts().slice(1).join("__COUNT__"))}<span role="status" aria-live="polite">${$.escape(busy ? ui().labels.loading : error)}</span></div> `);
|
|
980
|
+
if (failure && failure !== "copy") $$renderer.push(`<!--[0--><button${$.attr("disabled", busy, true)}>${$.escape(ui().labels.retry)}</button>`);
|
|
496
981
|
else $$renderer.push("<!--[-1-->");
|
|
497
|
-
$$renderer.push(`<!--]
|
|
982
|
+
$$renderer.push(`<!--]--> `);
|
|
983
|
+
if (failure === "folder" && retryRequest) $$renderer.push(`<!--[0--><a${$.attr("href", retryRequest[1])}>${$.escape(ui().labels.openNative)}</a>`);
|
|
984
|
+
else $$renderer.push("<!--[-1-->");
|
|
985
|
+
$$renderer.push(`<!--]--> <div class="entry-head"${$.attr("data-custom-columns", Boolean(model.ui?.columns))}${$.attr_style("", { "grid-template-columns": model.ui?.columns ? ui().columns.map((column) => column === "name" ? "minmax(0,1fr)" : typeof column === "object" ? "minmax(0,12rem)" : "minmax(6rem,max-content)").join(" ") : void 0 })}><!--[-->`);
|
|
986
|
+
const each_array_2 = $.ensure_array_like(ui().columns);
|
|
987
|
+
for (let $$index_2 = 0, $$length = each_array_2.length; $$index_2 < $$length; $$index_2++) {
|
|
988
|
+
let column = each_array_2[$$index_2];
|
|
989
|
+
$$renderer.push(`<span>${$.escape(columnLabel(column, ui()))}</span>`);
|
|
990
|
+
}
|
|
991
|
+
$$renderer.push(`<!--]--></div> <ul class="entry-list">`);
|
|
498
992
|
if (model.parentHref) $$renderer.push(`<!--[0--><li class="entry"><a${$.attr("href", model.parentHref)}>../</a></li>`);
|
|
499
993
|
else $$renderer.push("<!--[-1-->");
|
|
500
994
|
$$renderer.push(`<!--]--> `);
|
|
501
995
|
if (start()) $$renderer.push(`<!--[0--><li aria-hidden="true"${$.attr_style("", { height: offsets()[start()] + "px" })}></li>`);
|
|
502
996
|
else $$renderer.push("<!--[-1-->");
|
|
503
997
|
$$renderer.push(`<!--]--> <!--[-->`);
|
|
504
|
-
const
|
|
505
|
-
for (let index = 0, $$length =
|
|
506
|
-
let entry =
|
|
507
|
-
$$renderer.push(`<li class="entry" data-entry=""${$.attr("data-id", entry.id)}${$.attr("data-order", start() + index)}${$.attr("data-active", selected === entry.id)}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
entry,
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
998
|
+
const each_array_3 = $.ensure_array_like(rows().slice(start(), end()));
|
|
999
|
+
for (let index = 0, $$length = each_array_3.length; index < $$length; index++) {
|
|
1000
|
+
let entry = each_array_3[index];
|
|
1001
|
+
$$renderer.push(`<li class="entry" data-entry=""${$.attr("data-custom-columns", Boolean(model.ui?.columns))}${$.attr("data-id", entry.id)}${$.attr("data-order", start() + index)}${$.attr("data-active", selected === entry.id)}${$.attr_style("", { "grid-template-columns": model.ui?.columns ? ui().columns.map((column) => column === "name" ? "minmax(0,1fr)" : "minmax(6rem,max-content)").join(" ") : void 0 })}><!--[-->`);
|
|
1002
|
+
const each_array_4 = $.ensure_array_like(ui().columns);
|
|
1003
|
+
for (let $$index_3 = 0, $$length = each_array_4.length; $$index_3 < $$length; $$index_3++) {
|
|
1004
|
+
let column = each_array_4[$$index_3];
|
|
1005
|
+
if (column === "name") {
|
|
1006
|
+
$$renderer.push(`<!--[0--><span class="identity">`);
|
|
1007
|
+
if (entry.href) {
|
|
1008
|
+
$$renderer.push(`<!--[0--><a class="name"${$.attr("href", safeHref(entry))}${$.attr("target", entry.exitsExplorer ? "_blank" : void 0)}${$.attr("rel", entry.exitsExplorer ? "noopener" : void 0)}>`);
|
|
1009
|
+
if (row) {
|
|
1010
|
+
$$renderer.push("<!--[0-->");
|
|
1011
|
+
row($$renderer, {
|
|
1012
|
+
model,
|
|
1013
|
+
entry,
|
|
1014
|
+
selected: selected === entry.id
|
|
1015
|
+
});
|
|
1016
|
+
$$renderer.push(`<!---->`);
|
|
1017
|
+
} else $$renderer.push(`<!--[-1--><span class="file-icon" aria-hidden="true">${$.html(entryIcon(entryType(entry)))}</span> ${$.escape(entry.name)}${$.escape(isDirectory(entry) ? "/" : "")}`);
|
|
1018
|
+
$$renderer.push(`<!--]--></a>`);
|
|
1019
|
+
} else {
|
|
1020
|
+
$$renderer.push(`<!--[-1--><span>`);
|
|
1021
|
+
if (row) {
|
|
1022
|
+
$$renderer.push("<!--[0-->");
|
|
1023
|
+
row($$renderer, {
|
|
1024
|
+
model,
|
|
1025
|
+
entry,
|
|
1026
|
+
selected: selected === entry.id
|
|
1027
|
+
});
|
|
1028
|
+
$$renderer.push(`<!---->`);
|
|
1029
|
+
} else $$renderer.push(`<!--[-1-->${$.escape(entry.name)}`);
|
|
1030
|
+
$$renderer.push(`<!--]--></span>`);
|
|
1031
|
+
}
|
|
1032
|
+
$$renderer.push(`<!--]-->`);
|
|
1033
|
+
if (entry.link) $$renderer.push(`<!--[0--><small class="target">${$.escape(entry.link.unavailable ? ui().labels.unavailable + " · " : "→ ")}${$.escape(entry.link.target)}</small>`);
|
|
1034
|
+
else $$renderer.push("<!--[-1-->");
|
|
1035
|
+
$$renderer.push(`<!--]--></span>`);
|
|
1036
|
+
} else if (column === "size") $$renderer.push(`<!--[1--><span class="kind">${$.escape(isDirectory(entry) ? ui().labels.folder : size(entry.size))}</span>`);
|
|
1037
|
+
else if (column === "modified") $$renderer.push(`<!--[2--><button class="timestamp-button"${$.attr("disabled", !ready, true)}><time${$.attr("datetime", entry.modifiedAt)}>${$.escape(formattedDate(entry.modifiedAt, ui(), ready))}</time></button>`);
|
|
1038
|
+
else $$renderer.push(`<!--[-1--><span class="entry-data">${$.escape(publicDataValue(entry.data?.[column.dataKey]))}</span>`);
|
|
1039
|
+
$$renderer.push(`<!--]-->`);
|
|
1040
|
+
}
|
|
1041
|
+
$$renderer.push(`<!--]--> `);
|
|
1042
|
+
if (actions || ui().actions.length) {
|
|
1043
|
+
$$renderer.push(`<!--[0--><span data-dirwell-actions="" style="grid-column:1/-1;display:flex;flex-wrap:wrap;gap:.5rem">`);
|
|
1044
|
+
if (actions) {
|
|
523
1045
|
$$renderer.push("<!--[0-->");
|
|
524
|
-
|
|
1046
|
+
actions($$renderer, {
|
|
525
1047
|
model,
|
|
526
1048
|
entry,
|
|
527
1049
|
selected: selected === entry.id
|
|
528
1050
|
});
|
|
529
1051
|
$$renderer.push(`<!---->`);
|
|
530
|
-
} else $$renderer.push(
|
|
1052
|
+
} else $$renderer.push("<!--[-1-->");
|
|
1053
|
+
$$renderer.push(`<!--]--> <!--[-->`);
|
|
1054
|
+
const each_array_5 = $.ensure_array_like(ui().actions);
|
|
1055
|
+
for (let $$index_4 = 0, $$length = each_array_5.length; $$index_4 < $$length; $$index_4++) {
|
|
1056
|
+
let action = each_array_5[$$index_4];
|
|
1057
|
+
if (action === "download" && entry.href && !isDirectory(entry) && !entry.link?.unavailable) $$renderer.push(`<!--[0--><a${$.attr("href", safeHref(entry))} download="">${$.escape(ui().labels.download)}</a>`);
|
|
1058
|
+
else if (action !== "download") $$renderer.push(`<!--[1--><button${$.attr("disabled", !ready || action === "copy-link" && !entry.href, true)}${$.attr("aria-label", (action === "copy-link" ? ui().labels.copyLink : ui().labels.copyPath) + ": " + entry.name)}>${$.escape(action === "copy-link" ? ui().labels.copyLink : ui().labels.copyPath)}</button>`);
|
|
1059
|
+
else $$renderer.push("<!--[-1-->");
|
|
1060
|
+
$$renderer.push(`<!--]-->`);
|
|
1061
|
+
}
|
|
531
1062
|
$$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>`);
|
|
1063
|
+
} else $$renderer.push("<!--[-1-->");
|
|
1064
|
+
$$renderer.push(`<!--]--></li>`);
|
|
537
1065
|
}
|
|
538
1066
|
$$renderer.push(`<!--]--> `);
|
|
539
1067
|
if (end() < rows().length) $$renderer.push(`<!--[0--><li aria-hidden="true"${$.attr_style("", { height: offsets()[rows().length] - offsets()[end()] + "px" })}></li>`);
|
|
540
1068
|
else $$renderer.push("<!--[-1-->");
|
|
541
1069
|
$$renderer.push(`<!--]--></ul> `);
|
|
542
|
-
if (rows().length === 0) $$renderer.push(`<!--[0--><p
|
|
1070
|
+
if (rows().length === 0 && !busy) $$renderer.push(`<!--[0--><p>${$.escape(model.total === 0 ? ui().labels.empty : ui().labels.noMatches)}</p>`);
|
|
543
1071
|
else $$renderer.push("<!--[-1-->");
|
|
544
1072
|
$$renderer.push(`<!--]--></section> `);
|
|
545
1073
|
if (model.page?.count > 1 && !complete()) {
|
|
546
|
-
$$renderer.push(`<!--[0--><nav
|
|
547
|
-
if (model.page.previousHref) $$renderer.push(`<!--[0--><a rel="prev"${$.attr("href", model.page.previousHref)}
|
|
1074
|
+
$$renderer.push(`<!--[0--><nav${$.attr("aria-label", ui().labels.pagination)}>`);
|
|
1075
|
+
if (model.page.previousHref) $$renderer.push(`<!--[0--><a rel="prev"${$.attr("href", model.page.previousHref)}>${$.escape(ui().labels.previous)}</a>`);
|
|
548
1076
|
else $$renderer.push("<!--[-1-->");
|
|
549
|
-
$$renderer.push(`<!--]-->
|
|
550
|
-
|
|
1077
|
+
$$renderer.push(`<!--]--> ${$.escape(label(ui(), "page", {
|
|
1078
|
+
page: model.page.number,
|
|
1079
|
+
pages: model.page.count
|
|
1080
|
+
}))} `);
|
|
1081
|
+
if (model.page.nextHref) $$renderer.push(`<!--[0--><a rel="next"${$.attr("href", model.page.nextHref)}>${$.escape(ui().labels.next)}</a>`);
|
|
551
1082
|
else $$renderer.push("<!--[-1-->");
|
|
552
1083
|
$$renderer.push(`<!--]--></nav>`);
|
|
553
1084
|
} else $$renderer.push("<!--[-1-->");
|
|
@@ -556,8 +1087,12 @@ function Ledger($$renderer, $$props) {
|
|
|
556
1087
|
$$renderer.push("<!--[0-->");
|
|
557
1088
|
footer($$renderer, { model });
|
|
558
1089
|
$$renderer.push(`<!---->`);
|
|
559
|
-
} else
|
|
560
|
-
|
|
1090
|
+
} else if (ui().brand.footerText) {
|
|
1091
|
+
$$renderer.push(`<!--[1--><p class="brand-footer">`);
|
|
1092
|
+
if (ui().brand.footerUrl) $$renderer.push(`<!--[0--><a${$.attr("href", ui().brand.footerUrl)}>${$.escape(ui().brand.footerText)}</a>`);
|
|
1093
|
+
else $$renderer.push(`<!--[-1-->${$.escape(ui().brand.footerText)}`);
|
|
1094
|
+
$$renderer.push(`<!--]--></p>`);
|
|
1095
|
+
} else $$renderer.push(`<!--[-1--><p class="keyboard-help">${$.escape(ui().labels.keyboardHelp)}</p>`);
|
|
561
1096
|
$$renderer.push(`<!--]--></main> `);
|
|
562
1097
|
$$renderer.push("<!--[-1-->");
|
|
563
1098
|
$$renderer.push(`<!--]--> `);
|
|
@@ -571,6 +1106,11 @@ const renderBody = (model) => render(Ledger, { props: { model } }).body;
|
|
|
571
1106
|
//#endregion
|
|
572
1107
|
//#region src/index.ts
|
|
573
1108
|
function createLedgerTheme(options = {}, renderer) {
|
|
1109
|
+
assertThemeOptions(options, ["title", "archiveSearch"]);
|
|
1110
|
+
if (options.title !== void 0 && (typeof options.title !== "string" || !options.title)) throw new TypeError("title must be a non-empty string");
|
|
1111
|
+
if (options.archiveSearch !== void 0 && typeof options.archiveSearch !== "boolean") throw new TypeError("archiveSearch must be a boolean");
|
|
1112
|
+
const ui = resolveCustomization(options);
|
|
1113
|
+
const publishedUi = pickCustomization(options);
|
|
574
1114
|
let resources;
|
|
575
1115
|
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
1116
|
script,
|
|
@@ -579,17 +1119,20 @@ function createLedgerTheme(options = {}, renderer) {
|
|
|
579
1119
|
}));
|
|
580
1120
|
const theme = {
|
|
581
1121
|
name: "ledger",
|
|
1122
|
+
searchIndex: options.archiveSearch ?? true,
|
|
582
1123
|
listing: { pagination: true },
|
|
583
1124
|
entryData: (entry, context) => ({ folderDataHref: folderDataHref(entry, context, "ledger") }),
|
|
584
1125
|
async render(context) {
|
|
585
1126
|
const model = {
|
|
586
|
-
...createThemePage(context, theme, options.title ?? "Dirwell"),
|
|
587
|
-
|
|
1127
|
+
...createThemePage(context, theme, options.title ?? ui.brand.name ?? "Dirwell"),
|
|
1128
|
+
...publishedUi === void 0 ? {} : { ui: publishedUi },
|
|
1129
|
+
searchIndexHref: theme.searchIndex === false ? null : context.searchIndexHref,
|
|
1130
|
+
searchQueryHref: theme.searchIndex === false ? null : context.searchQueryHref ?? null
|
|
588
1131
|
};
|
|
589
1132
|
const { script, scriptName, styles } = await loadResources();
|
|
590
1133
|
const json = JSON.stringify(model).replaceAll("<", "\\u003c");
|
|
591
1134
|
return {
|
|
592
|
-
html: `<!doctype html><html lang="
|
|
1135
|
+
html: `<!doctype html><html lang="${escapeHtml(ui.language)}" dir="${ui.direction}" 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>`}${ui.brand.faviconUrl ? `<link rel="icon" href="${escapeHtml(ui.brand.faviconUrl)}">` : ""}<style>${styles}${renderer?.styles ?? ""}${customizationCss(ui)}</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
1136
|
assets: {
|
|
594
1137
|
[scriptName]: script,
|
|
595
1138
|
[pageAsset("ledger", model.path, model.page?.number ?? 1)]: JSON.stringify(model)
|