@dirwell/theme-crosswave 0.0.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 +3 -1
- package/dist/client.js +16 -16
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +128 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{model-VZ01cfyg.d.mts → model-Dr7YgDTv.d.mts} +3 -1
- package/dist/model.d.mts +1 -1
- package/dist/source/Crosswave.vue +131 -0
- package/dist/source/shared/live-search.mjs +28 -0
- package/dist/source/shared/navigation.mjs +1 -0
- package/dist/styles.css +18 -0
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ThemeAction, c as ThemeLabels, i as ThemePage, o as ThemeColumn, s as ThemeCustomization, t as FrameworkRenderer } from "./model-
|
|
1
|
+
import { a as ThemeAction, c as ThemeLabels, i as ThemePage, o as ThemeColumn, s as ThemeCustomization, t as FrameworkRenderer } from "./model-Dr7YgDTv.mjs";
|
|
2
2
|
import { ExplorerTheme, ListingEntry } from "@dirwell/core";
|
|
3
3
|
//#region src/icons.d.ts
|
|
4
4
|
type CrosswaveIcon = "all" | "folder" | "image" | "audio" | "video" | "document" | "other" | "link" | "back" | "search" | "pause" | "play" | "controller";
|
|
@@ -26,6 +26,7 @@ export interface CrosswaveThemeOptions extends ThemeCustomization {
|
|
|
26
26
|
readonly categories?: readonly CrosswaveCategory[];
|
|
27
27
|
readonly color?: "azure" | "violet" | "amber" | "rose" | "jade" | "graphite";
|
|
28
28
|
readonly gamepad?: boolean;
|
|
29
|
+
readonly archiveSearch?: boolean;
|
|
29
30
|
readonly backgroundMotion?: boolean;
|
|
30
31
|
}
|
|
31
32
|
export declare function createCrosswaveTheme(options?: CrosswaveThemeOptions, renderer?: FrameworkRenderer): ExplorerTheme;
|
package/dist/index.mjs
CHANGED
|
@@ -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,34 @@ 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
|
|
258
290
|
//#region ../shared-theme/src/customization.ts
|
|
259
291
|
/** Small serializable inputs shared by official themes; layout stays theme-owned. */
|
|
260
292
|
const defaultLabels = {
|
|
@@ -559,6 +591,7 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
|
|
|
559
591
|
if (data.ui !== void 0) resolveCustomization(data.ui);
|
|
560
592
|
const result = {
|
|
561
593
|
...data,
|
|
594
|
+
searchQueryHref: link(data.searchQueryHref ?? null),
|
|
562
595
|
updatesHref: link(data.updatesHref ?? null),
|
|
563
596
|
entries,
|
|
564
597
|
dataHref: source.href,
|
|
@@ -955,6 +988,71 @@ var App_server_default = {
|
|
|
955
988
|
setup(__props) {
|
|
956
989
|
const props = __props, model = shallowRef(restoreThemePage(props.model, Boolean(import.meta.hot))), entries = shallowRef(model.value.entries), ui = computed(() => resolveCustomization(model.value.ui));
|
|
957
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
|
+
});
|
|
958
1056
|
const complete = computed(() => entries.value.length === model.value.total), membership = (entry) => Array.isArray(entry.presentation?.categories) ? entry.presentation.categories : [];
|
|
959
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);
|
|
960
1058
|
const nodes = /* @__PURE__ */ new Map();
|
|
@@ -1156,6 +1254,7 @@ var App_server_default = {
|
|
|
1156
1254
|
}
|
|
1157
1255
|
}
|
|
1158
1256
|
function keyboard(event) {
|
|
1257
|
+
if (archiveOpen.value) return;
|
|
1159
1258
|
if (event.isComposing || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
1160
1259
|
if (event.target?.closest?.("[data-dirwell-actions]")) return;
|
|
1161
1260
|
if (/INPUT|TEXTAREA|SELECT/.test(event.target?.tagName ?? "")) {
|
|
@@ -1186,6 +1285,7 @@ var App_server_default = {
|
|
|
1186
1285
|
}
|
|
1187
1286
|
}
|
|
1188
1287
|
function controller(value) {
|
|
1288
|
+
if (archiveOpen.value) return;
|
|
1189
1289
|
pad.value = !!value;
|
|
1190
1290
|
if (!value) {
|
|
1191
1291
|
pressed.clear();
|
|
@@ -1384,6 +1484,7 @@ var App_server_default = {
|
|
|
1384
1484
|
}
|
|
1385
1485
|
});
|
|
1386
1486
|
onBeforeUnmount(() => {
|
|
1487
|
+
archiveAbort?.abort();
|
|
1387
1488
|
disposed = true;
|
|
1388
1489
|
document.body.removeAttribute("data-cw-enhanced");
|
|
1389
1490
|
abort?.abort();
|
|
@@ -1418,7 +1519,10 @@ var App_server_default = {
|
|
|
1418
1519
|
ssrRenderList(model.value.categories, (item) => {
|
|
1419
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>`);
|
|
1420
1521
|
});
|
|
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
|
|
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">`);
|
|
1422
1526
|
if (model.value.parentHref) _push(`<li class="cw-row"><a${ssrRenderAttr("href", model.value.parentHref)} class="cw-entry">../</a></li>`);
|
|
1423
1527
|
else _push(`<!---->`);
|
|
1424
1528
|
if (start.value) _push(`<li aria-hidden="true" style="${ssrRenderStyle({ height: offsets.value[start.value] + "px" })}"></li>`);
|
|
@@ -1531,7 +1635,21 @@ var App_server_default = {
|
|
|
1531
1635
|
ssrRenderList(Object.keys(colors), (name) => {
|
|
1532
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>`);
|
|
1533
1637
|
});
|
|
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
|
|
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(`<!--]-->`);
|
|
1535
1653
|
};
|
|
1536
1654
|
}
|
|
1537
1655
|
};
|
|
@@ -1546,7 +1664,8 @@ function createCrosswaveTheme(options = {}, renderer) {
|
|
|
1546
1664
|
"categories",
|
|
1547
1665
|
"color",
|
|
1548
1666
|
"gamepad",
|
|
1549
|
-
"backgroundMotion"
|
|
1667
|
+
"backgroundMotion",
|
|
1668
|
+
"archiveSearch"
|
|
1550
1669
|
]);
|
|
1551
1670
|
if (options.title !== void 0 && (typeof options.title !== "string" || !options.title)) throw new TypeError("title must be a non-empty string");
|
|
1552
1671
|
if (options.color !== void 0 && ![
|
|
@@ -1557,7 +1676,11 @@ function createCrosswaveTheme(options = {}, renderer) {
|
|
|
1557
1676
|
"jade",
|
|
1558
1677
|
"graphite"
|
|
1559
1678
|
].includes(options.color)) throw new TypeError("Unsupported Crosswave color");
|
|
1560
|
-
for (const key of [
|
|
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`);
|
|
1561
1684
|
const ui = resolveCustomization(options);
|
|
1562
1685
|
const publishedUi = pickCustomization(options);
|
|
1563
1686
|
const categoryLabels = {
|
|
@@ -1592,6 +1715,7 @@ function createCrosswaveTheme(options = {}, renderer) {
|
|
|
1592
1715
|
const model = {
|
|
1593
1716
|
...createThemePage(context, theme, options.title ?? ui.brand.name ?? "Dirwell"),
|
|
1594
1717
|
...publishedUi === void 0 ? {} : { ui: publishedUi },
|
|
1718
|
+
searchQueryHref: options.archiveSearch === false ? null : context.searchQueryHref ?? null,
|
|
1595
1719
|
categories: categories.map(({ id, label, svg }) => ({
|
|
1596
1720
|
id,
|
|
1597
1721
|
label,
|