@dirwell/theme-crosswave 0.0.1 → 0.0.3

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.
@@ -125,7 +125,7 @@ interface ThemePage$1 {
125
125
  readonly title: string;
126
126
  readonly metadataHead?: string;
127
127
  readonly path: string;
128
- readonly rootHref: string;
128
+ readonly rootHref: string | null;
129
129
  readonly parentHref: string | null;
130
130
  readonly parentDataHref: string | null;
131
131
  readonly rootDataHref: string | null;
@@ -133,13 +133,15 @@ interface ThemePage$1 {
133
133
  readonly manifestHref: string | null;
134
134
  readonly listingRevision?: string;
135
135
  readonly updatesHref: string | null;
136
+ readonly searchQueryHref?: string | null;
137
+ readonly searchRefreshIntervalMs?: number;
136
138
  readonly total: number;
137
139
  readonly entries: readonly ListingEntry[];
138
140
  readonly sort: Required<SortOptions>;
139
141
  readonly page: ListingPage | null;
140
142
  readonly breadcrumbs: readonly {
141
143
  readonly name: string;
142
- readonly href: string;
144
+ readonly href: string | null;
143
145
  readonly dataHref: string | null;
144
146
  }[];
145
147
  }
@@ -169,4 +171,4 @@ interface RowContext {
169
171
  }
170
172
  //#endregion
171
173
  export { ThemeAction as a, ThemeLabels as c, ThemePage as i, ListingEntry$1 as n, ThemeColumn as o, RowContext as r, ThemeCustomization as s, FrameworkRenderer as t };
172
- //# sourceMappingURL=model-VZ01cfyg.d.mts.map
174
+ //# sourceMappingURL=model-Bvw3-a6R.d.mts.map
package/dist/model.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as ThemePage, n as ListingEntry, r as RowContext, t as FrameworkRenderer } from "./model-VZ01cfyg.mjs";
1
+ import { i as ThemePage, n as ListingEntry, r as RowContext, t as FrameworkRenderer } from "./model-Bvw3-a6R.mjs";
2
2
  export { FrameworkRenderer, type ListingEntry, RowContext, ThemePage };
@@ -9,6 +9,7 @@ import {
9
9
  watch,
10
10
  nextTick,
11
11
  } from "vue";
12
+ import { queryLiveArchive } from "./shared/live-search.mjs";
12
13
  import { useGamepad } from "@vueuse/core";
13
14
  import { loadListing } from "@dirwell/core/client";
