Fix search not working
This commit is contained in:
parent
f229bb978e
commit
a6589fb1f3
@ -31,7 +31,7 @@ src/
|
|||||||
|
|
||||||
## Key Design Decisions
|
## Key Design Decisions
|
||||||
|
|
||||||
- **Svelte 5 runes** — Use `$state`, `$derived`, `$effect`. Props-based event passing (no Svelte events).
|
- **Svelte 5 runes** — Use `$state`, `$derived`, `$effect`. Props-based event passing (no Svelte events). Event handler attributes are all lower-case (e.g. oninput)
|
||||||
- **No external crypto libraries** — Uses the browser's native Web Crypto API exclusively.
|
- **No external crypto libraries** — Uses the browser's native Web Crypto API exclusively.
|
||||||
- **Key never persisted** — Encryption key lives only in `$state` memory; cleared on lock, tab switch, or page close.
|
- **Key never persisted** — Encryption key lives only in `$state` memory; cleared on lock, tab switch, or page close.
|
||||||
- **Single-file build** — `vite-plugin-singlefile` inlines all JS/CSS; post-build script inlines favicon.
|
- **Single-file build** — `vite-plugin-singlefile` inlines all JS/CSS; post-build script inlines favicon.
|
||||||
|
|||||||
34
dist/index.html
vendored
34
dist/index.html
vendored
@ -5763,6 +5763,7 @@ function LockScreen($$anchor, $$props) {
|
|||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
//#region src/lib/stores/search.svelte.js
|
//#region src/lib/stores/search.svelte.js
|
||||||
|
var DEBOUNCE_MS = 300;
|
||||||
var SearchStore = class {
|
var SearchStore = class {
|
||||||
#query = /* @__PURE__ */ state("");
|
#query = /* @__PURE__ */ state("");
|
||||||
get query() {
|
get query() {
|
||||||
@ -5771,6 +5772,13 @@ var SearchStore = class {
|
|||||||
set query(value) {
|
set query(value) {
|
||||||
set(this.#query, value, true);
|
set(this.#query, value, true);
|
||||||
}
|
}
|
||||||
|
#debouncedQuery = /* @__PURE__ */ state("");
|
||||||
|
get debouncedQuery() {
|
||||||
|
return get(this.#debouncedQuery);
|
||||||
|
}
|
||||||
|
set debouncedQuery(value) {
|
||||||
|
set(this.#debouncedQuery, value, true);
|
||||||
|
}
|
||||||
#activeGroupId = /* @__PURE__ */ state("all");
|
#activeGroupId = /* @__PURE__ */ state("all");
|
||||||
get activeGroupId() {
|
get activeGroupId() {
|
||||||
return get(this.#activeGroupId);
|
return get(this.#activeGroupId);
|
||||||
@ -5785,8 +5793,21 @@ var SearchStore = class {
|
|||||||
set refreshTrigger(value) {
|
set refreshTrigger(value) {
|
||||||
set(this.#refreshTrigger, value, true);
|
set(this.#refreshTrigger, value, true);
|
||||||
}
|
}
|
||||||
|
#debounceTimer = null;
|
||||||
|
setSearchQuery(value) {
|
||||||
|
this.query = value;
|
||||||
|
if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
|
||||||
|
if (value === "") {
|
||||||
|
this.debouncedQuery = "";
|
||||||
|
this.#debounceTimer = null;
|
||||||
|
} else this.#debounceTimer = setTimeout(() => {
|
||||||
|
this.debouncedQuery = value;
|
||||||
|
this.#debounceTimer = null;
|
||||||
|
}, DEBOUNCE_MS);
|
||||||
|
}
|
||||||
clear() {
|
clear() {
|
||||||
this.query = "";
|
this.query = "";
|
||||||
|
this.debouncedQuery = "";
|
||||||
this.activeGroupId = "all";
|
this.activeGroupId = "all";
|
||||||
}
|
}
|
||||||
/** Force subscribed components to re-fetch data. */
|
/** Force subscribed components to re-fetch data. */
|
||||||
@ -6007,13 +6028,13 @@ function Sidebar($$anchor, $$props) {
|
|||||||
set_value(input, search.query);
|
set_value(input, search.query);
|
||||||
set_class(button, 1, `group-item ${search.activeGroupId === "all" ? "active" : ""}`, "svelte-181dlmc");
|
set_class(button, 1, `group-item ${search.activeGroupId === "all" ? "active" : ""}`, "svelte-181dlmc");
|
||||||
});
|
});
|
||||||
event("Input", input, (e) => search.query = e.target.value);
|
delegated("input", input, (e) => search.setSearchQuery(e.target.value));
|
||||||
delegated("click", button, () => search.activeGroupId = "all");
|
delegated("click", button, () => search.activeGroupId = "all");
|
||||||
delegated("click", button_4, () => openGroupForm(null));
|
delegated("click", button_4, () => openGroupForm(null));
|
||||||
append($$anchor, div);
|
append($$anchor, div);
|
||||||
pop();
|
pop();
|
||||||
}
|
}
|
||||||
delegate(["click"]);
|
delegate(["input", "click"]);
|
||||||
//#endregion
|
//#endregion
|
||||||
//#region src/components/EntryList.svelte
|
//#region src/components/EntryList.svelte
|
||||||
var root_1$6 = /* @__PURE__ */ from_html(`<div class="loading svelte-13s7gu4">Loading entries...</div>`);
|
var root_1$6 = /* @__PURE__ */ from_html(`<div class="loading svelte-13s7gu4">Loading entries...</div>`);
|
||||||
@ -6034,7 +6055,7 @@ function EntryList($$anchor, $$props) {
|
|||||||
set(loading, true);
|
set(loading, true);
|
||||||
set(error, "");
|
set(error, "");
|
||||||
try {
|
try {
|
||||||
const query = search.query.trim();
|
const query = search.debouncedQuery.trim();
|
||||||
const groupId = search.activeGroupId;
|
const groupId = search.activeGroupId;
|
||||||
if (query) set(entries, await searchEntries(query, groupId !== "all" ? { groupId } : {}), true);
|
if (query) set(entries, await searchEntries(query, groupId !== "all" ? { groupId } : {}), true);
|
||||||
else if (groupId !== "all") set(entries, await getEntries({ groupId }), true);
|
else if (groupId !== "all") set(entries, await getEntries({ groupId }), true);
|
||||||
@ -6046,7 +6067,7 @@ function EntryList($$anchor, $$props) {
|
|||||||
set(loading, false);
|
set(loading, false);
|
||||||
}
|
}
|
||||||
user_effect(() => {
|
user_effect(() => {
|
||||||
search.query;
|
search.debouncedQuery;
|
||||||
search.activeGroupId;
|
search.activeGroupId;
|
||||||
search.refreshTrigger;
|
search.refreshTrigger;
|
||||||
loadEntries();
|
loadEntries();
|
||||||
@ -6883,10 +6904,7 @@ function MainLayout($$anchor, $$props) {
|
|||||||
if (get(sidebarOpen)) $$render(consequent);
|
if (get(sidebarOpen)) $$render(consequent);
|
||||||
});
|
});
|
||||||
var aside = sibling(node, 2);
|
var aside = sibling(node, 2);
|
||||||
Sidebar(child(aside), { $$events: {
|
Sidebar(child(aside), {});
|
||||||
back: handleBack,
|
|
||||||
goList
|
|
||||||
} });
|
|
||||||
reset(aside);
|
reset(aside);
|
||||||
var main = sibling(aside, 2);
|
var main = sibling(aside, 2);
|
||||||
var div_2 = child(main);
|
var div_2 = child(main);
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
loading = true
|
loading = true
|
||||||
error = ''
|
error = ''
|
||||||
try {
|
try {
|
||||||
const query = searchStore.query.trim()
|
const query = searchStore.debouncedQuery.trim()
|
||||||
const groupId = searchStore.activeGroupId
|
const groupId = searchStore.activeGroupId
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
@ -35,9 +35,9 @@
|
|||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload when search query, active group filter, or refresh trigger changes
|
// Reload when debounced search query, active group filter, or refresh trigger changes
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
searchStore.query
|
searchStore.debouncedQuery
|
||||||
searchStore.activeGroupId
|
searchStore.activeGroupId
|
||||||
searchStore.refreshTrigger
|
searchStore.refreshTrigger
|
||||||
loadEntries()
|
loadEntries()
|
||||||
|
|||||||
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="sidebar {sidebarOpen ? 'open' : ''}">
|
<aside class="sidebar {sidebarOpen ? 'open' : ''}">
|
||||||
<Sidebar on:back={handleBack} on:goList={goList} />
|
<Sidebar />
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- Main content -->
|
<!-- Main content -->
|
||||||
|
|||||||
@ -96,7 +96,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Search entries..."
|
placeholder="Search entries..."
|
||||||
value={searchStore.query}
|
value={searchStore.query}
|
||||||
onInput={(e) => searchStore.query = e.target.value}
|
oninput={(e) => searchStore.setSearchQuery(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,36 @@
|
|||||||
* Shared between Sidebar and EntryList for coordinated filtering.
|
* Shared between Sidebar and EntryList for coordinated filtering.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const DEBOUNCE_MS = 300
|
||||||
|
|
||||||
export class SearchStore {
|
export class SearchStore {
|
||||||
query = $state('')
|
query = $state('') // raw input value — bound to the search input
|
||||||
|
debouncedQuery = $state('') // debounced value — used for actual search
|
||||||
activeGroupId = $state('all') // 'all' or a group id
|
activeGroupId = $state('all') // 'all' or a group id
|
||||||
refreshTrigger = $state(0) // incremented to force a re-fetch
|
refreshTrigger = $state(0) // incremented to force a re-fetch
|
||||||
|
#debounceTimer = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the search query with debouncing.
|
||||||
|
* Call this from the input handler instead of setting `query` directly.
|
||||||
|
*/
|
||||||
|
setSearchQuery(value) {
|
||||||
|
this.query = value
|
||||||
|
if (this.#debounceTimer) clearTimeout(this.#debounceTimer)
|
||||||
|
if (value === '') {
|
||||||
|
this.debouncedQuery = ''
|
||||||
|
this.#debounceTimer = null
|
||||||
|
} else {
|
||||||
|
this.#debounceTimer = setTimeout(() => {
|
||||||
|
this.debouncedQuery = value
|
||||||
|
this.#debounceTimer = null
|
||||||
|
}, DEBOUNCE_MS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.query = ''
|
this.query = ''
|
||||||
|
this.debouncedQuery = ''
|
||||||
this.activeGroupId = 'all'
|
this.activeGroupId = 'all'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,12 +18,12 @@ describe('SearchStore', () => {
|
|||||||
|
|
||||||
describe('query', () => {
|
describe('query', () => {
|
||||||
it('should update query', () => {
|
it('should update query', () => {
|
||||||
search.query = 'test'
|
search.setSearchQuery('test')
|
||||||
expect(search.query).toBe('test')
|
expect(search.query).toBe('test')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle unicode query', () => {
|
it('should handle unicode query', () => {
|
||||||
search.query = 'тест'
|
search.setSearchQuery('тест')
|
||||||
expect(search.query).toBe('тест')
|
expect(search.query).toBe('тест')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -48,7 +48,7 @@ describe('SearchStore', () => {
|
|||||||
|
|
||||||
describe('clear()', () => {
|
describe('clear()', () => {
|
||||||
it('should reset query to empty string', () => {
|
it('should reset query to empty string', () => {
|
||||||
search.query = 'some search'
|
search.setSearchQuery('some search')
|
||||||
search.clear()
|
search.clear()
|
||||||
expect(search.query).toBe('')
|
expect(search.query).toBe('')
|
||||||
})
|
})
|
||||||
@ -60,7 +60,7 @@ describe('SearchStore', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should reset both fields simultaneously', () => {
|
it('should reset both fields simultaneously', () => {
|
||||||
search.query = 'some search'
|
search.setSearchQuery('some search')
|
||||||
search.activeGroupId = 'group-123'
|
search.activeGroupId = 'group-123'
|
||||||
search.clear()
|
search.clear()
|
||||||
expect(search.query).toBe('')
|
expect(search.query).toBe('')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user