aboutsummaryrefslogtreecommitdiffstats
path: root/packages/shared-react/hooks
diff options
context:
space:
mode:
authorlexafaxine <40200356+lexafaxine@users.noreply.github.com>2025-07-14 09:00:36 +0900
committerGitHub <noreply@github.com>2025-07-14 01:00:36 +0100
commit39fcda015b467be6c08d134fd45ec94204b08a09 (patch)
treece78ec11e2cbbb349ec7d02ba69fad8fe16e19a1 /packages/shared-react/hooks
parentecb13cec5d5c646308b34c714401a716f3cdf199 (diff)
downloadkarakeep-39fcda015b467be6c08d134fd45ec94204b08a09.tar.zst
feat: adding search history #1541 (#1627)
* feat: adding search history * fix popover should close when no matched history * remove unnecessary react import * replace current Input component with CommandInput for better UX * add i18n for recent searches label * fix bug * refactor local storage logic to make code reusable * using zod schema to validate search history and revert debounce change * Consolidate some of the files --------- Co-authored-by: Mohamed Bassem <me@mbassem.com>
Diffstat (limited to 'packages/shared-react/hooks')
-rw-r--r--packages/shared-react/hooks/search-history.ts102
1 files changed, 102 insertions, 0 deletions
diff --git a/packages/shared-react/hooks/search-history.ts b/packages/shared-react/hooks/search-history.ts
new file mode 100644
index 00000000..bc3c4e3d
--- /dev/null
+++ b/packages/shared-react/hooks/search-history.ts
@@ -0,0 +1,102 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { z } from "zod";
+
+const searchHistorySchema = z.array(z.string());
+
+const BOOKMARK_SEARCH_HISTORY_KEY = "karakeep_search_history";
+const MAX_STORED_ITEMS = 50;
+
+class SearchHistoryUtil {
+ constructor(
+ private storage: {
+ getItem(key: string): Promise<string | null> | string | null;
+ setItem(key: string, value: string): Promise<void> | void;
+ removeItem(key: string): Promise<void> | void;
+ },
+ ) {}
+
+ async getSearchHistory(): Promise<string[]> {
+ try {
+ const rawHistory = await this.storage.getItem(
+ BOOKMARK_SEARCH_HISTORY_KEY,
+ );
+ if (rawHistory) {
+ const parsed = JSON.parse(rawHistory) as unknown;
+ const result = searchHistorySchema.safeParse(parsed);
+ if (result.success) {
+ return result.data;
+ }
+ }
+ return [];
+ } catch (error) {
+ console.error("Failed to parse search history:", error);
+ return [];
+ }
+ }
+
+ async addSearchTermToHistory(term: string): Promise<void> {
+ if (!term || term.trim().length === 0) {
+ return;
+ }
+ try {
+ const currentHistory = await this.getSearchHistory();
+ const filteredHistory = currentHistory.filter(
+ (item) => item.toLowerCase() !== term.toLowerCase(),
+ );
+ const newHistory = [term, ...filteredHistory];
+ const finalHistory = newHistory.slice(0, MAX_STORED_ITEMS);
+ await this.storage.setItem(
+ BOOKMARK_SEARCH_HISTORY_KEY,
+ JSON.stringify(finalHistory),
+ );
+ } catch (error) {
+ console.error("Failed to save search history:", error);
+ }
+ }
+
+ async clearSearchHistory(): Promise<void> {
+ try {
+ await this.storage.removeItem(BOOKMARK_SEARCH_HISTORY_KEY);
+ } catch (error) {
+ console.error("Failed to clear search history:", error);
+ }
+ }
+}
+
+export function useSearchHistory(adapter: {
+ getItem(key: string): Promise<string | null> | string | null;
+ setItem(key: string, value: string): Promise<void> | void;
+ removeItem(key: string): Promise<void> | void;
+}) {
+ const [history, setHistory] = useState<string[]>([]);
+ const searchHistoryUtil = useMemo(() => new SearchHistoryUtil(adapter), []);
+
+ const loadHistory = useCallback(async () => {
+ const storedHistory = await searchHistoryUtil.getSearchHistory();
+ setHistory(storedHistory);
+ }, [searchHistoryUtil]);
+
+ useEffect(() => {
+ loadHistory();
+ }, [loadHistory]);
+
+ const addTerm = useCallback(
+ async (term: string) => {
+ await searchHistoryUtil.addSearchTermToHistory(term);
+ await loadHistory();
+ },
+ [searchHistoryUtil, loadHistory],
+ );
+
+ const clearHistory = useCallback(async () => {
+ await searchHistoryUtil.clearSearchHistory();
+ setHistory([]);
+ }, [searchHistoryUtil]);
+
+ return {
+ history,
+ addTerm,
+ clearHistory,
+ refreshHistory: loadHistory,
+ };
+}