@dirwell/theme-crosswave 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 +19 -19
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +709 -61
- package/dist/index.mjs.map +1 -1
- package/dist/model-VZ01cfyg.d.mts +172 -0
- package/dist/model.d.mts +1 -1
- package/dist/source/Crosswave.vue +405 -57
- 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 -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",
|
|
@@ -255,6 +255,247 @@ function entryCategories(entry, type, categories) {
|
|
|
255
255
|
return categories.filter((category) => category.match === "all" || folder && category.match === "folders" || !folder && category.match === "other" && !specific.length || specific.includes(category));
|
|
256
256
|
}
|
|
257
257
|
//#endregion
|
|
258
|
+
//#region ../shared-theme/src/customization.ts
|
|
259
|
+
/** Small serializable inputs shared by official themes; layout stays theme-owned. */
|
|
260
|
+
const defaultLabels = {
|
|
261
|
+
other: "other",
|
|
262
|
+
crosswaveKeyboardHelp: "Arrow keys navigate · Enter confirms · Backspace returns",
|
|
263
|
+
brokenLink: "broken link",
|
|
264
|
+
unavailableLink: "unavailable link",
|
|
265
|
+
cycleLink: "cycle link",
|
|
266
|
+
azure: "azure",
|
|
267
|
+
violet: "violet",
|
|
268
|
+
amber: "amber",
|
|
269
|
+
rose: "rose",
|
|
270
|
+
jade: "jade",
|
|
271
|
+
graphite: "graphite",
|
|
272
|
+
allFiles: "All files",
|
|
273
|
+
categoryFolders: "Folders",
|
|
274
|
+
categoryPhotos: "Photos",
|
|
275
|
+
categoryMusic: "Music",
|
|
276
|
+
categoryVideos: "Videos",
|
|
277
|
+
categoryDocuments: "Documents",
|
|
278
|
+
categoryOther: "Other",
|
|
279
|
+
indexOf: "Index of {path}",
|
|
280
|
+
home: "Home",
|
|
281
|
+
breadcrumb: "Breadcrumb",
|
|
282
|
+
archive: "File archive",
|
|
283
|
+
searchDirectory: "Search this directory",
|
|
284
|
+
searchFiles: "Search files",
|
|
285
|
+
search: "Search",
|
|
286
|
+
types: "Types",
|
|
287
|
+
directory: "directory",
|
|
288
|
+
file: "file",
|
|
289
|
+
link: "link",
|
|
290
|
+
folder: "Folder",
|
|
291
|
+
sort: "Sort",
|
|
292
|
+
name: "Name",
|
|
293
|
+
size: "Size",
|
|
294
|
+
modified: "Modified",
|
|
295
|
+
names: "Names",
|
|
296
|
+
natural: "Natural",
|
|
297
|
+
locale: "Locale",
|
|
298
|
+
unicode: "Unicode",
|
|
299
|
+
order: "Order",
|
|
300
|
+
ascending: "Ascending",
|
|
301
|
+
descending: "Descending",
|
|
302
|
+
directoriesFirst: "Folders first",
|
|
303
|
+
appearance: "Appearance",
|
|
304
|
+
system: "System",
|
|
305
|
+
light: "Light",
|
|
306
|
+
dark: "Dark",
|
|
307
|
+
searchArchive: "Search archive",
|
|
308
|
+
archiveSearchInput: "Search archive files",
|
|
309
|
+
entries: "{count} entries",
|
|
310
|
+
pageEntries: "{total} entries · {count} on this page",
|
|
311
|
+
loading: "Loading…",
|
|
312
|
+
noMatches: "No matching entries.",
|
|
313
|
+
empty: "This directory is empty.",
|
|
314
|
+
pagination: "Pagination",
|
|
315
|
+
previous: "Previous",
|
|
316
|
+
next: "Next",
|
|
317
|
+
page: "Page {page} of {pages}",
|
|
318
|
+
keyboardHelp: "Arrow keys select files · Home / End jump · / searches · Backspace opens parent",
|
|
319
|
+
modifiedTime: "Modified time",
|
|
320
|
+
close: "Close",
|
|
321
|
+
unknown: "Unknown",
|
|
322
|
+
unavailable: "Target unavailable",
|
|
323
|
+
beginArchive: "Enter a search to begin.",
|
|
324
|
+
searchingArchive: "Searching archive…",
|
|
325
|
+
archiveResults: "{count} results",
|
|
326
|
+
truncatedResults: "Showing {shown} of {count} results",
|
|
327
|
+
archiveError: "Could not load archive search. Try again.",
|
|
328
|
+
listingError: "Could not load complete directory data. Native page links remain available.",
|
|
329
|
+
folderError: "Could not open the folder. Its native link remains available.",
|
|
330
|
+
updatesError: "Live updates disconnected. Refresh to retry.",
|
|
331
|
+
retry: "Retry",
|
|
332
|
+
openNative: "Open folder directly",
|
|
333
|
+
categories: "File categories",
|
|
334
|
+
items: "{count} items",
|
|
335
|
+
filesIn: "Files in {path}",
|
|
336
|
+
controller: "Controller · Confirm / Back",
|
|
337
|
+
color: "Color",
|
|
338
|
+
pauseMotion: "Pause motion",
|
|
339
|
+
resumeMotion: "Resume motion",
|
|
340
|
+
copyLink: "Copy link",
|
|
341
|
+
copyPath: "Copy path",
|
|
342
|
+
download: "Download",
|
|
343
|
+
copied: "Copied.",
|
|
344
|
+
copyError: "Could not copy. Use the native link or select the path.",
|
|
345
|
+
repository: "Repository",
|
|
346
|
+
author: "by",
|
|
347
|
+
target: "Target",
|
|
348
|
+
newTab: "opens in a new tab"
|
|
349
|
+
};
|
|
350
|
+
function resolveCustomization(options = {}) {
|
|
351
|
+
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;
|
|
352
|
+
for (const [key, value] of Object.entries({
|
|
353
|
+
language,
|
|
354
|
+
locale,
|
|
355
|
+
timeZone
|
|
356
|
+
})) if (typeof value !== "string" || !value) throw new TypeError(`${key} must be a non-empty string`);
|
|
357
|
+
new Intl.DateTimeFormat(locale, { timeZone });
|
|
358
|
+
const direction = options.direction ?? "ltr";
|
|
359
|
+
if (direction !== "ltr" && direction !== "rtl") throw new TypeError("direction must be ltr or rtl");
|
|
360
|
+
const labels = { ...defaultLabels };
|
|
361
|
+
if (options.labels !== void 0) {
|
|
362
|
+
if (typeof options.labels !== "object" || options.labels === null || Array.isArray(options.labels)) throw new TypeError("labels must be an object");
|
|
363
|
+
for (const [key, value] of Object.entries(options.labels)) {
|
|
364
|
+
if (!Object.hasOwn(labels, key) || typeof value !== "string") throw new TypeError(`Unsupported label or value: ${key}`);
|
|
365
|
+
Object.assign(labels, { [key]: value });
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const brand = options.brand ?? {};
|
|
369
|
+
if (typeof brand !== "object" || brand === null || Array.isArray(brand)) throw new TypeError("brand must be an object");
|
|
370
|
+
for (const [key, value] of Object.entries(brand)) {
|
|
371
|
+
if (![
|
|
372
|
+
"name",
|
|
373
|
+
"logoUrl",
|
|
374
|
+
"logoAlt",
|
|
375
|
+
"footerText",
|
|
376
|
+
"footerUrl",
|
|
377
|
+
"faviconUrl"
|
|
378
|
+
].includes(key) || typeof value !== "string") throw new TypeError(`Unsupported brand option or value: ${key}`);
|
|
379
|
+
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`);
|
|
380
|
+
}
|
|
381
|
+
const columns = options.columns ?? [
|
|
382
|
+
"name",
|
|
383
|
+
"size",
|
|
384
|
+
"modified"
|
|
385
|
+
];
|
|
386
|
+
if (!Array.isArray(columns) || !columns.includes("name")) throw new TypeError("columns must contain name and unique supported columns");
|
|
387
|
+
const columnIds = /* @__PURE__ */ new Set();
|
|
388
|
+
for (const column of columns) {
|
|
389
|
+
let id;
|
|
390
|
+
if (typeof column === "string") {
|
|
391
|
+
if (![
|
|
392
|
+
"name",
|
|
393
|
+
"size",
|
|
394
|
+
"modified"
|
|
395
|
+
].includes(column)) throw new TypeError("Unsupported column");
|
|
396
|
+
id = column;
|
|
397
|
+
} else {
|
|
398
|
+
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");
|
|
399
|
+
id = "data:" + column.dataKey;
|
|
400
|
+
}
|
|
401
|
+
if (columnIds.has(id)) throw new TypeError("columns must contain unique supported columns");
|
|
402
|
+
columnIds.add(id);
|
|
403
|
+
}
|
|
404
|
+
const actions = options.actions ?? [];
|
|
405
|
+
if (!Array.isArray(actions) || new Set(actions).size !== actions.length || actions.some((key) => ![
|
|
406
|
+
"copy-link",
|
|
407
|
+
"copy-path",
|
|
408
|
+
"download"
|
|
409
|
+
].includes(key))) throw new TypeError("actions must contain unique supported actions");
|
|
410
|
+
const cssVariables = options.cssVariables ?? {};
|
|
411
|
+
if (typeof cssVariables !== "object" || cssVariables === null || Array.isArray(cssVariables)) throw new TypeError("cssVariables must be an object");
|
|
412
|
+
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}`);
|
|
413
|
+
return {
|
|
414
|
+
language,
|
|
415
|
+
dateOptions: {
|
|
416
|
+
...options.locale === void 0 ? {} : { locale: options.locale },
|
|
417
|
+
...options.timeZone === void 0 ? {} : { timeZone: options.timeZone }
|
|
418
|
+
},
|
|
419
|
+
direction,
|
|
420
|
+
locale,
|
|
421
|
+
timeZone,
|
|
422
|
+
labels,
|
|
423
|
+
brand: { ...brand },
|
|
424
|
+
columns: columns.map((column) => typeof column === "string" ? column : { ...column }),
|
|
425
|
+
actions: [...actions],
|
|
426
|
+
cssVariables: { ...cssVariables }
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
resolveCustomization();
|
|
430
|
+
function label(ui, key, values = {}) {
|
|
431
|
+
return ui.labels[key].replace(/\{([a-zA-Z]+)\}/g, (match, name) => Object.hasOwn(values, name) ? String(values[name]) : match);
|
|
432
|
+
}
|
|
433
|
+
const dateFormats = /* @__PURE__ */ new Map();
|
|
434
|
+
function formattedDate(value, ui, local = false) {
|
|
435
|
+
const date = new Date(value);
|
|
436
|
+
if (Number.isNaN(date.getTime())) return ui.labels.unknown;
|
|
437
|
+
if (ui.dateOptions.locale === void 0 && ui.dateOptions.timeZone === void 0) return local ? date.toLocaleString(void 0, {
|
|
438
|
+
dateStyle: "short",
|
|
439
|
+
timeStyle: "short"
|
|
440
|
+
}) : date.toISOString().slice(0, 16).replace("T", " ") + " UTC";
|
|
441
|
+
const locale = ui.dateOptions.locale, timeZone = ui.dateOptions.timeZone ?? (local ? void 0 : "UTC");
|
|
442
|
+
const key = String(locale) + "\0" + String(timeZone);
|
|
443
|
+
let formatter = dateFormats.get(key);
|
|
444
|
+
if (formatter === void 0) {
|
|
445
|
+
formatter = new Intl.DateTimeFormat(locale, {
|
|
446
|
+
timeZone,
|
|
447
|
+
dateStyle: "short",
|
|
448
|
+
timeStyle: "short"
|
|
449
|
+
});
|
|
450
|
+
if (dateFormats.size >= 32) dateFormats.delete(dateFormats.keys().next().value);
|
|
451
|
+
dateFormats.set(key, formatter);
|
|
452
|
+
}
|
|
453
|
+
return formatter.format(date);
|
|
454
|
+
}
|
|
455
|
+
function customizationCss(ui) {
|
|
456
|
+
const declarations = Object.entries(ui.cssVariables).map(([key, value]) => `${key}:${value}`).join(";");
|
|
457
|
+
return declarations ? `:root:root{${declarations}}${ui.cssVariables["--dw-font-family"] ? "body{font-family:var(--dw-font-family)}" : ""}` : "";
|
|
458
|
+
}
|
|
459
|
+
/** Publish only supplied customization instead of repeating defaults in every page. */
|
|
460
|
+
function pickCustomization(options) {
|
|
461
|
+
const result = {};
|
|
462
|
+
for (const key of [
|
|
463
|
+
"language",
|
|
464
|
+
"direction",
|
|
465
|
+
"locale",
|
|
466
|
+
"timeZone",
|
|
467
|
+
"labels",
|
|
468
|
+
"brand",
|
|
469
|
+
"columns",
|
|
470
|
+
"actions",
|
|
471
|
+
"cssVariables"
|
|
472
|
+
]) if (options[key] !== void 0) {
|
|
473
|
+
const value = options[key];
|
|
474
|
+
Object.assign(result, { [key]: Array.isArray(value) ? value.map((item) => typeof item === "object" ? { ...item } : item) : typeof value === "object" && value !== null ? { ...value } : value });
|
|
475
|
+
}
|
|
476
|
+
return Object.keys(result).length ? result : void 0;
|
|
477
|
+
}
|
|
478
|
+
/** Built-in factories fail on typos instead of silently ignoring configuration. */
|
|
479
|
+
function assertThemeOptions(value, extraKeys) {
|
|
480
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Theme options must be an object");
|
|
481
|
+
const allowed = [
|
|
482
|
+
"language",
|
|
483
|
+
"direction",
|
|
484
|
+
"locale",
|
|
485
|
+
"timeZone",
|
|
486
|
+
"labels",
|
|
487
|
+
"brand",
|
|
488
|
+
"columns",
|
|
489
|
+
"actions",
|
|
490
|
+
"cssVariables",
|
|
491
|
+
...extraKeys
|
|
492
|
+
];
|
|
493
|
+
for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new TypeError(`Unsupported theme option: ${key}`);
|
|
494
|
+
}
|
|
495
|
+
function publicDataValue(value) {
|
|
496
|
+
return value === void 0 || value === null ? "" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
497
|
+
}
|
|
498
|
+
//#endregion
|
|
258
499
|
//#region ../shared-theme/src/navigation.ts
|
|
259
500
|
/** Folder snapshots resolve relative URLs before History changes the document URL. */
|
|
260
501
|
async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false, attempt = 0) {
|
|
@@ -270,6 +511,25 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
270
511
|
const data = value;
|
|
271
512
|
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
513
|
if (data.metadataHead !== void 0 && typeof data.metadataHead !== "string") throw new ListingDataError("Invalid folder metadata");
|
|
514
|
+
const sort = data.sort;
|
|
515
|
+
if (!sort || typeof sort !== "object" || ![
|
|
516
|
+
"name",
|
|
517
|
+
"size",
|
|
518
|
+
"modified"
|
|
519
|
+
].includes(String(sort.field)) || !["asc", "desc"].includes(String(sort.direction)) || ![
|
|
520
|
+
"natural",
|
|
521
|
+
"locale",
|
|
522
|
+
"unicode"
|
|
523
|
+
].includes(String(sort.nameMode)) || typeof sort.directoriesFirst !== "boolean") throw new ListingDataError("Invalid folder order");
|
|
524
|
+
if (theme === "crosswave") {
|
|
525
|
+
const categories = data.categories;
|
|
526
|
+
const ids = /* @__PURE__ */ new Set();
|
|
527
|
+
if (!Array.isArray(categories) || categories.length === 0 || categories.some((category) => {
|
|
528
|
+
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;
|
|
529
|
+
ids.add(category.id);
|
|
530
|
+
return false;
|
|
531
|
+
})) throw new ListingDataError("Invalid folder categories");
|
|
532
|
+
}
|
|
273
533
|
const base = document.querySelector("base")?.href ?? pageHref;
|
|
274
534
|
const link = (raw) => {
|
|
275
535
|
if (raw === null) return null;
|
|
@@ -286,6 +546,17 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
286
546
|
} } : {}
|
|
287
547
|
}));
|
|
288
548
|
if (entries.length > Number(data.total)) throw new ListingDataError("Invalid folder count");
|
|
549
|
+
if (data.page !== null) {
|
|
550
|
+
const page = data.page;
|
|
551
|
+
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");
|
|
552
|
+
data.page = {
|
|
553
|
+
...page,
|
|
554
|
+
href: link(page.href),
|
|
555
|
+
previousHref: link(page.previousHref),
|
|
556
|
+
nextHref: link(page.nextHref)
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (data.ui !== void 0) resolveCustomization(data.ui);
|
|
289
560
|
const result = {
|
|
290
561
|
...data,
|
|
291
562
|
updatesHref: link(data.updatesHref ?? null),
|
|
@@ -305,10 +576,10 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
305
576
|
};
|
|
306
577
|
})
|
|
307
578
|
};
|
|
308
|
-
if (
|
|
579
|
+
if (result.manifestHref) {
|
|
309
580
|
const loaded = await loadListing(result.manifestHref, {
|
|
310
581
|
signal,
|
|
311
|
-
cache: "no-store"
|
|
582
|
+
cache: fresh ? "no-store" : "default"
|
|
312
583
|
});
|
|
313
584
|
if (loaded.manifest.total !== data.total || data.listingRevision !== void 0 && loaded.manifest.revision !== data.listingRevision) {
|
|
314
585
|
if (attempt < 2) return fetchThemePage(dataHref, pageHref, theme, signal, true, attempt + 1);
|
|
@@ -376,6 +647,139 @@ function applyThemeMetadata(page, pageHref) {
|
|
|
376
647
|
}
|
|
377
648
|
}
|
|
378
649
|
//#endregion
|
|
650
|
+
//#region ../shared-theme/src/view-state.ts
|
|
651
|
+
const supportedTypes = [
|
|
652
|
+
"directory",
|
|
653
|
+
"file",
|
|
654
|
+
"link"
|
|
655
|
+
];
|
|
656
|
+
function validSort(value) {
|
|
657
|
+
if (typeof value !== "object" || value === null) return false;
|
|
658
|
+
const sort = value;
|
|
659
|
+
return [
|
|
660
|
+
"name",
|
|
661
|
+
"size",
|
|
662
|
+
"modified"
|
|
663
|
+
].includes(String(sort.field)) && ["asc", "desc"].includes(String(sort.direction)) && [
|
|
664
|
+
"natural",
|
|
665
|
+
"locale",
|
|
666
|
+
"unicode"
|
|
667
|
+
].includes(String(sort.nameMode)) && typeof sort.directoriesFirst === "boolean";
|
|
668
|
+
}
|
|
669
|
+
function position(value) {
|
|
670
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(value, 1e9)) : 0;
|
|
671
|
+
}
|
|
672
|
+
/** URL controls are shareable; selection and scroll belong only to one history entry. */
|
|
673
|
+
function readView(href, theme, path, defaults, categories = [], historyState = null) {
|
|
674
|
+
const params = new URL(href).searchParams;
|
|
675
|
+
const result = {
|
|
676
|
+
...defaults,
|
|
677
|
+
query: (params.get("dw.q") ?? "").slice(0, 4096),
|
|
678
|
+
selected: null,
|
|
679
|
+
windowY: 0,
|
|
680
|
+
listTop: 0
|
|
681
|
+
};
|
|
682
|
+
if (theme === "ledger") {
|
|
683
|
+
const parts = params.get("dw.sort")?.split(":");
|
|
684
|
+
if (parts?.length === 4) {
|
|
685
|
+
const sort = {
|
|
686
|
+
field: parts[0],
|
|
687
|
+
direction: parts[1],
|
|
688
|
+
nameMode: parts[2],
|
|
689
|
+
directoriesFirst: parts[3] === "folders"
|
|
690
|
+
};
|
|
691
|
+
if (["folders", "mixed"].includes(parts[3]) && validSort(sort)) result.sort = sort;
|
|
692
|
+
}
|
|
693
|
+
if (params.has("dw.types")) {
|
|
694
|
+
const types = params.get("dw.types").split(",").filter(Boolean);
|
|
695
|
+
if (types.every((type) => supportedTypes.includes(type))) result.types = [...new Set(types)];
|
|
696
|
+
}
|
|
697
|
+
} else {
|
|
698
|
+
const category = params.get("dw.category");
|
|
699
|
+
if (category !== null && categories.includes(category)) result.category = category;
|
|
700
|
+
}
|
|
701
|
+
if (typeof historyState === "object" && historyState !== null && "dirwell" in historyState) {
|
|
702
|
+
const state = historyState.dirwell;
|
|
703
|
+
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) {
|
|
704
|
+
const view = state.view;
|
|
705
|
+
result.selected = typeof view.selected === "string" && view.selected.length <= 4096 ? view.selected : null;
|
|
706
|
+
result.windowY = position(view.windowY);
|
|
707
|
+
result.listTop = position(view.listTop);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return result;
|
|
711
|
+
}
|
|
712
|
+
function viewHref(href, theme, view, defaults) {
|
|
713
|
+
const url = new URL(href);
|
|
714
|
+
for (const key of [
|
|
715
|
+
"dw.q",
|
|
716
|
+
"dw.sort",
|
|
717
|
+
"dw.types",
|
|
718
|
+
"dw.category"
|
|
719
|
+
]) url.searchParams.delete(key);
|
|
720
|
+
if (view.query) url.searchParams.set("dw.q", view.query.slice(0, 4096));
|
|
721
|
+
if (theme === "ledger") {
|
|
722
|
+
if (view.sort && [
|
|
723
|
+
"field",
|
|
724
|
+
"direction",
|
|
725
|
+
"nameMode",
|
|
726
|
+
"directoriesFirst"
|
|
727
|
+
].some((key) => view.sort[key] !== defaults.sort?.[key])) {
|
|
728
|
+
const sort = view.sort;
|
|
729
|
+
url.searchParams.set("dw.sort", `${sort.field}:${sort.direction}:${sort.nameMode}:${sort.directoriesFirst ? "folders" : "mixed"}`);
|
|
730
|
+
}
|
|
731
|
+
if (view.types && supportedTypes.some((type) => view.types.includes(type) !== (defaults.types ?? supportedTypes).includes(type))) url.searchParams.set("dw.types", view.types.join(","));
|
|
732
|
+
} else if (view.category && view.category !== defaults.category) url.searchParams.set("dw.category", view.category);
|
|
733
|
+
return url.href;
|
|
734
|
+
}
|
|
735
|
+
/** History failures never require discarding a successfully loaded view. */
|
|
736
|
+
function createViewHistory(theme) {
|
|
737
|
+
let committedHref = location.href;
|
|
738
|
+
let committedState = history.state;
|
|
739
|
+
const stateFor = (dataHref, path, view) => ({
|
|
740
|
+
...typeof history.state === "object" && history.state !== null ? history.state : {},
|
|
741
|
+
dataHref,
|
|
742
|
+
dirwell: {
|
|
743
|
+
version: 1,
|
|
744
|
+
theme,
|
|
745
|
+
path,
|
|
746
|
+
...view === void 0 ? {} : { view: {
|
|
747
|
+
query: view.query,
|
|
748
|
+
...view.sort === void 0 ? {} : { sort: { ...view.sort } },
|
|
749
|
+
...view.types === void 0 ? {} : { types: [...view.types] },
|
|
750
|
+
category: view.category ?? null,
|
|
751
|
+
selected: view.selected ?? null,
|
|
752
|
+
windowY: view.windowY ?? 0,
|
|
753
|
+
listTop: view.listTop ?? 0
|
|
754
|
+
} }
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
return {
|
|
758
|
+
save(dataHref, path, view, defaults) {
|
|
759
|
+
if (location.href !== committedHref) return;
|
|
760
|
+
const href = viewHref(committedHref, theme, view, defaults);
|
|
761
|
+
const state = stateFor(dataHref, path, view);
|
|
762
|
+
try {
|
|
763
|
+
history.replaceState(state, "", href);
|
|
764
|
+
committedHref = href;
|
|
765
|
+
committedState = state;
|
|
766
|
+
} catch {}
|
|
767
|
+
},
|
|
768
|
+
commit(dataHref, path, href, push) {
|
|
769
|
+
const state = stateFor(dataHref, path);
|
|
770
|
+
if (push) history.pushState(state, "", href);
|
|
771
|
+
else history.replaceState(state, "", href);
|
|
772
|
+
committedHref = href;
|
|
773
|
+
committedState = state;
|
|
774
|
+
},
|
|
775
|
+
rollback() {
|
|
776
|
+
if (location.href !== committedHref) try {
|
|
777
|
+
history.replaceState(committedState, "", committedHref);
|
|
778
|
+
} catch {}
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
//#endregion
|
|
379
783
|
//#region ../shared-theme/src/icons.ts
|
|
380
784
|
const paths = {
|
|
381
785
|
directory: "<path d=\"M3 7V5h6l2 3h10v12H3Z\"/><path d=\"M3 10h18\"/>",
|
|
@@ -404,14 +808,6 @@ function size(bytes) {
|
|
|
404
808
|
}
|
|
405
809
|
return `${value.toFixed(index === 0 || value >= 100 ? 0 : 1)} ${units[index]}`;
|
|
406
810
|
}
|
|
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
811
|
//#endregion
|
|
416
812
|
//#region .generated/wave.mjs
|
|
417
813
|
function createWave(canvas, colors, root, moving, onReady) {
|
|
@@ -557,11 +953,89 @@ var App_server_default = {
|
|
|
557
953
|
required: true
|
|
558
954
|
} },
|
|
559
955
|
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(
|
|
956
|
+
const props = __props, model = shallowRef(restoreThemePage(props.model, Boolean(import.meta.hot))), entries = shallowRef(model.value.entries), ui = computed(() => resolveCustomization(model.value.ui));
|
|
957
|
+
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());
|
|
562
958
|
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) =>
|
|
959
|
+
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
960
|
const nodes = /* @__PURE__ */ new Map();
|
|
961
|
+
const virtual = computed(() => ready.value && rows.value.length > 500);
|
|
962
|
+
const offsets = computed(() => {
|
|
963
|
+
const result = [0];
|
|
964
|
+
for (const entry of rows.value) result.push(result.at(-1) + (heights.value.get(entry.id) ?? 76));
|
|
965
|
+
return result;
|
|
966
|
+
});
|
|
967
|
+
const start = computed(() => {
|
|
968
|
+
if (!virtual.value) return 0;
|
|
969
|
+
const position = Math.max(0, scrollTop.value - (model.value.parentHref ? 76 : 0));
|
|
970
|
+
let low = 0, high = rows.value.length;
|
|
971
|
+
while (low < high) {
|
|
972
|
+
const middle = low + high >> 1;
|
|
973
|
+
if (offsets.value[middle + 1] <= position) low = middle + 1;
|
|
974
|
+
else high = middle;
|
|
975
|
+
}
|
|
976
|
+
return Math.max(0, low - 5);
|
|
977
|
+
});
|
|
978
|
+
const end = computed(() => {
|
|
979
|
+
if (!virtual.value) return rows.value.length;
|
|
980
|
+
let low = 0, high = rows.value.length;
|
|
981
|
+
while (low < high) {
|
|
982
|
+
const middle = low + high >> 1;
|
|
983
|
+
if (offsets.value[middle + 1] <= scrollTop.value + viewportHeight.value) low = middle + 1;
|
|
984
|
+
else high = middle;
|
|
985
|
+
}
|
|
986
|
+
return Math.min(rows.value.length, Math.max(start.value + 35, low + 5));
|
|
987
|
+
});
|
|
988
|
+
const visibleRows = computed(() => rows.value.slice(start.value, end.value));
|
|
989
|
+
const heightCache = /* @__PURE__ */ new Map();
|
|
990
|
+
let rowObserver, layoutObserver, viewHistory, measuredWidth;
|
|
991
|
+
function measureLayout() {
|
|
992
|
+
if (!listScroll.value) return;
|
|
993
|
+
const width = listScroll.value.clientWidth;
|
|
994
|
+
if (measuredWidth !== void 0 && width !== measuredWidth) {
|
|
995
|
+
heightCache.clear();
|
|
996
|
+
heights.value = new Map([...nodes].map(([id, node]) => [id, node.getBoundingClientRect().height]));
|
|
997
|
+
}
|
|
998
|
+
measuredWidth = width;
|
|
999
|
+
const footerHeight = footerElement.value?.getBoundingClientRect().height ?? 82;
|
|
1000
|
+
document.documentElement.style.setProperty("--cw-footer-height", footerHeight + "px");
|
|
1001
|
+
const available = Math.max(160, innerHeight - listScroll.value.getBoundingClientRect().top - footerHeight - 24);
|
|
1002
|
+
viewportHeight.value = available;
|
|
1003
|
+
listScroll.value.style.setProperty("--cw-list-height", available + "px");
|
|
1004
|
+
}
|
|
1005
|
+
watch([virtual, () => model.value.path], () => nextTick(measureLayout));
|
|
1006
|
+
const viewDefaults = (page) => ({
|
|
1007
|
+
query: "",
|
|
1008
|
+
category: page.categories[0]?.id ?? null
|
|
1009
|
+
});
|
|
1010
|
+
function saveView() {
|
|
1011
|
+
if (!viewHistory) return;
|
|
1012
|
+
viewHistory.save(new URL(model.value.dataHref, document.baseURI).href, model.value.path, {
|
|
1013
|
+
query: query.value,
|
|
1014
|
+
category: category.value,
|
|
1015
|
+
selected: selected.value,
|
|
1016
|
+
windowY: window.scrollY,
|
|
1017
|
+
listTop: listScroll.value?.scrollTop ?? 0
|
|
1018
|
+
}, viewDefaults(model.value));
|
|
1019
|
+
}
|
|
1020
|
+
watchEffect((onCleanup) => {
|
|
1021
|
+
if (!ready.value || busy.value) return;
|
|
1022
|
+
const view = {
|
|
1023
|
+
query: query.value,
|
|
1024
|
+
category: category.value,
|
|
1025
|
+
selected: selected.value,
|
|
1026
|
+
listTop: scrollTop.value
|
|
1027
|
+
};
|
|
1028
|
+
const dataHref = model.value.dataHref, path = model.value.path, defaults = viewDefaults(model.value);
|
|
1029
|
+
const frame = requestAnimationFrame(() => viewHistory?.save(new URL(dataHref, document.baseURI).href, path, {
|
|
1030
|
+
...view,
|
|
1031
|
+
windowY: window.scrollY
|
|
1032
|
+
}, defaults));
|
|
1033
|
+
onCleanup(() => cancelAnimationFrame(frame));
|
|
1034
|
+
});
|
|
1035
|
+
watch([query, category], () => {
|
|
1036
|
+
scrollTop.value = 0;
|
|
1037
|
+
if (listScroll.value) listScroll.value.scrollTop = 0;
|
|
1038
|
+
});
|
|
565
1039
|
let wave, gamepad, abort, navigation = 0, timer, lastPad = 0, pressed = /* @__PURE__ */ new Set(), disposed = false, padIdentity = null, motionQuery;
|
|
566
1040
|
function safeHref(entry) {
|
|
567
1041
|
return typeof location !== "undefined" && location.protocol === "file:" && directoryLike(entry) && !entry.exitsExplorer && entry.href?.endsWith("/") ? entry.href + "index.html" : entry.href;
|
|
@@ -577,67 +1051,113 @@ var App_server_default = {
|
|
|
577
1051
|
});
|
|
578
1052
|
if (token === navigation && !local.signal.aborted && !disposed) entries.value = data.entries;
|
|
579
1053
|
} catch (cause) {
|
|
580
|
-
if (token === navigation && !local.signal.aborted)
|
|
1054
|
+
if (token === navigation && !local.signal.aborted) {
|
|
1055
|
+
error.value = ui.value.labels.listingError;
|
|
1056
|
+
failure.value = "listing";
|
|
1057
|
+
}
|
|
581
1058
|
} finally {
|
|
582
1059
|
if (token === navigation) busy.value = false;
|
|
583
1060
|
}
|
|
584
1061
|
}
|
|
585
|
-
async function openFolder(dataHref, url, push, refresh = false) {
|
|
1062
|
+
async function openFolder(dataHref, url, push, refresh = false, restoreState = null, fresh = false) {
|
|
1063
|
+
if (push) saveView();
|
|
586
1064
|
const token = ++navigation;
|
|
587
1065
|
abort?.abort();
|
|
588
1066
|
const local = abort = new AbortController();
|
|
589
1067
|
busy.value = true;
|
|
590
1068
|
error.value = "";
|
|
1069
|
+
failure.value = null;
|
|
591
1070
|
try {
|
|
592
|
-
const next = await fetchThemePage(dataHref, url, "crosswave", local.signal, refresh);
|
|
1071
|
+
const next = await fetchThemePage(dataHref, url, "crosswave", local.signal, refresh || fresh);
|
|
593
1072
|
let nextEntries = next.entries;
|
|
594
1073
|
if (refresh && next.manifestHref && nextEntries.length !== next.total) nextEntries = (await loadListing(next.manifestHref, {
|
|
595
1074
|
signal: local.signal,
|
|
596
1075
|
cache: "no-store"
|
|
597
1076
|
})).entries;
|
|
598
1077
|
if (token !== navigation || local.signal.aborted || disposed) return;
|
|
1078
|
+
if (!refresh) viewHistory.commit(next.dataHref, next.path, url, push);
|
|
1079
|
+
if (next.path !== model.value.path) {
|
|
1080
|
+
heightCache.set(model.value.path, heights.value);
|
|
1081
|
+
if (heightCache.size > 8) heightCache.delete(heightCache.keys().next().value);
|
|
1082
|
+
heights.value = heightCache.get(next.path) ?? /* @__PURE__ */ new Map();
|
|
1083
|
+
}
|
|
599
1084
|
model.value = next;
|
|
1085
|
+
retryRequest.value = null;
|
|
600
1086
|
entries.value = nextEntries;
|
|
601
1087
|
if (!refresh) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1088
|
+
const view = readView(url, "crosswave", next.path, viewDefaults(next), next.categories.map((item) => item.id), restoreState);
|
|
1089
|
+
query.value = view.query;
|
|
1090
|
+
selected.value = view.selected;
|
|
1091
|
+
category.value = view.category;
|
|
605
1092
|
}
|
|
606
|
-
if (push) history.pushState({ dataHref: next.dataHref }, "", url);
|
|
607
1093
|
applyThemeMetadata(next, url);
|
|
608
1094
|
await loadComplete();
|
|
1095
|
+
if (!refresh) {
|
|
1096
|
+
const view = readView(url, "crosswave", next.path, viewDefaults(next), next.categories.map((item) => item.id), restoreState);
|
|
1097
|
+
await nextTick();
|
|
1098
|
+
if (selected.value && !rows.value.some((entry) => entry.id === selected.value)) selected.value = null;
|
|
1099
|
+
scrollTop.value = view.listTop;
|
|
1100
|
+
if (listScroll.value) listScroll.value.scrollTop = view.listTop;
|
|
1101
|
+
await nextTick();
|
|
1102
|
+
window.scrollTo({ top: view.windowY });
|
|
1103
|
+
if (restoreState && selected.value) nodes.get(selected.value)?.querySelector("a")?.focus({ preventScroll: true });
|
|
1104
|
+
}
|
|
609
1105
|
} catch (cause) {
|
|
610
|
-
if (token === navigation && !local.signal.aborted)
|
|
1106
|
+
if (token === navigation && !local.signal.aborted) {
|
|
1107
|
+
error.value = ui.value.labels.folderError;
|
|
1108
|
+
failure.value = "folder";
|
|
1109
|
+
retryRequest.value = [
|
|
1110
|
+
dataHref,
|
|
1111
|
+
url,
|
|
1112
|
+
push,
|
|
1113
|
+
refresh,
|
|
1114
|
+
restoreState
|
|
1115
|
+
];
|
|
1116
|
+
viewHistory?.rollback();
|
|
1117
|
+
}
|
|
611
1118
|
} finally {
|
|
612
1119
|
if (token === navigation) busy.value = false;
|
|
613
1120
|
}
|
|
614
1121
|
}
|
|
615
1122
|
async function move(index) {
|
|
616
|
-
const
|
|
1123
|
+
const target = Math.max(0, Math.min(rows.value.length - 1, index));
|
|
1124
|
+
const entry = rows.value[target];
|
|
617
1125
|
if (!entry) return;
|
|
618
1126
|
selected.value = entry.id;
|
|
1127
|
+
if (virtual.value) {
|
|
1128
|
+
scrollTop.value = Math.max(0, offsets.value[target] - 100 + (model.value.parentHref ? 76 : 0));
|
|
1129
|
+
if (listScroll.value) listScroll.value.scrollTop = scrollTop.value;
|
|
1130
|
+
}
|
|
619
1131
|
await nextTick();
|
|
620
1132
|
const node = nodes.get(entry.id);
|
|
621
1133
|
node?.querySelector("a")?.focus({ preventScroll: true });
|
|
622
1134
|
node?.scrollIntoView({ block: "nearest" });
|
|
623
1135
|
}
|
|
624
|
-
function stepCategory(delta) {
|
|
1136
|
+
async function stepCategory(delta) {
|
|
1137
|
+
if (ui.value.direction === "rtl") delta = -delta;
|
|
1138
|
+
const moveFocus = document.activeElement?.getAttribute("role") === "tab";
|
|
625
1139
|
const items = model.value.categories, index = items.findIndex((item) => item.id === category.value);
|
|
626
1140
|
category.value = items[(index + delta + items.length) % items.length].id;
|
|
627
1141
|
selected.value = null;
|
|
1142
|
+
if (moveFocus) {
|
|
1143
|
+
await nextTick();
|
|
1144
|
+
document.getElementById("cw-tab-" + category.value)?.focus({ preventScroll: true });
|
|
1145
|
+
}
|
|
628
1146
|
}
|
|
629
|
-
function confirm() {
|
|
1147
|
+
async function confirm() {
|
|
630
1148
|
if (!active.value || busy.value) return;
|
|
1149
|
+
await move(rows.value.findIndex((entry) => entry.id === active.value.id));
|
|
631
1150
|
(nodes.get(active.value.id)?.querySelector("a"))?.click();
|
|
632
1151
|
}
|
|
633
1152
|
function parent() {
|
|
634
1153
|
if (model.value.parentHref) {
|
|
635
|
-
if (location.protocol === "file:") location.href = model.value.parentHref;
|
|
1154
|
+
if (location.protocol === "file:" || typeof model.value.parentDataHref !== "string") location.href = model.value.parentHref;
|
|
636
1155
|
else openFolder(model.value.parentDataHref, new URL(model.value.parentHref, document.baseURI).href, true);
|
|
637
1156
|
}
|
|
638
1157
|
}
|
|
639
1158
|
function keyboard(event) {
|
|
640
1159
|
if (event.isComposing || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
1160
|
+
if (event.target?.closest?.("[data-dirwell-actions]")) return;
|
|
641
1161
|
if (/INPUT|TEXTAREA|SELECT/.test(event.target?.tagName ?? "")) {
|
|
642
1162
|
if (event.key === "Escape") query.value = "";
|
|
643
1163
|
return;
|
|
@@ -736,7 +1256,7 @@ var App_server_default = {
|
|
|
736
1256
|
.38
|
|
737
1257
|
]
|
|
738
1258
|
};
|
|
739
|
-
const onPop = (event) => event.state?.dataHref ? openFolder(event.state.dataHref, location.href, false) : location.reload();
|
|
1259
|
+
const onPop = (event) => event.state?.dataHref ? openFolder(event.state.dataHref, location.href, false, false, event.state) : location.reload();
|
|
740
1260
|
const suspend = () => {
|
|
741
1261
|
gamepad?.pause();
|
|
742
1262
|
controller(null);
|
|
@@ -757,7 +1277,36 @@ var App_server_default = {
|
|
|
757
1277
|
ready.value = true;
|
|
758
1278
|
document.body.setAttribute("data-cw-enhanced", "");
|
|
759
1279
|
abort = new AbortController();
|
|
760
|
-
|
|
1280
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1281
|
+
rowObserver = new ResizeObserver((observations) => {
|
|
1282
|
+
const next = new Map(heights.value);
|
|
1283
|
+
let changed = false;
|
|
1284
|
+
for (const observation of observations) {
|
|
1285
|
+
const id = observation.target.dataset.id;
|
|
1286
|
+
const height = observation.target.getBoundingClientRect().height;
|
|
1287
|
+
if (height > 0 && next.get(id) !== height) {
|
|
1288
|
+
next.set(id, height);
|
|
1289
|
+
changed = true;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (changed) heights.value = next;
|
|
1293
|
+
});
|
|
1294
|
+
for (const node of nodes.values()) rowObserver.observe(node);
|
|
1295
|
+
layoutObserver = new ResizeObserver(measureLayout);
|
|
1296
|
+
if (footerElement.value) layoutObserver.observe(footerElement.value);
|
|
1297
|
+
const header = document.querySelector("[data-dirwell-theme='crosswave'] .cw-top");
|
|
1298
|
+
if (header) layoutObserver.observe(header);
|
|
1299
|
+
}
|
|
1300
|
+
measureLayout();
|
|
1301
|
+
window.addEventListener("resize", measureLayout);
|
|
1302
|
+
viewHistory = createViewHistory("crosswave");
|
|
1303
|
+
const view = readView(location.href, "crosswave", model.value.path, viewDefaults(model.value), model.value.categories.map((item) => item.id), history.state);
|
|
1304
|
+
query.value = view.query;
|
|
1305
|
+
category.value = view.category;
|
|
1306
|
+
selected.value = view.selected;
|
|
1307
|
+
scrollTop.value = view.listTop;
|
|
1308
|
+
if (listScroll.value) listScroll.value.scrollTop = view.listTop;
|
|
1309
|
+
saveView();
|
|
761
1310
|
try {
|
|
762
1311
|
const stored = localStorage.getItem("dirwell-crosswave-color");
|
|
763
1312
|
if (Object.hasOwn(colors, stored)) color.value = stored;
|
|
@@ -785,7 +1334,8 @@ var App_server_default = {
|
|
|
785
1334
|
gamepad.resume();
|
|
786
1335
|
watchEffect(() => controller(gamepad.gamepads.value.find((value) => value.connected && value.mapping === "standard") ?? null), { flush: "sync" });
|
|
787
1336
|
}
|
|
788
|
-
const updateClock = () => clock.value = (/* @__PURE__ */ new Date()).toLocaleTimeString(
|
|
1337
|
+
const updateClock = () => clock.value = (/* @__PURE__ */ new Date()).toLocaleTimeString(ui.value.dateOptions.locale, {
|
|
1338
|
+
timeZone: ui.value.dateOptions.timeZone,
|
|
789
1339
|
hour: "2-digit",
|
|
790
1340
|
minute: "2-digit"
|
|
791
1341
|
});
|
|
@@ -804,9 +1354,15 @@ var App_server_default = {
|
|
|
804
1354
|
if (!href || typeof EventSource === "undefined") return;
|
|
805
1355
|
const updates = new EventSource(href);
|
|
806
1356
|
updates.onmessage = () => pendingUpdates.value = href;
|
|
807
|
-
updates.onerror = () =>
|
|
1357
|
+
updates.onerror = () => {
|
|
1358
|
+
error.value = ui.value.labels.updatesError;
|
|
1359
|
+
failure.value = "updates";
|
|
1360
|
+
};
|
|
808
1361
|
updates.onopen = () => {
|
|
809
|
-
if (
|
|
1362
|
+
if (failure.value === "updates") {
|
|
1363
|
+
error.value = "";
|
|
1364
|
+
failure.value = null;
|
|
1365
|
+
}
|
|
810
1366
|
};
|
|
811
1367
|
onCleanup(() => updates.close());
|
|
812
1368
|
});
|
|
@@ -831,6 +1387,9 @@ var App_server_default = {
|
|
|
831
1387
|
disposed = true;
|
|
832
1388
|
document.body.removeAttribute("data-cw-enhanced");
|
|
833
1389
|
abort?.abort();
|
|
1390
|
+
rowObserver?.disconnect();
|
|
1391
|
+
layoutObserver?.disconnect();
|
|
1392
|
+
window.removeEventListener("resize", measureLayout);
|
|
834
1393
|
gamepad?.pause();
|
|
835
1394
|
wave?.dispose();
|
|
836
1395
|
clearInterval(timer);
|
|
@@ -844,25 +1403,29 @@ var App_server_default = {
|
|
|
844
1403
|
return (_ctx, _push, _parent, _attrs) => {
|
|
845
1404
|
_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
1405
|
ssrRenderSlot(_ctx.$slots, "header", { model: model.value }, () => {
|
|
847
|
-
_push(`<
|
|
1406
|
+
if (ui.value.brand.logoUrl) _push(`<img${ssrRenderAttr("src", ui.value.brand.logoUrl)}${ssrRenderAttr("alt", ui.value.brand.logoAlt ?? "")} width="40" height="40">`);
|
|
1407
|
+
else _push(`<!---->`);
|
|
1408
|
+
_push(`<a${ssrRenderAttr("href", model.value.rootHref)}>${ssrInterpolate(ui.value.brand.name ?? model.value.title)}</a>`);
|
|
848
1409
|
}, _push, _parent);
|
|
849
|
-
_push(`</div><nav class="cw-location"
|
|
1410
|
+
_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
1411
|
ssrRenderList(model.value.breadcrumbs, (crumb) => {
|
|
851
1412
|
_push(`<!--[--> / <a${ssrRenderAttr("href", crumb.href)}>${ssrInterpolate(crumb.name)}</a><!--]-->`);
|
|
852
1413
|
});
|
|
853
1414
|
_push(`<!--]--></nav>`);
|
|
854
1415
|
if (ready.value) _push(`<div class="cw-clock">${ssrInterpolate(clock.value)}</div>`);
|
|
855
1416
|
else _push(`<!---->`);
|
|
856
|
-
_push(`</header><main class="cw-stage" data-framework="vue"${ssrRenderAttr("aria-busy", busy.value)}><h1 class="cw-sr"
|
|
1417
|
+
_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
1418
|
ssrRenderList(model.value.categories, (item) => {
|
|
858
1419
|
_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
1420
|
});
|
|
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)}
|
|
1421
|
+
_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></div><div class="cw-list-scroll"${ssrRenderAttr("data-virtual", virtual.value)}><ul class="cw-files">`);
|
|
861
1422
|
if (model.value.parentHref) _push(`<li class="cw-row"><a${ssrRenderAttr("href", model.value.parentHref)} class="cw-entry">../</a></li>`);
|
|
862
1423
|
else _push(`<!---->`);
|
|
1424
|
+
if (start.value) _push(`<li aria-hidden="true" style="${ssrRenderStyle({ height: offsets.value[start.value] + "px" })}"></li>`);
|
|
1425
|
+
else _push(`<!---->`);
|
|
863
1426
|
_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)}>`);
|
|
1427
|
+
ssrRenderList(visibleRows.value, (entry, visibleIndex) => {
|
|
1428
|
+
_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
1429
|
if (entry.href) {
|
|
867
1430
|
_push(`<a class="cw-entry"${ssrRenderAttr("href", safeHref(entry))}${ssrRenderAttr("target", entry.exitsExplorer ? "_blank" : void 0)}${ssrRenderAttr("rel", entry.exitsExplorer ? "noopener" : void 0)}>`);
|
|
868
1431
|
ssrRenderSlot(_ctx.$slots, "row", {
|
|
@@ -870,10 +1433,22 @@ var App_server_default = {
|
|
|
870
1433
|
entry,
|
|
871
1434
|
selected: selected.value === entry.id
|
|
872
1435
|
}, () => {
|
|
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
|
-
|
|
1436
|
+
_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"><!--[-->`);
|
|
1437
|
+
ssrRenderList(ui.value.columns, (column) => {
|
|
1438
|
+
_push(`<!--[-->`);
|
|
1439
|
+
if (column === "name") _push(`<span class="cw-name">${ssrInterpolate(entry.name)}${ssrInterpolate(unref(directoryLike)(entry) ? "/" : "")}</span>`);
|
|
1440
|
+
else _push(`<!---->`);
|
|
1441
|
+
if (entry.link && column === "name") _push(`<span class="cw-target">${ssrInterpolate(entry.link.target)}</span>`);
|
|
1442
|
+
else _push(`<!---->`);
|
|
1443
|
+
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>`);
|
|
1444
|
+
else _push(`<!---->`);
|
|
1445
|
+
if (column === "modified") _push(`<span class="cw-row-date">${ssrInterpolate(ui.value.labels.modified)} ${ssrInterpolate(unref(formattedDate)(entry.modifiedAt, ui.value, ready.value))}</span>`);
|
|
1446
|
+
else _push(`<!---->`);
|
|
1447
|
+
if (typeof column === "object") _push(`<span class="cw-data-value">${ssrInterpolate(column.label)}: ${ssrInterpolate(unref(publicDataValue)(entry.data?.[column.dataKey]))}</span>`);
|
|
1448
|
+
else _push(`<!---->`);
|
|
1449
|
+
_push(`<!--]-->`);
|
|
1450
|
+
});
|
|
1451
|
+
_push(`<!--]--></span>`);
|
|
877
1452
|
}, _push, _parent);
|
|
878
1453
|
_push(`</a>`);
|
|
879
1454
|
} else {
|
|
@@ -887,40 +1462,76 @@ var App_server_default = {
|
|
|
887
1462
|
}, _push, _parent);
|
|
888
1463
|
_push(`</span>`);
|
|
889
1464
|
}
|
|
1465
|
+
if (ui.value.actions.length || _ctx.$slots.actions) {
|
|
1466
|
+
_push(`<span data-dirwell-actions class="cw-row-actions">`);
|
|
1467
|
+
ssrRenderSlot(_ctx.$slots, "actions", {
|
|
1468
|
+
model: model.value,
|
|
1469
|
+
entry,
|
|
1470
|
+
selected: selected.value === entry.id
|
|
1471
|
+
}, null, _push, _parent);
|
|
1472
|
+
_push(`<!--[-->`);
|
|
1473
|
+
ssrRenderList(ui.value.actions, (action) => {
|
|
1474
|
+
_push(`<!--[-->`);
|
|
1475
|
+
if (action === "download" && entry.href && !unref(directoryLike)(entry) && !entry.link?.unavailable) _push(`<a${ssrRenderAttr("href", safeHref(entry))} download>${ssrInterpolate(ui.value.labels.download)}</a>`);
|
|
1476
|
+
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>`);
|
|
1477
|
+
else _push(`<!---->`);
|
|
1478
|
+
_push(`<!--]-->`);
|
|
1479
|
+
});
|
|
1480
|
+
_push(`<!--]--></span>`);
|
|
1481
|
+
} else _push(`<!---->`);
|
|
890
1482
|
_push(`</li>`);
|
|
891
1483
|
});
|
|
892
|
-
_push(`<!--]
|
|
893
|
-
if (
|
|
1484
|
+
_push(`<!--]-->`);
|
|
1485
|
+
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>`);
|
|
1486
|
+
else _push(`<!---->`);
|
|
1487
|
+
_push(`</ul>`);
|
|
1488
|
+
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
1489
|
else _push(`<!---->`);
|
|
895
1490
|
_push(`</div></section>`);
|
|
896
1491
|
if (ready.value && active.value) {
|
|
897
|
-
_push(`<aside class="cw-detail"><h2>${ssrInterpolate(active.value.name)}</h2
|
|
1492
|
+
_push(`<aside class="cw-detail"><h2>${ssrInterpolate(active.value.name)}</h2>`);
|
|
1493
|
+
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>`);
|
|
1494
|
+
else _push(`<!---->`);
|
|
1495
|
+
if (ui.value.columns.includes("modified")) _push(`<p>${ssrInterpolate(unref(formattedDate)(active.value.modifiedAt, ui.value, ready.value))}</p>`);
|
|
1496
|
+
else _push(`<!---->`);
|
|
898
1497
|
if (active.value.link) _push(`<p>${ssrInterpolate(active.value.link.target)}</p>`);
|
|
899
1498
|
else _push(`<!---->`);
|
|
900
1499
|
_push(`</aside>`);
|
|
901
1500
|
} else _push(`<!---->`);
|
|
902
|
-
_push(`<p role="status" aria-live="polite">${ssrInterpolate(busy.value ?
|
|
1501
|
+
_push(`<p role="status" aria-live="polite">${ssrInterpolate(busy.value ? ui.value.labels.loading : error.value)}</p>`);
|
|
1502
|
+
if (failure.value && failure.value !== "copy") _push(`<button${ssrIncludeBooleanAttr(busy.value) ? " disabled" : ""}>${ssrInterpolate(ui.value.labels.retry)}</button>`);
|
|
1503
|
+
else _push(`<!---->`);
|
|
1504
|
+
if (failure.value === "folder" && retryRequest.value) _push(`<a${ssrRenderAttr("href", retryRequest.value[1])}>${ssrInterpolate(ui.value.labels.openNative)}</a>`);
|
|
1505
|
+
else _push(`<!---->`);
|
|
903
1506
|
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"
|
|
1507
|
+
_push(`<nav${ssrRenderAttr("aria-label", ui.value.labels.pagination)}>`);
|
|
1508
|
+
if (model.value.page.previousHref) _push(`<a${ssrRenderAttr("href", model.value.page.previousHref)} rel="prev">${ssrInterpolate(ui.value.labels.previous)}</a>`);
|
|
906
1509
|
else _push(`<!---->`);
|
|
907
|
-
_push(`
|
|
908
|
-
|
|
1510
|
+
_push(` ${ssrInterpolate(unref(label)(ui.value, "page", {
|
|
1511
|
+
page: model.value.page.number,
|
|
1512
|
+
pages: model.value.page.count
|
|
1513
|
+
}))} `);
|
|
1514
|
+
if (model.value.page.nextHref) _push(`<a${ssrRenderAttr("href", model.value.page.nextHref)} rel="next">${ssrInterpolate(ui.value.labels.next)}</a>`);
|
|
909
1515
|
else _push(`<!---->`);
|
|
910
1516
|
_push(`</nav>`);
|
|
911
1517
|
} else _push(`<!---->`);
|
|
912
|
-
_push(`</main><footer class="cw-bottom"><div class="cw-key-help">`);
|
|
1518
|
+
_push(`</main><footer class="cw-bottom"><div class="${ssrRenderClass(ui.value.brand.footerText || _ctx.$slots.footer ? "cw-custom-footer" : "cw-key-help")}">`);
|
|
913
1519
|
ssrRenderSlot(_ctx.$slots, "footer", { model: model.value }, () => {
|
|
914
|
-
|
|
1520
|
+
if (ui.value.brand.footerText) {
|
|
1521
|
+
_push(`<!--[-->`);
|
|
1522
|
+
if (ui.value.brand.footerUrl) _push(`<a${ssrRenderAttr("href", ui.value.brand.footerUrl)}>${ssrInterpolate(ui.value.brand.footerText)}</a>`);
|
|
1523
|
+
else _push(`<span>${ssrInterpolate(ui.value.brand.footerText)}</span>`);
|
|
1524
|
+
_push(`<!--]-->`);
|
|
1525
|
+
} else _push(`<!--[-->${ssrInterpolate(ui.value.labels.crosswaveKeyboardHelp)}<!--]-->`);
|
|
915
1526
|
}, _push, _parent);
|
|
916
1527
|
_push(`</div>`);
|
|
917
|
-
if (pad.value) _push(`<div class="cw-controller"
|
|
1528
|
+
if (pad.value) _push(`<div class="cw-controller">${ssrInterpolate(ui.value.labels.controller)}</div>`);
|
|
918
1529
|
else _push(`<!---->`);
|
|
919
|
-
_push(`<label class="cw-appearance"
|
|
1530
|
+
_push(`<label class="cw-appearance">${ssrInterpolate(ui.value.labels.color)}<select${ssrIncludeBooleanAttr(!ready.value) ? " disabled" : ""}><!--[-->`);
|
|
920
1531
|
ssrRenderList(Object.keys(colors), (name) => {
|
|
921
|
-
_push(`<option${ssrIncludeBooleanAttr(Array.isArray(color.value) ? ssrLooseContain(color.value,
|
|
1532
|
+
_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
1533
|
});
|
|
923
|
-
_push(`<!--]--></select></label><button class="cw-motion"${ssrIncludeBooleanAttr(!ready.value) ? " disabled" : ""}>${ssrInterpolate(paused.value ?
|
|
1534
|
+
_push(`<!--]--></select></label><button class="cw-motion"${ssrIncludeBooleanAttr(!ready.value) ? " disabled" : ""}>${ssrInterpolate(paused.value ? ui.value.labels.resumeMotion : ui.value.labels.pauseMotion)}</button></footer><!--]-->`);
|
|
924
1535
|
};
|
|
925
1536
|
}
|
|
926
1537
|
};
|
|
@@ -930,7 +1541,44 @@ const renderBody = (model) => renderToString(createSSRApp(App_server_default, {
|
|
|
930
1541
|
//#endregion
|
|
931
1542
|
//#region src/index.ts
|
|
932
1543
|
function createCrosswaveTheme(options = {}, renderer) {
|
|
933
|
-
|
|
1544
|
+
assertThemeOptions(options, [
|
|
1545
|
+
"title",
|
|
1546
|
+
"categories",
|
|
1547
|
+
"color",
|
|
1548
|
+
"gamepad",
|
|
1549
|
+
"backgroundMotion"
|
|
1550
|
+
]);
|
|
1551
|
+
if (options.title !== void 0 && (typeof options.title !== "string" || !options.title)) throw new TypeError("title must be a non-empty string");
|
|
1552
|
+
if (options.color !== void 0 && ![
|
|
1553
|
+
"azure",
|
|
1554
|
+
"violet",
|
|
1555
|
+
"amber",
|
|
1556
|
+
"rose",
|
|
1557
|
+
"jade",
|
|
1558
|
+
"graphite"
|
|
1559
|
+
].includes(options.color)) throw new TypeError("Unsupported Crosswave color");
|
|
1560
|
+
for (const key of ["gamepad", "backgroundMotion"]) if (options[key] !== void 0 && typeof options[key] !== "boolean") throw new TypeError(`${key} must be a boolean`);
|
|
1561
|
+
const ui = resolveCustomization(options);
|
|
1562
|
+
const publishedUi = pickCustomization(options);
|
|
1563
|
+
const categoryLabels = {
|
|
1564
|
+
all: ui.labels.allFiles,
|
|
1565
|
+
folder: ui.labels.categoryFolders,
|
|
1566
|
+
image: ui.labels.categoryPhotos,
|
|
1567
|
+
audio: ui.labels.categoryMusic,
|
|
1568
|
+
video: ui.labels.categoryVideos,
|
|
1569
|
+
document: ui.labels.categoryDocuments,
|
|
1570
|
+
other: ui.labels.categoryOther
|
|
1571
|
+
};
|
|
1572
|
+
const categories = resolveCrosswaveCategories(options.categories ?? defaultCrosswaveCategories.map((category) => ({
|
|
1573
|
+
...category,
|
|
1574
|
+
label: categoryLabels[category.id] ?? category.label
|
|
1575
|
+
})));
|
|
1576
|
+
let resources;
|
|
1577
|
+
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]) => ({
|
|
1578
|
+
script,
|
|
1579
|
+
styles,
|
|
1580
|
+
scriptName: `crosswave-client-${createHash("sha256").update(script).digest("hex").slice(0, 16)}.js`
|
|
1581
|
+
}));
|
|
934
1582
|
const theme = {
|
|
935
1583
|
name: "crosswave",
|
|
936
1584
|
searchIndex: false,
|
|
@@ -942,7 +1590,8 @@ function createCrosswaveTheme(options = {}, renderer) {
|
|
|
942
1590
|
}),
|
|
943
1591
|
async render(context) {
|
|
944
1592
|
const model = {
|
|
945
|
-
...createThemePage(context, theme, options.title ?? "Dirwell"),
|
|
1593
|
+
...createThemePage(context, theme, options.title ?? ui.brand.name ?? "Dirwell"),
|
|
1594
|
+
...publishedUi === void 0 ? {} : { ui: publishedUi },
|
|
946
1595
|
categories: categories.map(({ id, label, svg }) => ({
|
|
947
1596
|
id,
|
|
948
1597
|
label,
|
|
@@ -952,11 +1601,10 @@ function createCrosswaveTheme(options = {}, renderer) {
|
|
|
952
1601
|
gamepad: options.gamepad ?? true,
|
|
953
1602
|
backgroundMotion: options.backgroundMotion ?? true
|
|
954
1603
|
};
|
|
955
|
-
const script
|
|
956
|
-
const scriptName = `crosswave-client-${createHash("sha256").update(script).digest("hex").slice(0, 16)}.js`;
|
|
1604
|
+
const { script, scriptName, styles } = await loadResources();
|
|
957
1605
|
const json = JSON.stringify(model).replaceAll("<", "\\u003c");
|
|
958
1606
|
return {
|
|
959
|
-
html: `<!doctype html><html lang="
|
|
1607
|
+
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>`,
|
|
960
1608
|
assets: {
|
|
961
1609
|
[scriptName]: script,
|
|
962
1610
|
[pageAsset("crosswave", model.path, model.page?.number ?? 1)]: JSON.stringify(model)
|