mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 22:06:17 +00:00
ci: migration of search history from localStorage to LevelDB and highlighting fix
This commit is contained in:
@@ -15,6 +15,8 @@ import { AutoUpdateSubHeader } from "./auto-update-sub-header";
|
||||
import { setFilters, setLibrarySearchQuery } from "@renderer/features";
|
||||
import cn from "classnames";
|
||||
import { SearchDropdown } from "@renderer/components";
|
||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
const pathTitle: Record<string, string> = {
|
||||
"/": "home",
|
||||
@@ -161,11 +163,11 @@ export function Header() {
|
||||
const handleSelectSuggestion = (suggestion: {
|
||||
title: string;
|
||||
objectId: string;
|
||||
shop: string;
|
||||
shop: GameShop;
|
||||
}) => {
|
||||
setIsDropdownVisible(false);
|
||||
inputRef.current?.blur();
|
||||
navigate(`/game/${suggestion.shop}/${suggestion.objectId}`);
|
||||
navigate(buildGameDetailsPath(suggestion));
|
||||
};
|
||||
|
||||
const handleClearSearch = () => {
|
||||
|
||||
@@ -19,24 +19,25 @@ export function HighlightText({ text, query }: Readonly<HighlightTextProps>) {
|
||||
return <>{text}</>;
|
||||
}
|
||||
|
||||
const textWords = text.split(/\b/);
|
||||
const matches: { start: number; end: number; text: string }[] = [];
|
||||
const matches: { start: number; end: number }[] = [];
|
||||
const textLower = text.toLowerCase();
|
||||
|
||||
let currentIndex = 0;
|
||||
textWords.forEach((word) => {
|
||||
const wordLower = word.toLowerCase();
|
||||
queryWords.forEach((queryWord) => {
|
||||
const escapedQuery = queryWord.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regex = new RegExp(
|
||||
`(?:^|[\\s])${escapedQuery}(?=[\\s]|$)|^${escapedQuery}$`,
|
||||
"gi"
|
||||
);
|
||||
|
||||
queryWords.forEach((queryWord) => {
|
||||
if (wordLower === queryWord) {
|
||||
matches.push({
|
||||
start: currentIndex,
|
||||
end: currentIndex + word.length,
|
||||
text: word,
|
||||
});
|
||||
}
|
||||
});
|
||||
let match;
|
||||
while ((match = regex.exec(textLower)) !== null) {
|
||||
const matchedText = match[0];
|
||||
const leadingSpace = matchedText.startsWith(" ") ? 1 : 0;
|
||||
const start = match.index + leadingSpace;
|
||||
const end = start + queryWord.length;
|
||||
|
||||
currentIndex += word.length;
|
||||
matches.push({ start, end });
|
||||
}
|
||||
});
|
||||
|
||||
if (matches.length === 0) {
|
||||
@@ -46,16 +47,14 @@ export function HighlightText({ text, query }: Readonly<HighlightTextProps>) {
|
||||
matches.sort((a, b) => a.start - b.start);
|
||||
|
||||
const mergedMatches: { start: number; end: number }[] = [];
|
||||
|
||||
if (matches.length === 0) {
|
||||
return <>{text}</>;
|
||||
}
|
||||
|
||||
let current = matches[0];
|
||||
|
||||
for (let i = 1; i < matches.length; i++) {
|
||||
if (matches[i].start <= current.end) {
|
||||
current.end = Math.max(current.end, matches[i].end);
|
||||
current = {
|
||||
start: current.start,
|
||||
end: Math.max(current.end, matches[i].end),
|
||||
};
|
||||
} else {
|
||||
mergedMatches.push(current);
|
||||
current = matches[i];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useState, useCallback, useEffect, useRef } from "react";
|
||||
import { levelDBService } from "@renderer/services/leveldb.service";
|
||||
|
||||
export interface SearchHistoryEntry {
|
||||
query: string;
|
||||
@@ -6,22 +7,47 @@ export interface SearchHistoryEntry {
|
||||
context: "library" | "catalogue";
|
||||
}
|
||||
|
||||
const STORAGE_KEY = "search-history";
|
||||
const LEVELDB_KEY = "searchHistory";
|
||||
const LEGACY_STORAGE_KEY = "search-history";
|
||||
const MAX_HISTORY_ENTRIES = 15;
|
||||
|
||||
export function useSearchHistory() {
|
||||
const [history, setHistory] = useState<SearchHistoryEntry[]>([]);
|
||||
const isInitialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
const loadHistory = async () => {
|
||||
if (isInitialized.current) return;
|
||||
isInitialized.current = true;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(stored) as SearchHistoryEntry[];
|
||||
setHistory(parsed);
|
||||
let data = (await levelDBService.get(LEVELDB_KEY, null, "json")) as
|
||||
| SearchHistoryEntry[]
|
||||
| null;
|
||||
|
||||
if (!data) {
|
||||
const legacyData = localStorage.getItem(LEGACY_STORAGE_KEY);
|
||||
if (legacyData) {
|
||||
try {
|
||||
const parsed = JSON.parse(legacyData) as SearchHistoryEntry[];
|
||||
await levelDBService.put(LEVELDB_KEY, parsed, null, "json");
|
||||
localStorage.removeItem(LEGACY_STORAGE_KEY);
|
||||
data = parsed;
|
||||
} catch {
|
||||
localStorage.removeItem(LEGACY_STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setHistory(data);
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
setHistory([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadHistory();
|
||||
}, []);
|
||||
|
||||
const addToHistory = useCallback(
|
||||
@@ -39,7 +65,7 @@ export function useSearchHistory() {
|
||||
(entry) => entry.query.toLowerCase() !== query.toLowerCase().trim()
|
||||
);
|
||||
const updated = [newEntry, ...filtered].slice(0, MAX_HISTORY_ENTRIES);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
|
||||
levelDBService.put(LEVELDB_KEY, updated, null, "json");
|
||||
return updated;
|
||||
});
|
||||
},
|
||||
@@ -49,14 +75,14 @@ export function useSearchHistory() {
|
||||
const removeFromHistory = useCallback((query: string) => {
|
||||
setHistory((prev) => {
|
||||
const updated = prev.filter((entry) => entry.query !== query);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
|
||||
levelDBService.put(LEVELDB_KEY, updated, null, "json");
|
||||
return updated;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const clearHistory = useCallback(() => {
|
||||
setHistory([]);
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
levelDBService.del(LEVELDB_KEY, null);
|
||||
}, []);
|
||||
|
||||
const getRecentHistory = useCallback(
|
||||
|
||||
@@ -2,11 +2,12 @@ import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useAppSelector } from "./redux";
|
||||
import { debounce } from "lodash-es";
|
||||
import { logger } from "@renderer/logger";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
export interface SearchSuggestion {
|
||||
title: string;
|
||||
objectId: string;
|
||||
shop: string;
|
||||
shop: GameShop;
|
||||
iconUrl: string | null;
|
||||
source: "library" | "catalogue";
|
||||
}
|
||||
@@ -89,7 +90,7 @@ export function useSearchSuggestions(
|
||||
{
|
||||
title: string;
|
||||
objectId: string;
|
||||
shop: string;
|
||||
shop: GameShop;
|
||||
iconUrl: string | null;
|
||||
}[]
|
||||
>("/catalogue/search/suggestions", {
|
||||
|
||||
Reference in New Issue
Block a user