14
15
  import {
@@ -52,6 +53,93 @@ const query = ref(""),
52
53
  viewportHeight = ref(400),
53
54
  retryRequest = shallowRef(null),
54
55
  heights = shallowRef(new Map());
56
+ const archiveOpen = ref(false),
57
+ archiveQuery = ref(""),
58
+ archiveRows = shallowRef([]),
59
+ archiveStatus = ref(""),
60
+ archiveFailed = ref(false),
61
+ archiveTrigger = ref(),
62
+ archiveDialog = ref(),
63
+ archiveInput = ref();
64
+ let archiveAbort,
65
+ archiveRequest = 0,
66
+ archivePending = false,
67
+ archiveDebouncing = false;
68
+ async function searchArchive() {
69
+ const token = ++archiveRequest;
70
+ archiveAbort?.abort();
71
+ const local = (archiveAbort = new AbortController());
72
+ archiveFailed.value = false;
73
+ if (!archiveQuery.value.trim()) {
74
+ archiveRows.value = [];
75
+ archiveStatus.value = ui.value.labels.beginArchive;
76
+ return;
77
+ }
78
+ archiveStatus.value = ui.value.labels.searchingArchive;
79
+ archivePending = true;
80
+ try {
81
+ const result = await queryLiveArchive(
82
+ model.value.searchQueryHref,
83
+ archiveQuery.value.trim(),
84
+ local.signal,
85
+ );
86
+ if (local.signal.aborted || token !== archiveRequest) return;
87
+ archiveRows.value = result.records;
88
+ archiveStatus.value = result.truncated
89
+ ? label(ui.value, "truncatedResults", {
90
+ shown: result.records.length,
91
+ count: result.records.length + "+",
92
+ })
93
+ : label(ui.value, "archiveResults", { count: result.records.length });
94
+ } catch {
95
+ if (local.signal.aborted || token !== archiveRequest) return;
96
+ archiveRows.value = [];
97
+ archiveStatus.value = ui.value.labels.archiveError;
98
+ archiveFailed.value = true;
99
+ } finally {
100
+ if (token === archiveRequest) archivePending = false;
101
+ }
102
+ }
103
+ watch([archiveOpen, archiveQuery], (_, __, cleanup) => {
104
+ archiveAbort?.abort();
105
+ archiveRequest++;
106
+ archivePending = false;
107
+ if (!archiveOpen.value) return;
108
+ archiveDebouncing = true;
109
+ const debounce = setTimeout(() => {
110
+ archiveDebouncing = false;
111
+ searchArchive();
112
+ }, 300);
113
+ cleanup(() => {
114
+ clearTimeout(debounce);
115
+ archiveDebouncing = false;
116
+ });
117
+ });
118
+ watch(archiveOpen, async (open, _, cleanup) => {
119
+ if (!open) {
120
+ await nextTick();
121
+ archiveTrigger.value?.focus();
122
+ return;
123
+ }
124
+ await nextTick();
125
+ if (!archiveOpen.value) return;
126
+ archiveDialog.value?.showModal();
127
+ archiveInput.value?.focus();
128
+ const timer = setInterval(() => {
129
+ if (
130
+ document.visibilityState === "visible" &&
131
+ archiveQuery.value.trim() &&
132
+ !archivePending &&
133
+ !archiveDebouncing
134
+ )
135
+ searchArchive();
136
+ }, model.value.searchRefreshIntervalMs ?? 2000);
137
+ cleanup(() => {
138
+ clearInterval(timer);
139
+ archiveAbort?.abort();
140
+ archiveRequest++;
141
+ });
142
+ });
55
143
  const complete = computed(() => entries.value.length === model.value.total),
56
144
  membership = (entry) =>
57
145
  Array.isArray(entry.presentation?.categories) ? entry.presentation.categories : [];
@@ -186,15 +274,7 @@ let wave,
186
274
  disposed = false,
187
275
  padIdentity = null,
188
276
  motionQuery;
189
- function safeHref(entry) {
190
- return typeof location !== "undefined" &&
191
- location.protocol === "file:" &&
192
- directoryLike(entry) &&
193
- !entry.exitsExplorer &&
194
- entry.href?.endsWith("/")
195
- ? entry.href + "index.html"
196
- : entry.href;
197
- }
277
+
198
278
  async function loadComplete() {
199
279
  if (complete.value || !model.value.manifestHref || location.protocol === "file:") return;
200
280
  const local = abort,
@@ -308,6 +388,7 @@ function navigateLink(event, href, dataHref) {
308
388
  openFolder(dataHref, new URL(href, document.baseURI).href, true);
309
389
  }
310
390
  async function openEntry(event, entry) {
391
+ selected.value = entry.id;
311
392
  saveView();
312
393
  const href = entry.presentation?.folderDataHref;
313
394
  if (
@@ -369,6 +450,7 @@ function parent() {
369
450
  }
370
451
  }
371
452
  function keyboard(event) {
453
+ if (archiveOpen.value) return;
372
454
  if (event.isComposing || event.altKey || event.ctrlKey || event.metaKey) return;
373
455
  if (event.target?.closest?.("[data-dirwell-actions]")) return;
374
456
  const editing = /INPUT|TEXTAREA|SELECT/.test(event.target?.tagName ?? "");
@@ -401,6 +483,7 @@ function keyboard(event) {
401
483
  }
402
484
  }
403
485
  function controller(value) {
486
+ if (archiveOpen.value) return;
404
487
  pad.value = !!value;
405
488
  if (!value) {
406
489
  pressed.clear();
@@ -628,6 +711,7 @@ watchEffect(() => {
628
711
  }
629
712
  });
630
713
  onBeforeUnmount(() => {
714
+ archiveAbort?.abort();
631
715
  disposed = true;
632
716
  document.body.removeAttribute("data-cw-enhanced");
633
717
  abort?.abort();
@@ -725,6 +809,15 @@ onBeforeUnmount(() => {
725
809
  ><span>{{ ui.labels.search }}</span
726
810
  ><input ref="search" type="search" v-model="query" :disabled="!ready || !complete"
727
811
  /></label>
812
+ <button
813
+ class="cw-archive-trigger"
814
+ ref="archiveTrigger"
815
+ v-if="model.searchQueryHref"
816
+ :disabled="!ready"
817
+ @click="archiveOpen = true"
818
+ >
819
+ {{ ui.labels.searchArchive }}
820
+ </button>
728
821
  </div>
729
822
  <div
730
823
  class="cw-list-scroll"
@@ -756,7 +849,7 @@ onBeforeUnmount(() => {
756
849
  <a
757
850
  v-if="entry.href"
758
851
  class="cw-entry"
759
- :href="safeHref(entry)"
852
+ :href="entry.href"
760
853
  :target="entry.exitsExplorer ? '_blank' : undefined"
761
854
  :rel="entry.exitsExplorer ? 'noopener' : undefined"
762
855
  @click="openEntry($event, entry)"
@@ -826,7 +919,7 @@ onBeforeUnmount(() => {
826
919
  !directoryLike(entry) &&
827
920
  !entry.link?.unavailable
828
921
  "
829
- :href="safeHref(entry)"
922
+ :href="entry.href"
830
923
  download
831
924
  >{{ ui.labels.download }}</a
832
925
  >
@@ -914,4 +1007,35 @@ onBeforeUnmount(() => {
914
1007
  {{ paused ? ui.labels.resumeMotion : ui.labels.pauseMotion }}
915
1008
  </button>
916
1009
  </footer>
1010
+ <dialog
1011
+ v-if="archiveOpen"
1012
+ ref="archiveDialog"
1013
+ class="cw-archive"
1014
+ :aria-label="ui.labels.searchArchive"
1015
+ @close="archiveOpen = false"
1016
+ @keydown.esc.prevent.stop="archiveOpen = false"
1017
+ >
1018
+ <h2>{{ ui.labels.searchArchive }}</h2>
1019
+ <input
1020
+ ref="archiveInput"
1021
+ type="search"
1022
+ v-model="archiveQuery"
1023
+ :aria-label="ui.labels.archiveSearchInput"
1024
+ />
1025
+ <p role="status">{{ archiveStatus }}</p>
1026
+ <button v-if="archiveFailed" @click="searchArchive">{{ ui.labels.retry }}</button>
1027
+ <ul>
1028
+ <li v-for="record in archiveRows" :key="record.path">
1029
+ <a
1030
+ v-if="record.href"
1031
+ :href="record.href"
1032
+ :target="record.exitsExplorer ? '_blank' : undefined"
1033
+ :rel="record.exitsExplorer ? 'noopener' : undefined"
1034
+ >{{ record.path }}</a
1035
+ >
1036
+ <span v-else>{{ record.path }}</span>
1037
+ </li>
1038
+ </ul>
1039
+ <button @click="archiveOpen = false">{{ ui.labels.close }}</button>
1040
+ </dialog>
917
1041
  </template>
@@ -0,0 +1,28 @@
1
+ // ../shared-theme/src/live-search.ts
2
+ async function queryLiveArchive(href, query, signal) {
3
+ const url = new URL(href, document.baseURI);
4
+ if (url.origin !== location.origin) throw new Error("Unsafe search endpoint");
5
+ url.searchParams.set("q", query);
6
+ const response = await fetch(url, { signal, cache: "no-store" });
7
+ if (!response.ok) throw new Error("Archive search unavailable");
8
+ const data = await response.json();
9
+ 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")
10
+ throw new Error("Invalid archive results");
11
+ const records = data.results.map((entry) => {
12
+ 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")
13
+ throw new Error("Invalid archive record");
14
+ const target = entry.href === null ? null : new URL(entry.href, url);
15
+ if (target && !["http:", "https:"].includes(target.protocol))
16
+ throw new Error("Unsafe archive link");
17
+ return {
18
+ name: entry.name,
19
+ path: entry.path,
20
+ href: target?.href ?? null,
21
+ exitsExplorer: entry.kind !== "directory"
22
+ };
23
+ });
24
+ return { records, truncated: data.truncated };
25
+ }
26
+ export {
27
+ queryLiveArchive
28
+ };
@@ -402,6 +402,7 @@ async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false,
402
402
  resolveCustomization(data.ui);
403
403
  const result = {
404
404
  ...data,
405
+ searchQueryHref: link(data.searchQueryHref ?? null),
405
406
  updatesHref: link(data.updatesHref ?? null),
406
407
  entries,
407
408
  dataHref: source.href,
package/dist/styles.css CHANGED
@@ -14,7 +14,7 @@
14
14
  .cw-bottom{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:22px 48px;position:fixed;bottom:0;left:0;right:0;background:linear-gradient(0deg,var(--cw-bottom),transparent);min-height:82px}.cw-credits{display:flex;align-items:center;gap:6px;margin:0;font-size:12px;font-weight:400;letter-spacing:normal;color:var(--cw-muted)}.cw-credits a{display:inline-flex;align-items:center;min-height:24px;padding-inline:2px;text-decoration:underline;text-underline-offset:4px;text-decoration-color:#edf4fc66}.cw-credits a:hover{text-decoration-color:currentColor}.cw-key-help{display:flex;gap:20px;flex-wrap:wrap;font-size:11px;color:var(--cw-muted)}kbd{font:inherit;color:#fff;margin-right:5px}.cw-appearance{display:flex;align-items:center;gap:16px;flex:none}.cw-appearance select{background:#0b193b70;border:1px solid #d7e9ff33;padding:7px 24px 7px 10px;border-radius:4px;max-width:116px;font-size:12px;min-height:36px}.cw-appearance button{display:flex;align-items:center;gap:8px;background:transparent;font-size:12px;min-height:40px;padding:6px 0}.cw-appearance button svg{width:16px;height:16px}.cw-controller{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--cw-muted)}.cw-controller svg{width:22px;height:22px}.cw-noscript{padding:0 48px 24px;color:var(--cw-muted);font-size:13px}
15
15
  body:not([data-cw-enhanced]) .cw-browser{padding-left:0}body:not([data-cw-enhanced]) .cw-stage{display:block;margin-top:20px}body:not([data-cw-enhanced]) .cw-list-scroll{max-height:none;min-height:0}body:not([data-cw-enhanced]) .cw-parent-placeholder{display:none}body:not([data-cw-enhanced]) .cw-bottom{display:none}
16
16
  .cw-navigation-notice{position:fixed;z-index:5;left:48px;right:24px;bottom:calc(var(--cw-footer-height,82px) + 8px);background:var(--cw-bottom);color:var(--cw-text);padding:12px 16px;border-radius:4px;font-size:13px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}.cw-navigation-notice[hidden]{display:none}.cw-navigation-notice button,.cw-navigation-notice a{background:none;min-height:44px;padding:6px 12px;border:1px solid var(--cw-muted);border-radius:4px}.cw-transition-layer *{animation:none!important;transition:none!important}.cw-transition-layer [data-cw-status]{display:none}
17
- @view-transition{navigation:auto}::view-transition-old(root),::view-transition-new(root){animation:none}::view-transition-old(cw-background),::view-transition-new(cw-background){animation-duration:420ms}::view-transition-old(cw-browser),::view-transition-old(cw-detail){animation:cw-out 180ms ease-in both}::view-transition-new(cw-browser),::view-transition-new(cw-detail){animation:cw-in 420ms var(--cw-ease) both}::view-transition-old(cw-header),::view-transition-new(cw-header){animation-duration:240ms}@keyframes cw-out{to{opacity:0;transform:translateX(-22px);filter:blur(3px)}}@keyframes cw-in{from{opacity:0;transform:translateX(32px);filter:blur(5px)}}:root[data-cw-transitions=false]::view-transition-group(*){animation-duration:0s}:root[data-cw-transitions=false]::view-transition-old(*),:root[data-cw-transitions=false]::view-transition-new(*){animation:none}.cw-category-changing .cw-files{animation:cw-category-in 260ms var(--cw-ease)}@keyframes cw-category-in{from{opacity:.35;transform:translateX(12px)}}
17
+ @media(scripting:enabled){@view-transition{navigation:auto}}::view-transition-old(root),::view-transition-new(root){animation:none}::view-transition-old(cw-background),::view-transition-new(cw-background){animation-duration:420ms}::view-transition-old(cw-browser),::view-transition-old(cw-detail){animation:cw-out 180ms ease-in both}::view-transition-new(cw-browser),::view-transition-new(cw-detail){animation:cw-in 420ms var(--cw-ease) both}::view-transition-old(cw-header),::view-transition-new(cw-header){animation-duration:240ms}@keyframes cw-out{to{opacity:0;transform:translateX(-22px);filter:blur(3px)}}@keyframes cw-in{from{opacity:0;transform:translateX(32px);filter:blur(5px)}}:root[data-cw-transitions=false]::view-transition-group(*){animation-duration:0s}:root[data-cw-transitions=false]::view-transition-old(*),:root[data-cw-transitions=false]::view-transition-new(*){animation:none}.cw-category-changing .cw-files{animation:cw-category-in 260ms var(--cw-ease)}@keyframes cw-category-in{from{opacity:.35;transform:translateX(12px)}}
18
18
  @media(min-width:1500px){.cw-stage{margin-top:55px}.cw-categories{gap:48px}.cw-category{min-width:112px}.cw-list-scroll{max-height:calc(100svh - 405px)}}
19
19
  @media(max-width:1000px){.cw-top{padding:24px 30px;gap:22px}.cw-stage{padding:0 36px;column-gap:35px}.cw-categories{padding-left:44px;gap:20px}.cw-browser{padding-left:44px}.cw-category{min-width:82px}.cw-list-head{flex-wrap:wrap;gap:8px}.cw-search{margin-left:52px;width:calc(100% - 52px)}.cw-list-scroll{max-height:calc(100svh - 390px)}.cw-bottom{padding:20px 30px}.cw-key-help{gap:12px}.cw-detail h2{font-size:21px}.cw-clock{font-size:12px}}
20
20
  @media(max-width:680px){.cw-top{padding:22px 20px 12px;flex-wrap:wrap;gap:16px;min-height:104px}.cw-brand{font-size:18px}.cw-clock{order:1;font-size:11px}.cw-location{order:2;width:100%;font-size:12px;gap:8px}.cw-stage{padding:0 16px;margin:12px auto 100px;display:block}.cw-rail{margin:0 -8px 8px;padding:12px 8px 20px}.cw-categories{padding-left:0;gap:14px}.cw-browser{padding-left:0}.cw-category{min-width:76px;min-height:83px;padding:10px 2px;gap:10px}.cw-category svg{width:33px;height:33px}.cw-category span{font-size:12px}.cw-category small{right:5px}.cw-list-head{padding:0 12px;margin-bottom:8px}.cw-directory{gap:12px}.cw-directory h2{font-size:16px}.cw-search{margin-left:46px;width:calc(100% - 46px);min-height:44px}.cw-search input,.cw-search button{min-height:44px}.cw-list-scroll{max-height:none;min-height:150px;padding:8px 2px 18px}.cw-entry{gap:20px;padding:14px 12px;min-height:78px}.cw-entry-icon{width:36px;min-width:36px}.cw-entry-icon svg{width:29px;height:29px}.cw-name{font-size:17px}.cw-detail{display:none!important}.cw-row-date{display:block}.cw-bottom{padding:16px 20px;min-height:76px;gap:12px}.cw-key-help{display:none!important}.cw-appearance{width:100%;justify-content:space-between}.cw-appearance select,.cw-appearance button{min-height:44px}.cw-controller{position:absolute;bottom:calc(var(--cw-footer-height,76px) + 4px);right:20px;background:var(--cw-bottom);padding:6px 9px;border-radius:4px;max-width:calc(100% - 40px)}.cw-notice{left:20px;right:20px;bottom:calc(var(--cw-footer-height,82px) + 8px)}.cw-noscript{padding:0 24px 24px}}
@@ -22,6 +22,24 @@ body:not([data-cw-enhanced]) .cw-browser{padding-left:0}body:not([data-cw-enhanc
22
22
  .cw-list-scroll[data-virtual=true]{max-height:var(--cw-list-height,calc(100svh - 365px));min-height:160px}.cw-stage{margin-bottom:calc(var(--cw-footer-height,82px) + 24px)}
23
23
  @media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;animation:none!important;transition:none!important}::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}.cw-row.cw-selected .cw-entry,.cw-category.cw-active,.cw-category:hover{transform:none}}
24
24
 
25
+ .cw-archive-trigger { min-height: 44px; padding: 8px 12px; color: var(--cw-text); background: transparent; border: 1px solid var(--cw-muted); border-radius: 4px; }
26
+ .cw-archive {
27
+ width: min(42rem, calc(100vw - 2rem));
28
+ max-height: calc(100dvh - 2rem);
29
+ overflow: auto;
30
+ padding: 1.5rem;
31
+ color: var(--cw-text);
32
+ background: var(--cw-bottom);
33
+ border: 1px solid var(--cw-muted);
34
+ border-radius: 1rem;
35
+ }
36
+ .cw-archive::backdrop { background: #000a; }
37
+ .cw-archive input { width: 100%; min-height: 2.75rem; }
38
+ .cw-archive a { color: var(--cw-muted); overflow-wrap: anywhere; }
39
+ .cw-archive button { min-height: 2.75rem; margin-block: .5rem; }
40
+ .cw-archive li { margin-block: .75rem; }
41
+
42
+
25
43
  .cw-directory{flex-wrap:wrap;gap:.35rem}.cw-list-head h2{font-size:17px;font-weight:400;margin:0}.cw-list-head [data-cw-count]{font-size:12px;color:var(--cw-muted)}
26
44
  .cw-motion{display:flex;align-items:center;background:transparent;border:1px solid #d7e9ff33;border-radius:4px;font-size:12px;min-height:44px;padding:6px 12px;white-space:nowrap}
27
45
  .cw-appearance{min-width:0}.cw-appearance select{max-width:116px}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dirwell/theme-crosswave",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Vue-owned media navigation with VueUse gamepad support.",
5
5
  "homepage": "https://vp-tw.github.io/dirwell/themes/",
6
6
  "bugs": {
@@ -38,7 +38,7 @@
38
38
  "tag": "latest"
39
39
  },
40
40
  "dependencies": {
41
- "@dirwell/core": "0.0.1",
41
+ "@dirwell/core": "0.0.3",
42
42
  "@vue/server-renderer": "3.5.43",
43
43
  "@vueuse/core": "15.0.0",
44
44
  "mime": "4.1.0",