@dirwell/theme-crosswave 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 +18 -18
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +826 -59
- package/dist/index.mjs.map +1 -1
- package/dist/model-Dr7YgDTv.d.mts +174 -0
- package/dist/model.d.mts +1 -1
- package/dist/source/Crosswave.vue +536 -57
- 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 +24 -1
- package/package.json +7 -3
- package/dist/model-D4-K54I9.d.mts +0 -53
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 mime from "mime";
|
|
6
6
|
import { computed, createSSRApp, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch, watchEffect } from "vue";
|
|
7
|
-
import { renderToString, ssrIncludeBooleanAttr, ssrInterpolate, ssrLooseContain, ssrLooseEqual, ssrRenderAttr, ssrRenderClass, ssrRenderDynamicAttr, ssrRenderList, ssrRenderSlot } from "vue/server-renderer";
|
|
7
|
+
import { renderToString, ssrIncludeBooleanAttr, ssrInterpolate, ssrLooseContain, ssrLooseEqual, ssrRenderAttr, ssrRenderClass, ssrRenderDynamicAttr, ssrRenderList, ssrRenderSlot, ssrRenderStyle } from "vue/server-renderer";
|
|
8
8
|
import { useGamepad } from "@vueuse/core";
|
|
9
9
|
import { ListingDataError, loadListing, parseListingEntries } from "@dirwell/core/client";
|
|
10
10
|
//#region ../shared-theme/src/page.ts
|
|
@@ -37,7 +37,7 @@ function createThemePage(context, theme, title) {
|
|
|
37
37
|
documentBaseHref: context.documentBaseHref,
|
|
38
38
|
directoryPath
|
|
39
39
|
};
|
|
40
|
-
const dataForDirectory = (logicalPath) => folderDataHref({
|
|
40
|
+
const dataForDirectory = (logicalPath) => context.hasPageForDirectory?.(logicalPath) === false ? null : folderDataHref({
|
|
41
41
|
id: logicalPath,
|
|
42
42
|
name: logicalPath,
|
|
43
43
|
kind: "directory",
|
|
@@ -68,6 +68,10 @@ function createThemePage(context, theme, title) {
|
|
|
68
68
|
manifestHref: context.listing?.manifestHref ?? null,
|
|
69
69
|
...context.listing ? { listingRevision: context.listing.revision } : {},
|
|
70
70
|
updatesHref: context.updatesHref ?? null,
|
|
71
|
+
...context.searchQueryHref ? {
|
|
72
|
+
searchQueryHref: context.searchQueryHref,
|
|
73
|
+
searchRefreshIntervalMs: context.searchRefreshIntervalMs
|
|
74
|
+
} : {},
|
|
71
75
|
total: context.listing?.page.total ?? entries.length,
|
|
72
76
|
entries,
|
|
73
77
|
sort: context.sort,
|
|
@@ -255,6 +259,275 @@ function entryCategories(entry, type, categories) {
|
|
|
255
259
|
return categories.filter((category) => category.match === "all" || folder && category.match === "folders" || !folder && category.match === "other" && !specific.length || specific.includes(category));
|
|
256
260
|
}
|
|
257
261
|
//#endregion
|
|
262
|
+
//#region ../shared-theme/src/live-search.ts
|
|
263
|
+
async function queryLiveArchive(href, query, signal) {
|
|
264
|
+
const url = new URL(href, document.baseURI);
|
|
265
|
+
if (url.origin !== location.origin) throw new Error("Unsafe search endpoint");
|
|
266
|
+
url.searchParams.set("q", query);
|
|
267
|
+
const response = await fetch(url, {
|
|
268
|
+
signal,
|
|
269
|
+
cache: "no-store"
|
|
270
|
+
});
|
|
271
|
+
if (!response.ok) throw new Error("Archive search unavailable");
|
|
272
|
+
const data = await response.json();
|
|
273
|
+
if (typeof data !== "object" || data === null || !("version" in data) || data.version !== 1 || !("results" in data) || !Array.isArray(data.results) || data.results.length > 1e4 || !("truncated" in data) || typeof data.truncated !== "boolean") throw new Error("Invalid archive results");
|
|
274
|
+
return {
|
|
275
|
+
records: data.results.map((entry) => {
|
|
276
|
+
if (typeof entry !== "object" || entry === null || !("name" in entry) || typeof entry.name !== "string" || !("path" in entry) || typeof entry.path !== "string" || !("kind" in entry) || !["file", "directory"].includes(String(entry.kind)) || !("href" in entry) || entry.href !== null && typeof entry.href !== "string") throw new Error("Invalid archive record");
|
|
277
|
+
const target = entry.href === null ? null : new URL(entry.href, url);
|
|
278
|
+
if (target && !["http:", "https:"].includes(target.protocol)) throw new Error("Unsafe archive link");
|
|
279
|
+
return {
|
|
280
|
+
name: entry.name,
|
|
281
|
+
path: entry.path,
|
|
282
|
+
href: target?.href ?? null,
|
|
283
|
+
exitsExplorer: entry.kind !== "directory"
|
|
284
|
+
};
|
|
285
|
+
}),
|
|
286
|
+
truncated: data.truncated
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region ../shared-theme/src/customization.ts
|
|
291
|
+
/** Small serializable inputs shared by official themes; layout stays theme-owned. */
|
|
292
|
+
const defaultLabels = {
|
|
293
|
+
other: "other",
|
|
294
|
+
crosswaveKeyboardHelp: "Arrow keys navigate · Enter confirms · Backspace returns",
|
|
295
|
+
brokenLink: "broken link",
|
|
296
|
+
unavailableLink: "unavailable link",
|
|
297
|
+
cycleLink: "cycle link",
|
|
298
|
+
azure: "azure",
|
|
299
|
+
violet: "violet",
|
|
300
|
+
amber: "amber",
|
|
301
|
+
rose: "rose",
|
|
302
|
+
jade: "jade",
|
|
303
|
+
graphite: "graphite",
|
|
304
|
+
allFiles: "All files",
|
|
305
|
+
categoryFolders: "Folders",
|
|
306
|
+
categoryPhotos: "Photos",
|
|
307
|
+
categoryMusic: "Music",
|
|
308
|
+
categoryVideos: "Videos",
|
|
309
|
+
categoryDocuments: "Documents",
|
|
310
|
+
categoryOther: "Other",
|
|
311
|
+
indexOf: "Index of {path}",
|
|
312
|
+
home: "Home",
|
|
313
|
+
breadcrumb: "Breadcrumb",
|
|
314
|
+
archive: "File archive",
|
|
315
|
+
searchDirectory: "Search this directory",
|
|
316
|
+
searchFiles: "Search files",
|
|
317
|
+
search: "Search",
|
|
318
|
+
types: "Types",
|
|
319
|
+
directory: "directory",
|
|
320
|
+
file: "file",
|
|
321
|
+
link: "link",
|
|
322
|
+
folder: "Folder",
|
|
323
|
+
sort: "Sort",
|
|
324
|
+
name: "Name",
|
|
325
|
+
size: "Size",
|
|
326
|
+
modified: "Modified",
|
|
327
|
+
names: "Names",
|
|
328
|
+
natural: "Natural",
|
|
329
|
+
locale: "Locale",
|
|
330
|
+
unicode: "Unicode",
|
|
331
|
+
order: "Order",
|
|
332
|
+
ascending: "Ascending",
|
|
333
|
+
descending: "Descending",
|
|
334
|
+
directoriesFirst: "Folders first",
|
|
335
|
+
appearance: "Appearance",
|
|
336
|
+
system: "System",
|
|
337
|
+
light: "Light",
|
|
338
|
+
dark: "Dark",
|
|
339
|
+
searchArchive: "Search archive",
|
|
340
|
+
archiveSearchInput: "Search archive files",
|
|
341
|
+
entries: "{count} entries",
|
|
342
|
+
pageEntries: "{total} entries · {count} on this page",
|
|
343
|
+
loading: "Loading…",
|
|
344
|
+
noMatches: "No matching entries.",
|
|
345
|
+
empty: "This directory is empty.",
|
|
346
|
+
pagination: "Pagination",
|
|
347
|
+
previous: "Previous",
|
|
348
|
+
next: "Next",
|
|
349
|
+
page: "Page {page} of {pages}",
|
|
350
|
+
keyboardHelp: "Arrow keys select files · Home / End jump · / searches · Backspace opens parent",
|
|
351
|
+
modifiedTime: "Modified time",
|
|
352
|
+
close: "Close",
|
|
353
|
+
unknown: "Unknown",
|
|
354
|
+
unavailable: "Target unavailable",
|
|
355
|
+
beginArchive: "Enter a search to begin.",
|
|
356
|
+
searchingArchive: "Searching archive…",
|
|
357
|
+
archiveResults: "{count} results",
|
|
358
|
+
truncatedResults: "Showing {shown} of {count} results",
|
|
359
|
+
archiveError: "Could not load archive search. Try again.",
|
|
360
|
+
listingError: "Could not load complete directory data. Native page links remain available.",
|
|
361
|
+
folderError: "Could not open the folder. Its native link remains available.",
|
|
362
|
+
updatesError: "Live updates disconnected. Refresh to retry.",
|
|
363
|
+
retry: "Retry",
|
|
364
|
+
openNative: "Open folder directly",
|
|
365
|
+
categories: "File categories",
|
|
366
|
+
items: "{count} items",
|
|
367
|
+
filesIn: "Files in {path}",
|
|
368
|
+
controller: "Controller · Confirm / Back",
|
|
369
|
+
color: "Color",
|
|
370
|
+
pauseMotion: "Pause motion",
|
|
371
|
+
resumeMotion: "Resume motion",
|
|
372
|
+
copyLink: "Copy link",
|
|
373
|
+
copyPath: "Copy path",
|
|
374
|
+
download: "Download",
|
|
375
|
+
copied: "Copied.",
|
|
376
|
+
copyError: "Could not copy. Use the native link or select the path.",
|
|
377
|
+
repository: "Repository",
|
|
378
|
+
author: "by",
|
|
379
|
+
target: "Target",
|
|
380
|
+
newTab: "opens in a new tab"
|
|
381
|
+
};
|
|
382
|
+
function resolveCustomization(options = {}) {
|
|
383
|
+
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;
|
|
384
|
+
for (const [key, value] of Object.entries({
|
|
385
|
+
language,
|
|
386
|
+
locale,
|
|
387
|
+
timeZone
|
|
388
|
+
})) if (typeof value !== "string" || !value) throw new TypeError(`${key} must be a non-empty string`);
|
|
389
|
+
new Intl.DateTimeFormat(locale, { timeZone });
|
|
390
|
+
const direction = options.direction ?? "ltr";
|
|
391
|
+
if (direction !== "ltr" && direction !== "rtl") throw new TypeError("direction must be ltr or rtl");
|
|
392
|
+
const labels = { ...defaultLabels };
|
|
393
|
+
if (options.labels !== void 0) {
|
|
394
|
+
if (typeof options.labels !== "object" || options.labels === null || Array.isArray(options.labels)) throw new TypeError("labels must be an object");
|
|
395
|
+
for (const [key, value] of Object.entries(options.labels)) {
|
|
396
|
+
if (!Object.hasOwn(labels, key) || typeof value !== "string") throw new TypeError(`Unsupported label or value: ${key}`);
|
|
397
|
+
Object.assign(labels, { [key]: value });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const brand = options.brand ?? {};
|
|
401
|
+
if (typeof brand !== "object" || brand === null || Array.isArray(brand)) throw new TypeError("brand must be an object");
|
|
402
|
+
for (const [key, value] of Object.entries(brand)) {
|
|
403
|
+
if (![
|
|
404
|
+
"name",
|
|
405
|
+
"logoUrl",
|
|
406
|
+
"logoAlt",
|
|
407
|
+
"footerText",
|
|
408
|
+
"footerUrl",
|
|
409
|
+
"faviconUrl"
|
|
410
|
+
].includes(key) || typeof value !== "string") throw new TypeError(`Unsupported brand option or value: ${key}`);
|
|
411
|
+
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`);
|
|
412
|
+
}
|
|
413
|
+
const columns = options.columns ?? [
|
|
414
|
+
"name",
|
|
415
|
+
"size",
|
|
416
|
+
"modified"
|
|
417
|
+
];
|
|
418
|
+
if (!Array.isArray(columns) || !columns.includes("name")) throw new TypeError("columns must contain name and unique supported columns");
|
|
419
|
+
const columnIds = /* @__PURE__ */ new Set();
|
|
420
|
+
for (const column of columns) {
|
|
421
|
+
let id;
|
|
422
|
+
if (typeof column === "string") {
|
|
423
|
+
if (![
|
|
424
|
+
"name",
|
|
425
|
+
"size",
|
|
426
|
+
"modified"
|
|
427
|
+
].includes(column)) throw new TypeError("Unsupported column");
|
|
428
|
+
id = column;
|
|
429
|
+
} else {
|
|
430
|
+
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");
|
|
431
|
+
id = "data:" + column.dataKey;
|
|
432
|
+
}
|
|
433
|
+
if (columnIds.has(id)) throw new TypeError("columns must contain unique supported columns");
|
|
434
|
+
columnIds.add(id);
|
|
435
|
+
}
|
|
436
|
+
const actions = options.actions ?? [];
|
|
437
|
+
if (!Array.isArray(actions) || new Set(actions).size !== actions.length || actions.some((key) => ![
|
|
438
|
+
"copy-link",
|
|
439
|
+
"copy-path",
|
|
440
|
+
"download"
|
|
441
|
+
].includes(key))) throw new TypeError("actions must contain unique supported actions");
|
|
442
|
+
const cssVariables = options.cssVariables ?? {};
|
|
443
|
+
if (typeof cssVariables !== "object" || cssVariables === null || Array.isArray(cssVariables)) throw new TypeError("cssVariables must be an object");
|
|
444
|
+
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}`);
|
|
445
|
+
return {
|
|
446
|
+
language,
|
|
447
|
+
dateOptions: {
|
|
448
|
+
...options.locale === void 0 ? {} : { locale: options.locale },
|
|
449
|
+
...options.timeZone === void 0 ? {} : { timeZone: options.timeZone }
|
|
450
|
+
},
|
|
451
|
+
direction,
|
|
452
|
+
locale,
|
|
453
|
+
timeZone,
|
|
454
|
+
labels,
|
|
455
|
+
brand: { ...brand },
|
|
456
|
+
columns: columns.map((column) => typeof column === "string" ? column : { ...column }),
|
|
457
|
+
actions: [...actions],
|
|
458
|
+
cssVariables: { ...cssVariables }
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
resolveCustomization();
|
|
462
|
+
function label(ui, key, values = {}) {
|
|
463
|
+
return ui.labels[key].replace(/\{([a-zA-Z]+)\}/g, (match, name) => Object.hasOwn(values, name) ? String(values[name]) : match);
|
|
464
|
+
}
|
|
465
|
+
const dateFormats = /* @__PURE__ */ new Map();
|
|
466
|
+
function formattedDate(value, ui, local = false) {
|
|
467
|
+
const date = new Date(value);
|
|
468
|
+
if (Number.isNaN(date.getTime())) return ui.labels.unknown;
|
|
469
|
+
if (ui.dateOptions.locale === void 0 && ui.dateOptions.timeZone === void 0) return local ? date.toLocaleString(void 0, {
|
|
470
|
+
dateStyle: "short",
|
|
471
|
+
timeStyle: "short"
|
|
472
|
+
}) : date.toISOString().slice(0, 16).replace("T", " ") + " UTC";
|
|
473
|
+
const locale = ui.dateOptions.locale, timeZone = ui.dateOptions.timeZone ?? (local ? void 0 : "UTC");
|
|
474
|
+
const key = String(locale) + "\0" + String(timeZone);
|
|
475
|
+
let formatter = dateFormats.get(key);
|
|
476
|
+
if (formatter === void 0) {
|
|
477
|
+
formatter = new Intl.DateTimeFormat(locale, {
|
|
478
|
+
timeZone,
|
|
479
|
+
dateStyle: "short",
|
|
480
|
+
timeStyle: "short"
|
|
481
|
+
});
|
|
482
|
+
if (dateFormats.size >= 32) dateFormats.delete(dateFormats.keys().next().value);
|
|
483
|
+
dateFormats.set(key, formatter);
|
|
484
|
+
}
|
|
485
|
+
return formatter.format(date);
|
|
486
|
+
}
|
|
487
|
+
function customizationCss(ui) {
|
|
488
|
+
const declarations = Object.entries(ui.cssVariables).map(([key, value]) => `${key}:${value}`).join(";");
|
|
489
|
+
return declarations ? `:root:root{${declarations}}${ui.cssVariables["--dw-font-family"] ? "body{font-family:var(--dw-font-family)}" : ""}` : "";
|
|
490
|
+
}
|
|
491
|
+
/** Publish only supplied customization instead of repeating defaults in every page. */
|
|
492
|
+
function pickCustomization(options) {
|
|
493
|
+
const result = {};
|
|
494
|
+
for (const key of [
|
|
495
|
+
"language",
|
|
496
|
+
"direction",
|
|
497
|
+
"locale",
|
|
498
|
+
"timeZone",
|
|
499
|
+
"labels",
|
|
500
|
+
"brand",
|
|
501
|
+
"columns",
|
|
502
|
+
"actions",
|
|
503
|
+
"cssVariables"
|
|
504
|
+
]) if (options[key] !== void 0) {
|
|
505
|
+
const value = options[key];
|
|
506
|
+
Object.assign(result, { [key]: Array.isArray(value) ? value.map((item) => typeof item === "object" ? { ...item } : item) : typeof value === "object" && value !== null ? { ...value } : value });
|
|
507
|
+
}
|
|
508
|
+
return Object.keys(result).length ? result : void 0;
|
|
509
|
+
}
|
|
510
|
+
/** Built-in factories fail on typos instead of silently ignoring configuration. */
|
|
511
|
+
function assertThemeOptions(value, extraKeys) {
|
|
512
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Theme options must be an object");
|
|
513
|
+
const allowed = [
|
|
514
|
+
"language",
|
|
515
|
+
"direction",
|
|
516
|
+
"locale",
|
|
517
|
+
"timeZone",
|
|
518
|
+
"labels",
|
|
519
|
+
"brand",
|
|
520
|
+
"columns",
|
|
521
|
+
"actions",
|
|
522
|
+
"cssVariables",
|
|
523
|
+
...extraKeys
|
|
524
|
+
];
|
|
525
|
+
for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new TypeError(`Unsupported theme option: ${key}`);
|
|
526
|
+
}
|
|
527
|
+
function publicDataValue(value) {
|
|
528
|
+
return value === void 0 || value === null ? "" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
529
|
+
}
|
|
530
|
+
//#endregion
|
|
258
531
|
//#region ../shared-theme/src/navigation.ts
|
|
259
532
|
/** Folder snapshots resolve relative URLs before History changes the document URL. */
|
|
260
533
|
async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false, attempt = 0) {
|
|
@@ -270,6 +543,25 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
270
543
|
const data = value;
|
|
271
544
|
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");
|
|
272
545
|
if (data.metadataHead !== void 0 && typeof data.metadataHead !== "string") throw new ListingDataError("Invalid folder metadata");
|
|
546
|
+
const sort = data.sort;
|
|
547
|
+
if (!sort || typeof sort !== "object" || ![
|
|
548
|
+
"name",
|
|
549
|
+
"size",
|
|
550
|
+
"modified"
|
|
551
|
+
].includes(String(sort.field)) || !["asc", "desc"].includes(String(sort.direction)) || ![
|
|
552
|
+
"natural",
|
|
553
|
+
"locale",
|
|
554
|
+
"unicode"
|
|
555
|
+
].includes(String(sort.nameMode)) || typeof sort.directoriesFirst !== "boolean") throw new ListingDataError("Invalid folder order");
|
|
556
|
+
if (theme === "crosswave") {
|
|
557
|
+
const categories = data.categories;
|
|
558
|
+
const ids = /* @__PURE__ */ new Set();
|
|
559
|
+
if (!Array.isArray(categories) || categories.length === 0 || categories.some((category) => {
|
|
560
|
+
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;
|
|
561
|
+
ids.add(category.id);
|
|
562
|
+
return false;
|
|
563
|
+
})) throw new ListingDataError("Invalid folder categories");
|
|
564
|
+
}
|
|
273
565
|
const base = document.querySelector("base")?.href ?? pageHref;
|
|
274
566
|
const link = (raw) => {
|
|
275
567
|
if (raw === null) return null;
|
|
@@ -286,8 +578,20 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
286
578
|
} } : {}
|
|
287
579
|
}));
|
|
288
580
|
if (entries.length > Number(data.total)) throw new ListingDataError("Invalid folder count");
|
|
581
|
+
if (data.page !== null) {
|
|
582
|
+
const page = data.page;
|
|
583
|
+
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");
|
|
584
|
+
data.page = {
|
|
585
|
+
...page,
|
|
586
|
+
href: link(page.href),
|
|
587
|
+
previousHref: link(page.previousHref),
|
|
588
|
+
nextHref: link(page.nextHref)
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
if (data.ui !== void 0) resolveCustomization(data.ui);
|
|
289
592
|
const result = {
|
|
290
593
|
...data,
|
|
594
|
+
searchQueryHref: link(data.searchQueryHref ?? null),
|
|
291
595
|
updatesHref: link(data.updatesHref ?? null),
|
|
292
596
|
entries,
|
|
293
597
|
dataHref: source.href,
|
|
@@ -305,10 +609,10 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
305
609
|
};
|
|
306
610
|
})
|
|
307
611
|
};
|
|
308
|
-
if (
|
|
612
|
+
if (result.manifestHref) {
|
|
309
613
|
const loaded = await loadListing(result.manifestHref, {
|
|
310
614
|
signal,
|
|
311
|
-
cache: "no-store"
|
|
615
|
+
cache: fresh ? "no-store" : "default"
|
|
312
616
|
});
|
|
313
617
|
if (loaded.manifest.total !== data.total || data.listingRevision !== void 0 && loaded.manifest.revision !== data.listingRevision) {
|
|
314
618
|
if (attempt < 2) return fetchThemePage(dataHref, pageHref, theme, signal, true, attempt + 1);
|
|
@@ -376,6 +680,139 @@ function applyThemeMetadata(page, pageHref) {
|
|
|
376
680
|
}
|
|
377
681
|
}
|
|
378
682
|
//#endregion
|
|
683
|
+
//#region ../shared-theme/src/view-state.ts
|
|
684
|
+
const supportedTypes = [
|
|
685
|
+
"directory",
|
|
686
|
+
"file",
|
|
687
|
+
"link"
|
|
688
|
+
];
|
|
689
|
+
function validSort(value) {
|
|
690
|
+
if (typeof value !== "object" || value === null) return false;
|
|
691
|
+
const sort = value;
|
|
692
|
+
return [
|
|
693
|
+
"name",
|
|
694
|
+
"size",
|
|
695
|
+
"modified"
|
|
696
|
+
].includes(String(sort.field)) && ["asc", "desc"].includes(String(sort.direction)) && [
|
|
697
|
+
"natural",
|
|
698
|
+
"locale",
|
|
699
|
+
"unicode"
|
|
700
|
+
].includes(String(sort.nameMode)) && typeof sort.directoriesFirst === "boolean";
|
|
701
|
+
}
|
|
702
|
+
function position(value) {
|
|
703
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(value, 1e9)) : 0;
|
|
704
|
+
}
|
|
705
|
+
/** URL controls are shareable; selection and scroll belong only to one history entry. */
|
|
706
|
+
function readView(href, theme, path, defaults, categories = [], historyState = null) {
|
|
707
|
+
const params = new URL(href).searchParams;
|
|
708
|
+
const result = {
|
|
709
|
+
...defaults,
|
|
710
|
+
query: (params.get("dw.q") ?? "").slice(0, 4096),
|
|
711
|
+
selected: null,
|
|
712
|
+
windowY: 0,
|
|
713
|
+
listTop: 0
|
|
714
|
+
};
|
|
715
|
+
if (theme === "ledger") {
|
|
716
|
+
const parts = params.get("dw.sort")?.split(":");
|
|
717
|
+
if (parts?.length === 4) {
|
|
718
|
+
const sort = {
|
|
719
|
+
field: parts[0],
|
|
720
|
+
direction: parts[1],
|
|
721
|
+
nameMode: parts[2],
|
|
722
|
+
directoriesFirst: parts[3] === "folders"
|
|
723
|
+
};
|
|
724
|
+
if (["folders", "mixed"].includes(parts[3]) && validSort(sort)) result.sort = sort;
|
|
725
|
+
}
|
|
726
|
+
if (params.has("dw.types")) {
|
|
727
|
+
const types = params.get("dw.types").split(",").filter(Boolean);
|
|
728
|
+
if (types.every((type) => supportedTypes.includes(type))) result.types = [...new Set(types)];
|
|
729
|
+
}
|
|
730
|
+
} else {
|
|
731
|
+
const category = params.get("dw.category");
|
|
732
|
+
if (category !== null && categories.includes(category)) result.category = category;
|
|
733
|
+
}
|
|
734
|
+
if (typeof historyState === "object" && historyState !== null && "dirwell" in historyState) {
|
|
735
|
+
const state = historyState.dirwell;
|
|
736
|
+
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) {
|
|
737
|
+
const view = state.view;
|
|
738
|
+
result.selected = typeof view.selected === "string" && view.selected.length <= 4096 ? view.selected : null;
|
|
739
|
+
result.windowY = position(view.windowY);
|
|
740
|
+
result.listTop = position(view.listTop);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return result;
|
|
744
|
+
}
|
|
745
|
+
function viewHref(href, theme, view, defaults) {
|
|
746
|
+
const url = new URL(href);
|
|
747
|
+
for (const key of [
|
|
748
|
+
"dw.q",
|
|
749
|
+
"dw.sort",
|
|
750
|
+
"dw.types",
|
|
751
|
+
"dw.category"
|
|
752
|
+
]) url.searchParams.delete(key);
|
|
753
|
+
if (view.query) url.searchParams.set("dw.q", view.query.slice(0, 4096));
|
|
754
|
+
if (theme === "ledger") {
|
|
755
|
+
if (view.sort && [
|
|
756
|
+
"field",
|
|
757
|
+
"direction",
|
|
758
|
+
"nameMode",
|
|
759
|
+
"directoriesFirst"
|
|
760
|
+
].some((key) => view.sort[key] !== defaults.sort?.[key])) {
|
|
761
|
+
const sort = view.sort;
|
|
762
|
+
url.searchParams.set("dw.sort", `${sort.field}:${sort.direction}:${sort.nameMode}:${sort.directoriesFirst ? "folders" : "mixed"}`);
|
|
763
|
+
}
|
|
764
|
+
if (view.types && supportedTypes.some((type) => view.types.includes(type) !== (defaults.types ?? supportedTypes).includes(type))) url.searchParams.set("dw.types", view.types.join(","));
|
|
765
|
+
} else if (view.category && view.category !== defaults.category) url.searchParams.set("dw.category", view.category);
|
|
766
|
+
return url.href;
|
|
767
|
+
}
|
|
768
|
+
/** History failures never require discarding a successfully loaded view. */
|
|
769
|
+
function createViewHistory(theme) {
|
|
770
|
+
let committedHref = location.href;
|
|
771
|
+
let committedState = history.state;
|
|
772
|
+
const stateFor = (dataHref, path, view) => ({
|
|
773
|
+
...typeof history.state === "object" && history.state !== null ? history.state : {},
|
|
774
|
+
dataHref,
|
|
775
|
+
dirwell: {
|
|
776
|
+
version: 1,
|
|
777
|
+
theme,
|
|
778
|
+
path,
|
|
779
|
+
...view === void 0 ? {} : { view: {
|
|
780
|
+
query: view.query,
|
|
781
|
+
...view.sort === void 0 ? {} : { sort: { ...view.sort } },
|
|
782
|
+
...view.types === void 0 ? {} : { types: [...view.types] },
|
|
783
|
+
category: view.category ?? null,
|
|
784
|
+
selected: view.selected ?? null,
|
|
785
|
+
windowY: view.windowY ?? 0,
|
|
786
|
+
listTop: view.listTop ?? 0
|
|
787
|
+
} }
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
return {
|
|
791
|
+
save(dataHref, path, view, defaults) {
|
|
792
|
+
if (location.href !== committedHref) return;
|
|
793
|
+
const href = viewHref(committedHref, theme, view, defaults);
|
|
794
|
+
const state = stateFor(dataHref, path, view);
|
|
795
|
+
try {
|
|
796
|
+
history.replaceState(state, "", href);
|
|
797
|
+
committedHref = href;
|
|
798
|
+
committedState = state;
|
|
799
|
+
} catch {}
|
|
800
|
+
},
|
|
801
|
+
commit(dataHref, path, href, push) {
|
|
802
|
+
const state = stateFor(dataHref, path);
|
|
803
|
+
if (push) history.pushState(state, "", href);
|
|
804
|
+
else history.replaceState(state, "", href);
|
|
805
|
+
committedHref = href;
|
|
806
|
+
committedState = state;
|
|
807
|
+
},
|
|
808
|
+
rollback() {
|
|
809
|
+
if (location.href !== committedHref) try {
|
|
810
|
+
history.replaceState(committedState, "", committedHref);
|
|
811
|
+
} catch {}
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
//#endregion
|
|
379
816
|
//#region ../shared-theme/src/icons.ts
|
|
380
817
|
const paths = {
|
|
381
818
|
directory: "<path d=\"M3 7V5h6l2 3h10v12H3Z\"/><path d=\"M3 10h18\"/>",
|
|
@@ -404,14 +841,6 @@ function size(bytes) {
|
|
|
404
841
|
}
|
|
405
842
|
return `${value.toFixed(index === 0 || value >= 100 ? 0 : 1)} ${units[index]}`;
|
|
406
843
|
}
|
|
407
|
-
function modified(value, local = false) {
|
|
408
|
-
const date = new Date(value);
|
|
409
|
-
if (Number.isNaN(date.getTime())) return "Unknown";
|
|
410
|
-
return local ? date.toLocaleString(void 0, {
|
|
411
|
-
dateStyle: "short",
|
|
412
|
-
timeStyle: "short"
|
|
413
|
-
}) : `${date.toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
414
|
-
}
|
|
415
844
|
//#endregion
|
|
416
845
|
//#region .generated/wave.mjs
|
|
417
846
|
function createWave(canvas, colors, root, moving, onReady) {
|
|
@@ -557,11 +986,154 @@ var App_server_default = {
|
|
|
557
986
|
required: true
|
|
558
987
|
} },
|
|
559
988
|
setup(__props) {
|
|
560
|
-
const props = __props, model = shallowRef(restoreThemePage(props.model, Boolean(import.meta.hot))), entries = shallowRef(model.value.entries);
|
|
561
|
-
const query = ref(""), category = ref(
|
|
989
|
+
const props = __props, model = shallowRef(restoreThemePage(props.model, Boolean(import.meta.hot))), entries = shallowRef(model.value.entries), ui = computed(() => resolveCustomization(model.value.ui));
|
|
990
|
+
const query = ref(""), category = ref(model.value.categories[0]?.id ?? null), selected = ref(null), ready = ref(false), busy = ref(false), error = ref(""), failure = ref(null), color = ref(props.model.color), paused = ref(!props.model.backgroundMotion), clock = ref(""), pad = ref(false), waveReady = ref(false), pendingUpdates = ref(null), canvas = ref(), search = ref(), listScroll = ref(), footerElement = ref(), scrollTop = ref(0), viewportHeight = ref(400), retryRequest = shallowRef(null), heights = shallowRef(/* @__PURE__ */ new Map());
|
|
991
|
+
const archiveOpen = ref(false), archiveQuery = ref(""), archiveRows = shallowRef([]), archiveStatus = ref(""), archiveFailed = ref(false), archiveTrigger = ref(), archiveDialog = ref(), archiveInput = ref();
|
|
992
|
+
let archiveAbort, archiveRequest = 0, archivePending = false, archiveDebouncing = false;
|
|
993
|
+
async function searchArchive() {
|
|
994
|
+
const token = ++archiveRequest;
|
|
995
|
+
archiveAbort?.abort();
|
|
996
|
+
const local = archiveAbort = new AbortController();
|
|
997
|
+
archiveFailed.value = false;
|
|
998
|
+
if (!archiveQuery.value.trim()) {
|
|
999
|
+
archiveRows.value = [];
|
|
1000
|
+
archiveStatus.value = ui.value.labels.beginArchive;
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
archiveStatus.value = ui.value.labels.searchingArchive;
|
|
1004
|
+
archivePending = true;
|
|
1005
|
+
try {
|
|
1006
|
+
const result = await queryLiveArchive(model.value.searchQueryHref, archiveQuery.value.trim(), local.signal);
|
|
1007
|
+
if (local.signal.aborted || token !== archiveRequest) return;
|
|
1008
|
+
archiveRows.value = result.records;
|
|
1009
|
+
archiveStatus.value = result.truncated ? label(ui.value, "truncatedResults", {
|
|
1010
|
+
shown: result.records.length,
|
|
1011
|
+
count: result.records.length + "+"
|
|
1012
|
+
}) : label(ui.value, "archiveResults", { count: result.records.length });
|
|
1013
|
+
} catch {
|
|
1014
|
+
if (local.signal.aborted || token !== archiveRequest) return;
|
|
1015
|
+
archiveRows.value = [];
|
|
1016
|
+
archiveStatus.value = ui.value.labels.archiveError;
|
|
1017
|
+
archiveFailed.value = true;
|
|
1018
|
+
} finally {
|
|
1019
|
+
if (token === archiveRequest) archivePending = false;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
watch([archiveOpen, archiveQuery], (_, __, cleanup) => {
|
|
1023
|
+
archiveAbort?.abort();
|
|
1024
|
+
archiveRequest++;
|
|
1025
|
+
archivePending = false;
|
|
1026
|
+
if (!archiveOpen.value) return;
|
|
1027
|
+
archiveDebouncing = true;
|
|
1028
|
+
const debounce = setTimeout(() => {
|
|
1029
|
+
archiveDebouncing = false;
|
|
1030
|
+
searchArchive();
|
|
1031
|
+
}, 300);
|
|
1032
|
+
cleanup(() => {
|
|
1033
|
+
clearTimeout(debounce);
|
|
1034
|
+
archiveDebouncing = false;
|
|
1035
|
+
});
|
|
1036
|
+
});
|
|
1037
|
+
watch(archiveOpen, async (open, _, cleanup) => {
|
|
1038
|
+
if (!open) {
|
|
1039
|
+
await nextTick();
|
|
1040
|
+
archiveTrigger.value?.focus();
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
await nextTick();
|
|
1044
|
+
if (!archiveOpen.value) return;
|
|
1045
|
+
archiveDialog.value?.showModal();
|
|
1046
|
+
archiveInput.value?.focus();
|
|
1047
|
+
const timer = setInterval(() => {
|
|
1048
|
+
if (document.visibilityState === "visible" && archiveQuery.value.trim() && !archivePending && !archiveDebouncing) searchArchive();
|
|
1049
|
+
}, model.value.searchRefreshIntervalMs ?? 2e3);
|
|
1050
|
+
cleanup(() => {
|
|
1051
|
+
clearInterval(timer);
|
|
1052
|
+
archiveAbort?.abort();
|
|
1053
|
+
archiveRequest++;
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
562
1056
|
const complete = computed(() => entries.value.length === model.value.total), membership = (entry) => Array.isArray(entry.presentation?.categories) ? entry.presentation.categories : [];
|
|
563
|
-
const rows = computed(() => entries.value.filter((entry) =>
|
|
1057
|
+
const rows = computed(() => entries.value.filter((entry) => membership(entry).includes(category.value) && `${entry.name} ${entry.link?.target ?? ""}`.toLocaleLowerCase().includes(query.value.trim().toLocaleLowerCase()))), active = computed(() => rows.value.find((entry) => entry.id === selected.value) ?? rows.value[0]), heading = computed(() => model.value.categories.find((item) => item.id === category.value)?.label ?? ui.value.labels.allFiles);
|
|
564
1058
|
const nodes = /* @__PURE__ */ new Map();
|
|
1059
|
+
const virtual = computed(() => ready.value && rows.value.length > 500);
|
|
1060
|
+
const offsets = computed(() => {
|
|
1061
|
+
const result = [0];
|
|
1062
|
+
for (const entry of rows.value) result.push(result.at(-1) + (heights.value.get(entry.id) ?? 76));
|
|
1063
|
+
return result;
|
|
1064
|
+
});
|
|
1065
|
+
const start = computed(() => {
|
|
1066
|
+
if (!virtual.value) return 0;
|
|
1067
|
+
const position = Math.max(0, scrollTop.value - (model.value.parentHref ? 76 : 0));
|
|
1068
|
+
let low = 0, high = rows.value.length;
|
|
1069
|
+
while (low < high) {
|
|
1070
|
+
const middle = low + high >> 1;
|
|
1071
|
+
if (offsets.value[middle + 1] <= position) low = middle + 1;
|
|
1072
|
+
else high = middle;
|
|
1073
|
+
}
|
|
1074
|
+
return Math.max(0, low - 5);
|
|
1075
|
+
});
|
|
1076
|
+
const end = computed(() => {
|
|
1077
|
+
if (!virtual.value) return rows.value.length;
|
|
1078
|
+
let low = 0, high = rows.value.length;
|
|
1079
|
+
while (low < high) {
|
|
1080
|
+
const middle = low + high >> 1;
|
|
1081
|
+
if (offsets.value[middle + 1] <= scrollTop.value + viewportHeight.value) low = middle + 1;
|
|
1082
|
+
else high = middle;
|
|
1083
|
+
}
|
|
1084
|
+
return Math.min(rows.value.length, Math.max(start.value + 35, low + 5));
|
|
1085
|
+
});
|
|
1086
|
+
const visibleRows = computed(() => rows.value.slice(start.value, end.value));
|
|
1087
|
+
const heightCache = /* @__PURE__ */ new Map();
|
|
1088
|
+
let rowObserver, layoutObserver, viewHistory, measuredWidth;
|
|
1089
|
+
function measureLayout() {
|
|
1090
|
+
if (!listScroll.value) return;
|
|
1091
|
+
const width = listScroll.value.clientWidth;
|
|
1092
|
+
if (measuredWidth !== void 0 && width !== measuredWidth) {
|
|
1093
|
+
heightCache.clear();
|
|
1094
|
+
heights.value = new Map([...nodes].map(([id, node]) => [id, node.getBoundingClientRect().height]));
|
|
1095
|
+
}
|
|
1096
|
+
measuredWidth = width;
|
|
1097
|
+
const footerHeight = footerElement.value?.getBoundingClientRect().height ?? 82;
|
|
1098
|
+
document.documentElement.style.setProperty("--cw-footer-height", footerHeight + "px");
|
|
1099
|
+
const available = Math.max(160, innerHeight - listScroll.value.getBoundingClientRect().top - footerHeight - 24);
|
|
1100
|
+
viewportHeight.value = available;
|
|
1101
|
+
listScroll.value.style.setProperty("--cw-list-height", available + "px");
|
|
1102
|
+
}
|
|
1103
|
+
watch([virtual, () => model.value.path], () => nextTick(measureLayout));
|
|
1104
|
+
const viewDefaults = (page) => ({
|
|
1105
|
+
query: "",
|
|
1106
|
+
category: page.categories[0]?.id ?? null
|
|
1107
|
+
});
|
|
1108
|
+
function saveView() {
|
|
1109
|
+
if (!viewHistory) return;
|
|
1110
|
+
viewHistory.save(new URL(model.value.dataHref, document.baseURI).href, model.value.path, {
|
|
1111
|
+
query: query.value,
|
|
1112
|
+
category: category.value,
|
|
1113
|
+
selected: selected.value,
|
|
1114
|
+
windowY: window.scrollY,
|
|
1115
|
+
listTop: listScroll.value?.scrollTop ?? 0
|
|
1116
|
+
}, viewDefaults(model.value));
|
|
1117
|
+
}
|
|
1118
|
+
watchEffect((onCleanup) => {
|
|
1119
|
+
if (!ready.value || busy.value) return;
|
|
1120
|
+
const view = {
|
|
1121
|
+
query: query.value,
|
|
1122
|
+
category: category.value,
|
|
1123
|
+
selected: selected.value,
|
|
1124
|
+
listTop: scrollTop.value
|
|
1125
|
+
};
|
|
1126
|
+
const dataHref = model.value.dataHref, path = model.value.path, defaults = viewDefaults(model.value);
|
|
1127
|
+
const frame = requestAnimationFrame(() => viewHistory?.save(new URL(dataHref, document.baseURI).href, path, {
|
|
1128
|
+
...view,
|
|
1129
|
+
windowY: window.scrollY
|
|
1130
|
+
}, defaults));
|
|
1131
|
+
onCleanup(() => cancelAnimationFrame(frame));
|
|
1132
|
+
});
|
|
1133
|
+
watch([query, category], () => {
|
|
1134
|
+
scrollTop.value = 0;
|
|
1135
|
+
if (listScroll.value) listScroll.value.scrollTop = 0;
|
|
1136
|
+
});
|
|
565
1137
|
let wave, gamepad, abort, navigation = 0, timer, lastPad = 0, pressed = /* @__PURE__ */ new Set(), disposed = false, padIdentity = null, motionQuery;
|
|
566
1138
|
function safeHref(entry) {
|
|
567
1139
|
return typeof location !== "undefined" && location.protocol === "file:" && directoryLike(entry) && !entry.exitsExplorer && entry.href?.endsWith("/") ? entry.href + "index.html" : entry.href;
|
|
@@ -577,67 +1149,114 @@ var App_server_default = {
|
|
|
577
1149
|
});
|
|
578
1150
|
if (token === navigation && !local.signal.aborted && !disposed) entries.value = data.entries;
|
|
579
1151
|
} catch (cause) {
|
|
580
|
-
if (token === navigation && !local.signal.aborted)
|
|
1152
|
+
if (token === navigation && !local.signal.aborted) {
|
|
1153
|
+
error.value = ui.value.labels.listingError;
|
|
1154
|
+
failure.value = "listing";
|
|
1155
|
+
}
|
|
581
1156
|
} finally {
|
|
582
1157
|
if (token === navigation) busy.value = false;
|
|
583
1158
|
}
|
|
584
1159
|
}
|
|
585
|
-
async function openFolder(dataHref, url, push, refresh = false) {
|
|
1160
|
+
async function openFolder(dataHref, url, push, refresh = false, restoreState = null, fresh = false) {
|
|
1161
|
+
if (push) saveView();
|
|
586
1162
|
const token = ++navigation;
|
|
587
1163
|
abort?.abort();
|
|
588
1164
|
const local = abort = new AbortController();
|
|
589
1165
|
busy.value = true;
|
|
590
1166
|
error.value = "";
|
|
1167
|
+
failure.value = null;
|
|
591
1168
|
try {
|
|
592
|
-
const next = await fetchThemePage(dataHref, url, "crosswave", local.signal, refresh);
|
|
1169
|
+
const next = await fetchThemePage(dataHref, url, "crosswave", local.signal, refresh || fresh);
|
|
593
1170
|
let nextEntries = next.entries;
|
|
594
1171
|
if (refresh && next.manifestHref && nextEntries.length !== next.total) nextEntries = (await loadListing(next.manifestHref, {
|
|
595
1172
|
signal: local.signal,
|
|
596
1173
|
cache: "no-store"
|
|
597
1174
|
})).entries;
|
|
598
1175
|
if (token !== navigation || local.signal.aborted || disposed) return;
|
|
1176
|
+
if (!refresh) viewHistory.commit(next.dataHref, next.path, url, push);
|
|
1177
|
+
if (next.path !== model.value.path) {
|
|
1178
|
+
heightCache.set(model.value.path, heights.value);
|
|
1179
|
+
if (heightCache.size > 8) heightCache.delete(heightCache.keys().next().value);
|
|
1180
|
+
heights.value = heightCache.get(next.path) ?? /* @__PURE__ */ new Map();
|
|
1181
|
+
}
|
|
599
1182
|
model.value = next;
|
|
1183
|
+
retryRequest.value = null;
|
|
600
1184
|
entries.value = nextEntries;
|
|
601
1185
|
if (!refresh) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1186
|
+
const view = readView(url, "crosswave", next.path, viewDefaults(next), next.categories.map((item) => item.id), restoreState);
|
|
1187
|
+
query.value = view.query;
|
|
1188
|
+
selected.value = view.selected;
|
|
1189
|
+
category.value = view.category;
|
|
605
1190
|
}
|
|
606
|
-
if (push) history.pushState({ dataHref: next.dataHref }, "", url);
|
|
607
1191
|
applyThemeMetadata(next, url);
|
|
608
1192
|
await loadComplete();
|
|
1193
|
+
if (!refresh) {
|
|
1194
|
+
const view = readView(url, "crosswave", next.path, viewDefaults(next), next.categories.map((item) => item.id), restoreState);
|
|
1195
|
+
await nextTick();
|
|
1196
|
+
if (selected.value && !rows.value.some((entry) => entry.id === selected.value)) selected.value = null;
|
|
1197
|
+
scrollTop.value = view.listTop;
|
|
1198
|
+
if (listScroll.value) listScroll.value.scrollTop = view.listTop;
|
|
1199
|
+
await nextTick();
|
|
1200
|
+
window.scrollTo({ top: view.windowY });
|
|
1201
|
+
if (restoreState && selected.value) nodes.get(selected.value)?.querySelector("a")?.focus({ preventScroll: true });
|
|
1202
|
+
}
|
|
609
1203
|
} catch (cause) {
|
|
610
|
-
if (token === navigation && !local.signal.aborted)
|
|
1204
|
+
if (token === navigation && !local.signal.aborted) {
|
|
1205
|
+
error.value = ui.value.labels.folderError;
|
|
1206
|
+
failure.value = "folder";
|
|
1207
|
+
retryRequest.value = [
|
|
1208
|
+
dataHref,
|
|
1209
|
+
url,
|
|
1210
|
+
push,
|
|
1211
|
+
refresh,
|
|
1212
|
+
restoreState
|
|
1213
|
+
];
|
|
1214
|
+
viewHistory?.rollback();
|
|
1215
|
+
}
|
|
611
1216
|
} finally {
|
|
612
1217
|
if (token === navigation) busy.value = false;
|
|
613
1218
|
}
|
|
614
1219
|
}
|
|
615
1220
|
async function move(index) {
|
|
616
|
-
const
|
|
1221
|
+
const target = Math.max(0, Math.min(rows.value.length - 1, index));
|
|
1222
|
+
const entry = rows.value[target];
|
|
617
1223
|
if (!entry) return;
|
|
618
1224
|
selected.value = entry.id;
|
|
1225
|
+
if (virtual.value) {
|
|
1226
|
+
scrollTop.value = Math.max(0, offsets.value[target] - 100 + (model.value.parentHref ? 76 : 0));
|
|
1227
|
+
if (listScroll.value) listScroll.value.scrollTop = scrollTop.value;
|
|
1228
|
+
}
|
|
619
1229
|
await nextTick();
|
|
620
1230
|
const node = nodes.get(entry.id);
|
|
621
1231
|
node?.querySelector("a")?.focus({ preventScroll: true });
|
|
622
1232
|
node?.scrollIntoView({ block: "nearest" });
|
|
623
1233
|
}
|
|
624
|
-
function stepCategory(delta) {
|
|
1234
|
+
async function stepCategory(delta) {
|
|
1235
|
+
if (ui.value.direction === "rtl") delta = -delta;
|
|
1236
|
+
const moveFocus = document.activeElement?.getAttribute("role") === "tab";
|
|
625
1237
|
const items = model.value.categories, index = items.findIndex((item) => item.id === category.value);
|
|
626
1238
|
category.value = items[(index + delta + items.length) % items.length].id;
|
|
627
1239
|
selected.value = null;
|
|
1240
|
+
if (moveFocus) {
|
|
1241
|
+
await nextTick();
|
|
1242
|
+
document.getElementById("cw-tab-" + category.value)?.focus({ preventScroll: true });
|
|
1243
|
+
}
|
|
628
1244
|
}
|
|
629
|
-
function confirm() {
|
|
1245
|
+
async function confirm() {
|
|
630
1246
|
if (!active.value || busy.value) return;
|
|
1247
|
+
await move(rows.value.findIndex((entry) => entry.id === active.value.id));
|
|
631
1248
|
(nodes.get(active.value.id)?.querySelector("a"))?.click();
|
|
632
1249
|
}
|
|
633
1250
|
function parent() {
|
|
634
1251
|
if (model.value.parentHref) {
|
|
635
|
-
if (location.protocol === "file:") location.href = model.value.parentHref;
|
|
1252
|
+
if (location.protocol === "file:" || typeof model.value.parentDataHref !== "string") location.href = model.value.parentHref;
|
|
636
1253
|
else openFolder(model.value.parentDataHref, new URL(model.value.parentHref, document.baseURI).href, true);
|
|
637
1254
|
}
|
|
638
1255
|
}
|
|
639
1256
|
function keyboard(event) {
|
|
1257
|
+
if (archiveOpen.value) return;
|
|
640
1258
|
if (event.isComposing || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
1259
|
+
if (event.target?.closest?.("[data-dirwell-actions]")) return;
|
|
641
1260
|
if (/INPUT|TEXTAREA|SELECT/.test(event.target?.tagName ?? "")) {
|
|
642
1261
|
if (event.key === "Escape") query.value = "";
|
|
643
1262
|
return;
|
|
@@ -666,6 +1285,7 @@ var App_server_default = {
|
|
|
666
1285
|
}
|
|
667
1286
|
}
|
|
668
1287
|
function controller(value) {
|
|
1288
|
+
if (archiveOpen.value) return;
|
|
669
1289
|
pad.value = !!value;
|
|
670
1290
|
if (!value) {
|
|
671
1291
|
pressed.clear();
|
|
@@ -736,7 +1356,7 @@ var App_server_default = {
|
|
|
736
1356
|
.38
|
|
737
1357
|
]
|
|
738
1358
|
};
|
|
739
|
-
const onPop = (event) => event.state?.dataHref ? openFolder(event.state.dataHref, location.href, false) : location.reload();
|
|
1359
|
+
const onPop = (event) => event.state?.dataHref ? openFolder(event.state.dataHref, location.href, false, false, event.state) : location.reload();
|
|
740
1360
|
const suspend = () => {
|
|
741
1361
|
gamepad?.pause();
|
|
742
1362
|
controller(null);
|
|
@@ -757,7 +1377,36 @@ var App_server_default = {
|
|
|
757
1377
|
ready.value = true;
|
|
758
1378
|
document.body.setAttribute("data-cw-enhanced", "");
|
|
759
1379
|
abort = new AbortController();
|
|
760
|
-
|
|
1380
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1381
|
+
rowObserver = new ResizeObserver((observations) => {
|
|
1382
|
+
const next = new Map(heights.value);
|
|
1383
|
+
let changed = false;
|
|
1384
|
+
for (const observation of observations) {
|
|
1385
|
+
const id = observation.target.dataset.id;
|
|
1386
|
+
const height = observation.target.getBoundingClientRect().height;
|
|
1387
|
+
if (height > 0 && next.get(id) !== height) {
|
|
1388
|
+
next.set(id, height);
|
|
1389
|
+
changed = true;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
if (changed) heights.value = next;
|
|
1393
|
+
});
|
|
1394
|
+
for (const node of nodes.values()) rowObserver.observe(node);
|
|
1395
|
+
layoutObserver = new ResizeObserver(measureLayout);
|
|
1396
|
+
if (footerElement.value) layoutObserver.observe(footerElement.value);
|
|
1397
|
+
const header = document.querySelector("[data-dirwell-theme='crosswave'] .cw-top");
|
|
1398
|
+
if (header) layoutObserver.observe(header);
|
|
1399
|
+
}
|
|
1400
|
+
measureLayout();
|
|
1401
|
+
window.addEventListener("resize", measureLayout);
|
|
1402
|
+
viewHistory = createViewHistory("crosswave");
|
|
1403
|
+
const view = readView(location.href, "crosswave", model.value.path, viewDefaults(model.value), model.value.categories.map((item) => item.id), history.state);
|
|
1404
|
+
query.value = view.query;
|
|
1405
|
+
category.value = view.category;
|
|
1406
|
+
selected.value = view.selected;
|
|
1407
|
+
scrollTop.value = view.listTop;
|
|
1408
|
+
if (listScroll.value) listScroll.value.scrollTop = view.listTop;
|
|
1409
|
+
saveView();
|
|
761
1410
|
try {
|
|
762
1411
|
const stored = localStorage.getItem("dirwell-crosswave-color");
|
|
763
1412
|
if (Object.hasOwn(colors, stored)) color.value = stored;
|
|
@@ -785,7 +1434,8 @@ var App_server_default = {
|
|
|
785
1434
|
gamepad.resume();
|
|
786
1435
|
watchEffect(() => controller(gamepad.gamepads.value.find((value) => value.connected && value.mapping === "standard") ?? null), { flush: "sync" });
|
|
787
1436
|
}
|
|
788
|
-
const updateClock = () => clock.value = (/* @__PURE__ */ new Date()).toLocaleTimeString(
|
|
1437
|
+
const updateClock = () => clock.value = (/* @__PURE__ */ new Date()).toLocaleTimeString(ui.value.dateOptions.locale, {
|
|
1438
|
+
timeZone: ui.value.dateOptions.timeZone,
|
|
789
1439
|
hour: "2-digit",
|
|
790
1440
|
minute: "2-digit"
|
|
791
1441
|
});
|
|
@@ -804,9 +1454,15 @@ var App_server_default = {
|
|
|
804
1454
|
if (!href || typeof EventSource === "undefined") return;
|
|
805
1455
|
const updates = new EventSource(href);
|
|
806
1456
|
updates.onmessage = () => pendingUpdates.value = href;
|
|
807
|
-
updates.onerror = () =>
|
|
1457
|
+
updates.onerror = () => {
|
|
1458
|
+
error.value = ui.value.labels.updatesError;
|
|
1459
|
+
failure.value = "updates";
|
|
1460
|
+
};
|
|
808
1461
|
updates.onopen = () => {
|
|
809
|
-
if (
|
|
1462
|
+
if (failure.value === "updates") {
|
|
1463
|
+
error.value = "";
|
|
1464
|
+
failure.value = null;
|
|
1465
|
+
}
|
|
810
1466
|
};
|
|
811
1467
|
onCleanup(() => updates.close());
|
|
812
1468
|
});
|
|
@@ -828,9 +1484,13 @@ var App_server_default = {
|
|
|
828
1484
|
}
|
|
829
1485
|
});
|
|
830
1486
|
onBeforeUnmount(() => {
|
|
1487
|
+
archiveAbort?.abort();
|
|
831
1488
|
disposed = true;
|
|
832
1489
|
document.body.removeAttribute("data-cw-enhanced");
|
|
833
1490
|
abort?.abort();
|
|
1491
|
+
rowObserver?.disconnect();
|
|
1492
|
+
layoutObserver?.disconnect();
|
|
1493
|
+
window.removeEventListener("resize", measureLayout);
|
|
834
1494
|
gamepad?.pause();
|
|
835
1495
|
wave?.dispose();
|
|
836
1496
|
clearInterval(timer);
|
|
@@ -844,25 +1504,32 @@ var App_server_default = {
|
|
|
844
1504
|
return (_ctx, _push, _parent, _attrs) => {
|
|
845
1505
|
_push(`<!--[--><div class="cw-background"${ssrRenderAttr("data-ready", waveReady.value ? true : void 0)}><canvas aria-hidden="true"></canvas><svg class="cw-fallback-wave" aria-hidden="true" viewBox="0 0 1200 600"><path d="M-100 400 Q400 150 1300 340"></path></svg></div><header class="cw-top"><div class="cw-brand">`);
|
|
846
1506
|
ssrRenderSlot(_ctx.$slots, "header", { model: model.value }, () => {
|
|
847
|
-
_push(`<
|
|
1507
|
+
if (ui.value.brand.logoUrl) _push(`<img${ssrRenderAttr("src", ui.value.brand.logoUrl)}${ssrRenderAttr("alt", ui.value.brand.logoAlt ?? "")} width="40" height="40">`);
|
|
1508
|
+
else _push(`<!---->`);
|
|
1509
|
+
_push(`<a${ssrRenderAttr("href", model.value.rootHref)}>${ssrInterpolate(ui.value.brand.name ?? model.value.title)}</a>`);
|
|
848
1510
|
}, _push, _parent);
|
|
849
|
-
_push(`</div><nav class="cw-location"
|
|
1511
|
+
_push(`</div><nav class="cw-location"${ssrRenderAttr("aria-label", ui.value.labels.breadcrumb)}><a${ssrRenderAttr("href", model.value.rootHref)}>${ssrInterpolate(ui.value.labels.home)}</a><!--[-->`);
|
|
850
1512
|
ssrRenderList(model.value.breadcrumbs, (crumb) => {
|
|
851
1513
|
_push(`<!--[--> / <a${ssrRenderAttr("href", crumb.href)}>${ssrInterpolate(crumb.name)}</a><!--]-->`);
|
|
852
1514
|
});
|
|
853
1515
|
_push(`<!--]--></nav>`);
|
|
854
1516
|
if (ready.value) _push(`<div class="cw-clock">${ssrInterpolate(clock.value)}</div>`);
|
|
855
1517
|
else _push(`<!---->`);
|
|
856
|
-
_push(`</header><main class="cw-stage" data-framework="vue"${ssrRenderAttr("aria-busy", busy.value)}><h1 class="cw-sr"
|
|
1518
|
+
_push(`</header><main class="cw-stage" data-framework="vue"${ssrRenderAttr("aria-busy", busy.value)}><h1 class="cw-sr">${ssrInterpolate(unref(label)(ui.value, "filesIn", { path: model.value.path || "/" }))}</h1><nav class="cw-rail"${ssrRenderAttr("aria-label", ui.value.labels.categories)}${ssrRenderDynamicAttr("hidden", !ready.value)}><div class="cw-categories" role="tablist"><!--[-->`);
|
|
857
1519
|
ssrRenderList(model.value.categories, (item) => {
|
|
858
1520
|
_push(`<button role="tab"${ssrRenderAttr("id", "cw-tab-" + item.id)} aria-controls="cw-panel"${ssrRenderAttr("aria-selected", category.value === item.id)}${ssrRenderAttr("tabindex", category.value === item.id ? 0 : -1)} class="${ssrRenderClass(["cw-category", { "cw-active": category.value === item.id }])}"${ssrIncludeBooleanAttr(!complete.value) ? " disabled" : ""}><span>${item.svg ?? ""}</span><span>${ssrInterpolate(item.label)}</span><small>${ssrInterpolate(entries.value.filter((entry) => membership(entry).includes(item.id)).length)}</small></button>`);
|
|
859
1521
|
});
|
|
860
|
-
_push(`<!--]--></div></nav><section class="cw-browser" id="cw-panel" role="tabpanel"${ssrRenderAttr("aria-labelledby", "cw-tab-" + category.value)}><div class="cw-list-head"><div class="cw-directory"><h2>${ssrInterpolate(heading.value)}</h2><span data-cw-count>${ssrInterpolate(complete.value ? rows.value.length : entries.value.length)}
|
|
1522
|
+
_push(`<!--]--></div></nav><section class="cw-browser" id="cw-panel" role="tabpanel"${ssrRenderAttr("aria-labelledby", "cw-tab-" + category.value)}><div class="cw-list-head"><div class="cw-directory"><h2>${ssrInterpolate(heading.value)}</h2><span data-cw-count>${ssrInterpolate(unref(label)(ui.value, "items", { count: complete.value ? rows.value.length : entries.value.length }))}</span></div><label class="cw-search"><span>${ssrInterpolate(ui.value.labels.search)}</span><input type="search"${ssrRenderAttr("value", query.value)}${ssrIncludeBooleanAttr(!ready.value || !complete.value) ? " disabled" : ""}></label>`);
|
|
1523
|
+
if (model.value.searchQueryHref) _push(`<button class="cw-archive-trigger"${ssrIncludeBooleanAttr(!ready.value) ? " disabled" : ""}>${ssrInterpolate(ui.value.labels.searchArchive)}</button>`);
|
|
1524
|
+
else _push(`<!---->`);
|
|
1525
|
+
_push(`</div><div class="cw-list-scroll"${ssrRenderAttr("data-virtual", virtual.value)}><ul class="cw-files">`);
|
|
861
1526
|
if (model.value.parentHref) _push(`<li class="cw-row"><a${ssrRenderAttr("href", model.value.parentHref)} class="cw-entry">../</a></li>`);
|
|
862
1527
|
else _push(`<!---->`);
|
|
1528
|
+
if (start.value) _push(`<li aria-hidden="true" style="${ssrRenderStyle({ height: offsets.value[start.value] + "px" })}"></li>`);
|
|
1529
|
+
else _push(`<!---->`);
|
|
863
1530
|
_push(`<!--[-->`);
|
|
864
|
-
ssrRenderList(
|
|
865
|
-
_push(`<li class="${ssrRenderClass(["cw-row", { "cw-selected": active.value?.id === entry.id }])}" data-cw-entry${ssrRenderAttr("data-id", entry.id)}>`);
|
|
1531
|
+
ssrRenderList(visibleRows.value, (entry, visibleIndex) => {
|
|
1532
|
+
_push(`<li${ssrRenderAttr("aria-posinset", start.value + visibleIndex + 1)}${ssrRenderAttr("aria-setsize", rows.value.length)} class="${ssrRenderClass(["cw-row", { "cw-selected": active.value?.id === entry.id }])}" data-cw-entry${ssrRenderAttr("data-custom-columns", Boolean(model.value.ui?.columns))}${ssrRenderAttr("data-id", entry.id)}>`);
|
|
866
1533
|
if (entry.href) {
|
|
867
1534
|
_push(`<a class="cw-entry"${ssrRenderAttr("href", safeHref(entry))}${ssrRenderAttr("target", entry.exitsExplorer ? "_blank" : void 0)}${ssrRenderAttr("rel", entry.exitsExplorer ? "noopener" : void 0)}>`);
|
|
868
1535
|
ssrRenderSlot(_ctx.$slots, "row", {
|
|
@@ -870,10 +1537,22 @@ var App_server_default = {
|
|
|
870
1537
|
entry,
|
|
871
1538
|
selected: selected.value === entry.id
|
|
872
1539
|
}, () => {
|
|
873
|
-
_push(`<span class="cw-entry-icon" aria-hidden="true">${unref(entryIcon)(entry.kind === "symlink" ? "link" : unref(directoryLike)(entry) ? "directory" : "file") ?? ""}</span><span class="cw-entry-label"
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1540
|
+
_push(`<span class="cw-entry-icon" aria-hidden="true">${unref(entryIcon)(entry.kind === "symlink" ? "link" : unref(directoryLike)(entry) ? "directory" : "file") ?? ""}</span><span class="cw-entry-label"><!--[-->`);
|
|
1541
|
+
ssrRenderList(ui.value.columns, (column) => {
|
|
1542
|
+
_push(`<!--[-->`);
|
|
1543
|
+
if (column === "name") _push(`<span class="cw-name">${ssrInterpolate(entry.name)}${ssrInterpolate(unref(directoryLike)(entry) ? "/" : "")}</span>`);
|
|
1544
|
+
else _push(`<!---->`);
|
|
1545
|
+
if (entry.link && column === "name") _push(`<span class="cw-target">${ssrInterpolate(entry.link.target)}</span>`);
|
|
1546
|
+
else _push(`<!---->`);
|
|
1547
|
+
if (column === "size") _push(`<span class="cw-row-meta">${ssrInterpolate(entry.link?.unavailable ? ui.value.labels.unavailable : ui.value.labels[entry.kind === "symlink" ? "link" : entry.kind])} · ${ssrInterpolate(unref(directoryLike)(entry) ? ui.value.labels.folder : unref(size)(entry.size))}</span>`);
|
|
1548
|
+
else _push(`<!---->`);
|
|
1549
|
+
if (column === "modified") _push(`<span class="cw-row-date">${ssrInterpolate(ui.value.labels.modified)} ${ssrInterpolate(unref(formattedDate)(entry.modifiedAt, ui.value, ready.value))}</span>`);
|
|
1550
|
+
else _push(`<!---->`);
|
|
1551
|
+
if (typeof column === "object") _push(`<span class="cw-data-value">${ssrInterpolate(column.label)}: ${ssrInterpolate(unref(publicDataValue)(entry.data?.[column.dataKey]))}</span>`);
|
|
1552
|
+
else _push(`<!---->`);
|
|
1553
|
+
_push(`<!--]-->`);
|
|
1554
|
+
});
|
|
1555
|
+
_push(`<!--]--></span>`);
|
|
877
1556
|
}, _push, _parent);
|
|
878
1557
|
_push(`</a>`);
|
|
879
1558
|
} else {
|
|
@@ -887,40 +1566,90 @@ var App_server_default = {
|
|
|
887
1566
|
}, _push, _parent);
|
|
888
1567
|
_push(`</span>`);
|
|
889
1568
|
}
|
|
1569
|
+
if (ui.value.actions.length || _ctx.$slots.actions) {
|
|
1570
|
+
_push(`<span data-dirwell-actions class="cw-row-actions">`);
|
|
1571
|
+
ssrRenderSlot(_ctx.$slots, "actions", {
|
|
1572
|
+
model: model.value,
|
|
1573
|
+
entry,
|
|
1574
|
+
selected: selected.value === entry.id
|
|
1575
|
+
}, null, _push, _parent);
|
|
1576
|
+
_push(`<!--[-->`);
|
|
1577
|
+
ssrRenderList(ui.value.actions, (action) => {
|
|
1578
|
+
_push(`<!--[-->`);
|
|
1579
|
+
if (action === "download" && entry.href && !unref(directoryLike)(entry) && !entry.link?.unavailable) _push(`<a${ssrRenderAttr("href", safeHref(entry))} download>${ssrInterpolate(ui.value.labels.download)}</a>`);
|
|
1580
|
+
else if (action !== "download") _push(`<button${ssrIncludeBooleanAttr(!ready.value || action === "copy-link" && !entry.href) ? " disabled" : ""}${ssrRenderAttr("aria-label", (action === "copy-link" ? ui.value.labels.copyLink : ui.value.labels.copyPath) + ": " + entry.name)}>${ssrInterpolate(action === "copy-link" ? ui.value.labels.copyLink : ui.value.labels.copyPath)}</button>`);
|
|
1581
|
+
else _push(`<!---->`);
|
|
1582
|
+
_push(`<!--]-->`);
|
|
1583
|
+
});
|
|
1584
|
+
_push(`<!--]--></span>`);
|
|
1585
|
+
} else _push(`<!---->`);
|
|
890
1586
|
_push(`</li>`);
|
|
891
1587
|
});
|
|
892
|
-
_push(`<!--]
|
|
893
|
-
if (
|
|
1588
|
+
_push(`<!--]-->`);
|
|
1589
|
+
if (end.value < rows.value.length) _push(`<li aria-hidden="true" style="${ssrRenderStyle({ height: offsets.value[rows.value.length] - offsets.value[end.value] + "px" })}"></li>`);
|
|
1590
|
+
else _push(`<!---->`);
|
|
1591
|
+
_push(`</ul>`);
|
|
1592
|
+
if (!rows.value.length && !busy.value) _push(`<p class="cw-empty">${ssrInterpolate(model.value.total === 0 ? ui.value.labels.empty : ui.value.labels.noMatches)}</p>`);
|
|
894
1593
|
else _push(`<!---->`);
|
|
895
1594
|
_push(`</div></section>`);
|
|
896
1595
|
if (ready.value && active.value) {
|
|
897
|
-
_push(`<aside class="cw-detail"><h2>${ssrInterpolate(active.value.name)}</h2
|
|
1596
|
+
_push(`<aside class="cw-detail"><h2>${ssrInterpolate(active.value.name)}</h2>`);
|
|
1597
|
+
if (ui.value.columns.includes("size")) _push(`<p>${ssrInterpolate(ui.value.labels[active.value.kind === "symlink" ? "link" : active.value.kind])} · ${ssrInterpolate(unref(directoryLike)(active.value) ? ui.value.labels.folder : unref(size)(active.value.size))}</p>`);
|
|
1598
|
+
else _push(`<!---->`);
|
|
1599
|
+
if (ui.value.columns.includes("modified")) _push(`<p>${ssrInterpolate(unref(formattedDate)(active.value.modifiedAt, ui.value, ready.value))}</p>`);
|
|
1600
|
+
else _push(`<!---->`);
|
|
898
1601
|
if (active.value.link) _push(`<p>${ssrInterpolate(active.value.link.target)}</p>`);
|
|
899
1602
|
else _push(`<!---->`);
|
|
900
1603
|
_push(`</aside>`);
|
|
901
1604
|
} else _push(`<!---->`);
|
|
902
|
-
_push(`<p role="status" aria-live="polite">${ssrInterpolate(busy.value ?
|
|
1605
|
+
_push(`<p role="status" aria-live="polite">${ssrInterpolate(busy.value ? ui.value.labels.loading : error.value)}</p>`);
|
|
1606
|
+
if (failure.value && failure.value !== "copy") _push(`<button${ssrIncludeBooleanAttr(busy.value) ? " disabled" : ""}>${ssrInterpolate(ui.value.labels.retry)}</button>`);
|
|
1607
|
+
else _push(`<!---->`);
|
|
1608
|
+
if (failure.value === "folder" && retryRequest.value) _push(`<a${ssrRenderAttr("href", retryRequest.value[1])}>${ssrInterpolate(ui.value.labels.openNative)}</a>`);
|
|
1609
|
+
else _push(`<!---->`);
|
|
903
1610
|
if (model.value.page?.count > 1 && !complete.value) {
|
|
904
|
-
_push(`<nav
|
|
905
|
-
if (model.value.page.previousHref) _push(`<a${ssrRenderAttr("href", model.value.page.previousHref)} rel="prev"
|
|
1611
|
+
_push(`<nav${ssrRenderAttr("aria-label", ui.value.labels.pagination)}>`);
|
|
1612
|
+
if (model.value.page.previousHref) _push(`<a${ssrRenderAttr("href", model.value.page.previousHref)} rel="prev">${ssrInterpolate(ui.value.labels.previous)}</a>`);
|
|
906
1613
|
else _push(`<!---->`);
|
|
907
|
-
_push(`
|
|
908
|
-
|
|
1614
|
+
_push(` ${ssrInterpolate(unref(label)(ui.value, "page", {
|
|
1615
|
+
page: model.value.page.number,
|
|
1616
|
+
pages: model.value.page.count
|
|
1617
|
+
}))} `);
|
|
1618
|
+
if (model.value.page.nextHref) _push(`<a${ssrRenderAttr("href", model.value.page.nextHref)} rel="next">${ssrInterpolate(ui.value.labels.next)}</a>`);
|
|
909
1619
|
else _push(`<!---->`);
|
|
910
1620
|
_push(`</nav>`);
|
|
911
1621
|
} else _push(`<!---->`);
|
|
912
|
-
_push(`</main><footer class="cw-bottom"><div class="cw-key-help">`);
|
|
1622
|
+
_push(`</main><footer class="cw-bottom"><div class="${ssrRenderClass(ui.value.brand.footerText || _ctx.$slots.footer ? "cw-custom-footer" : "cw-key-help")}">`);
|
|
913
1623
|
ssrRenderSlot(_ctx.$slots, "footer", { model: model.value }, () => {
|
|
914
|
-
|
|
1624
|
+
if (ui.value.brand.footerText) {
|
|
1625
|
+
_push(`<!--[-->`);
|
|
1626
|
+
if (ui.value.brand.footerUrl) _push(`<a${ssrRenderAttr("href", ui.value.brand.footerUrl)}>${ssrInterpolate(ui.value.brand.footerText)}</a>`);
|
|
1627
|
+
else _push(`<span>${ssrInterpolate(ui.value.brand.footerText)}</span>`);
|
|
1628
|
+
_push(`<!--]-->`);
|
|
1629
|
+
} else _push(`<!--[-->${ssrInterpolate(ui.value.labels.crosswaveKeyboardHelp)}<!--]-->`);
|
|
915
1630
|
}, _push, _parent);
|
|
916
1631
|
_push(`</div>`);
|
|
917
|
-
if (pad.value) _push(`<div class="cw-controller"
|
|
1632
|
+
if (pad.value) _push(`<div class="cw-controller">${ssrInterpolate(ui.value.labels.controller)}</div>`);
|
|
918
1633
|
else _push(`<!---->`);
|
|
919
|
-
_push(`<label class="cw-appearance"
|
|
1634
|
+
_push(`<label class="cw-appearance">${ssrInterpolate(ui.value.labels.color)}<select${ssrIncludeBooleanAttr(!ready.value) ? " disabled" : ""}><!--[-->`);
|
|
920
1635
|
ssrRenderList(Object.keys(colors), (name) => {
|
|
921
|
-
_push(`<option${ssrIncludeBooleanAttr(Array.isArray(color.value) ? ssrLooseContain(color.value,
|
|
1636
|
+
_push(`<option${ssrRenderAttr("value", name)}${ssrIncludeBooleanAttr(Array.isArray(color.value) ? ssrLooseContain(color.value, name) : ssrLooseEqual(color.value, name)) ? " selected" : ""}>${ssrInterpolate(ui.value.labels[name])}</option>`);
|
|
922
1637
|
});
|
|
923
|
-
_push(`<!--]--></select></label><button class="cw-motion"${ssrIncludeBooleanAttr(!ready.value) ? " disabled" : ""}>${ssrInterpolate(paused.value ?
|
|
1638
|
+
_push(`<!--]--></select></label><button class="cw-motion"${ssrIncludeBooleanAttr(!ready.value) ? " disabled" : ""}>${ssrInterpolate(paused.value ? ui.value.labels.resumeMotion : ui.value.labels.pauseMotion)}</button></footer>`);
|
|
1639
|
+
if (archiveOpen.value) {
|
|
1640
|
+
_push(`<dialog class="cw-archive"${ssrRenderAttr("aria-label", ui.value.labels.searchArchive)}><h2>${ssrInterpolate(ui.value.labels.searchArchive)}</h2><input type="search"${ssrRenderAttr("value", archiveQuery.value)}${ssrRenderAttr("aria-label", ui.value.labels.archiveSearchInput)}><p role="status">${ssrInterpolate(archiveStatus.value)}</p>`);
|
|
1641
|
+
if (archiveFailed.value) _push(`<button>${ssrInterpolate(ui.value.labels.retry)}</button>`);
|
|
1642
|
+
else _push(`<!---->`);
|
|
1643
|
+
_push(`<ul><!--[-->`);
|
|
1644
|
+
ssrRenderList(archiveRows.value, (record) => {
|
|
1645
|
+
_push(`<li>`);
|
|
1646
|
+
if (record.href) _push(`<a${ssrRenderAttr("href", record.href)}${ssrRenderAttr("target", record.exitsExplorer ? "_blank" : void 0)}${ssrRenderAttr("rel", record.exitsExplorer ? "noopener" : void 0)}>${ssrInterpolate(record.path)}</a>`);
|
|
1647
|
+
else _push(`<span>${ssrInterpolate(record.path)}</span>`);
|
|
1648
|
+
_push(`</li>`);
|
|
1649
|
+
});
|
|
1650
|
+
_push(`<!--]--></ul><button>${ssrInterpolate(ui.value.labels.close)}</button></dialog>`);
|
|
1651
|
+
} else _push(`<!---->`);
|
|
1652
|
+
_push(`<!--]-->`);
|
|
924
1653
|
};
|
|
925
1654
|
}
|
|
926
1655
|
};
|
|
@@ -930,7 +1659,43 @@ const renderBody = (model) => renderToString(createSSRApp(App_server_default, {
|
|
|
930
1659
|
//#endregion
|
|
931
1660
|
//#region src/index.ts
|
|
932
1661
|
function createCrosswaveTheme(options = {}, renderer) {
|
|
933
|
-
|
|
1662
|
+
assertThemeOptions(options, [
|
|
1663
|
+
"title",
|
|
1664
|
+
"categories",
|
|
1665
|
+
"color",
|
|
1666
|
+
"gamepad",
|
|
1667
|
+
"backgroundMotion",
|
|
1668
|
+
"archiveSearch"
|
|
1669
|
+
]);
|
|
1670
|
+
if (options.title !== void 0 && (typeof options.title !== "string" || !options.title)) throw new TypeError("title must be a non-empty string");
|
|
1671
|
+
if (options.color !== void 0 && ![
|
|
1672
|
+
"azure",
|
|
1673
|
+
"violet",
|
|
1674
|
+
"amber",
|
|
1675
|
+
"rose",
|
|
1676
|
+
"jade",
|
|
1677
|
+
"graphite"
|
|
1678
|
+
].includes(options.color)) throw new TypeError("Unsupported Crosswave color");
|
|
1679
|
+
for (const key of [
|
|
1680
|
+
"gamepad",
|
|
1681
|
+
"backgroundMotion",
|
|
1682
|
+
"archiveSearch"
|
|
1683
|
+
]) if (options[key] !== void 0 && typeof options[key] !== "boolean") throw new TypeError(`${key} must be a boolean`);
|
|
1684
|
+
const ui = resolveCustomization(options);
|
|
1685
|
+
const publishedUi = pickCustomization(options);
|
|
1686
|
+
const categoryLabels = {
|
|
1687
|
+
all: ui.labels.allFiles,
|
|
1688
|
+
folder: ui.labels.categoryFolders,
|
|
1689
|
+
image: ui.labels.categoryPhotos,
|
|
1690
|
+
audio: ui.labels.categoryMusic,
|
|
1691
|
+
video: ui.labels.categoryVideos,
|
|
1692
|
+
document: ui.labels.categoryDocuments,
|
|
1693
|
+
other: ui.labels.categoryOther
|
|
1694
|
+
};
|
|
1695
|
+
const categories = resolveCrosswaveCategories(options.categories ?? defaultCrosswaveCategories.map((category) => ({
|
|
1696
|
+
...category,
|
|
1697
|
+
label: categoryLabels[category.id] ?? category.label
|
|
1698
|
+
})));
|
|
934
1699
|
let resources;
|
|
935
1700
|
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]) => ({
|
|
936
1701
|
script,
|
|
@@ -948,7 +1713,9 @@ function createCrosswaveTheme(options = {}, renderer) {
|
|
|
948
1713
|
}),
|
|
949
1714
|
async render(context) {
|
|
950
1715
|
const model = {
|
|
951
|
-
...createThemePage(context, theme, options.title ?? "Dirwell"),
|
|
1716
|
+
...createThemePage(context, theme, options.title ?? ui.brand.name ?? "Dirwell"),
|
|
1717
|
+
...publishedUi === void 0 ? {} : { ui: publishedUi },
|
|
1718
|
+
searchQueryHref: options.archiveSearch === false ? null : context.searchQueryHref ?? null,
|
|
952
1719
|
categories: categories.map(({ id, label, svg }) => ({
|
|
953
1720
|
id,
|
|
954
1721
|
label,
|
|
@@ -961,7 +1728,7 @@ function createCrosswaveTheme(options = {}, renderer) {
|
|
|
961
1728
|
const { script, scriptName, styles } = await loadResources();
|
|
962
1729
|
const json = JSON.stringify(model).replaceAll("<", "\\u003c");
|
|
963
1730
|
return {
|
|
964
|
-
html: `<!doctype html><html lang="
|
|
1731
|
+
html: `<!doctype html><html lang="${escapeHtml(ui.language)}" dir="${ui.direction}" data-cw-color="${escapeHtml(model.color)}" data-cw-motion="${model.backgroundMotion}"><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="crosswave">${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>`,
|
|
965
1732
|
assets: {
|
|
966
1733
|
[scriptName]: script,
|
|
967
1734
|
[pageAsset("crosswave", model.path, model.page?.number ?? 1)]: JSON.stringify(model)
|