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