diff --git a/app.vue b/app.vue index b6025e6..6b9f531 100644 --- a/app.vue +++ b/app.vue @@ -38,25 +38,25 @@ onMounted(async () => { @font-face { font-family: SFRoundedRegular; font-display: swap; - src: url("~/assets/fonts/SFRoundedRegular.otf") format("opentype"); + src: url("/fonts/SFRoundedRegular.otf") format("opentype"); } @font-face { font-family: SFRoundedMedium; font-display: swap; - src: url("~/assets/fonts/SFRoundedMedium.otf") format("opentype"); + src: url("/fonts/SFRoundedMedium.otf") format("opentype"); } @font-face { font-family: SFRoundedSemiBold; font-display: swap; - src: url("~/assets/fonts/SFRoundedSemiBold.otf") format("opentype"); + src: url("/fonts/SFRoundedSemiBold.otf") format("opentype"); } @font-face { font-family: CommitMono; font-display: swap; - src: url("~/assets/fonts/CommitMono.woff2") format("woff2"); + src: url("/fonts/CommitMono.woff2") format("woff2"); } :root { @@ -85,6 +85,7 @@ onMounted(async () => { font-family: SFRoundedRegular; scroll-behavior: smooth; scrollbar-width: thin; + user-select: none; --os-handle-bg: #ada9a1; --os-handle-bg-hover: #78756f; diff --git a/components/TopBar.vue b/components/TopBar.vue index 951bee3..91924ec 100644 --- a/components/TopBar.vue +++ b/components/TopBar.vue @@ -3,7 +3,7 @@ (null); const emit = defineEmits<{ (e: "search", query: string): void; + (e: "searchStarted"): void; (e: "focus"): void; }>(); -const onSearch = () => { +const onInputChange = () => { + emit("searchStarted"); emit("search", searchQuery.value); }; diff --git a/assets/fonts/CommitMono.woff2 b/fonts/CommitMono.woff2 similarity index 100% rename from assets/fonts/CommitMono.woff2 rename to fonts/CommitMono.woff2 diff --git a/assets/fonts/SFRoundedMedium.otf b/fonts/SFRoundedMedium.otf similarity index 100% rename from assets/fonts/SFRoundedMedium.otf rename to fonts/SFRoundedMedium.otf diff --git a/assets/fonts/SFRoundedRegular.otf b/fonts/SFRoundedRegular.otf similarity index 100% rename from assets/fonts/SFRoundedRegular.otf rename to fonts/SFRoundedRegular.otf diff --git a/assets/fonts/SFRoundedSemiBold.otf b/fonts/SFRoundedSemiBold.otf similarity index 100% rename from assets/fonts/SFRoundedSemiBold.otf rename to fonts/SFRoundedSemiBold.otf diff --git a/pages/index.vue b/pages/index.vue index fc4c925..824f0e1 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,64 +1,40 @@ @@ -156,7 +130,7 @@ const getWeekNumber = (date: Date): number => { ((date.getTime() - firstDayOfYear.getTime()) / 86400000 + firstDayOfYear.getDay() + 1) / - 7 + 7 ); }; @@ -177,8 +151,8 @@ const groupedHistory = computed(() => { const filteredItems = searchQuery.value ? history.value.filter((item) => - item.content.toLowerCase().includes(searchQuery.value.toLowerCase()) - ) + item.content.toLowerCase().includes(searchQuery.value.toLowerCase()) + ) : history.value; const yesterday = new Date(today.getTime() - 86400000); @@ -321,32 +295,78 @@ const scrollToSelectedItem = (): void => { }); }; +let searchController: AbortController | null = null; +let searchQueue: Array = []; +let isProcessingSearch = false; + +const searchStarted = () => { + if (searchController) { + searchController.abort(); + } +}; + +const processSearchQueue = async () => { + if (isProcessingSearch || searchQueue.length === 0) return; + + isProcessingSearch = true; + const query = searchQueue.pop(); + searchQueue = []; + + try { + if (!query || !query.trim()) { + history.value = []; + offset = 0; + await loadHistoryChunk(); + isProcessingSearch = false; + return; + } + + const results = await $history.searchHistory(query); + + if (searchController?.signal.aborted) { + isProcessingSearch = false; + return; + } + + history.value = results.map((item) => + Object.assign( + new HistoryItem( + item.source, + item.content_type, + item.content, + item.favicon, + item.source_icon, + item.language + ), + { id: item.id, timestamp: new Date(item.timestamp) } + ) + ); + + if (groupedHistory.value.length > 0) { + handleSelection(0, 0, false); + } + } catch (error) { + console.error("Search error:", error); + } finally { + isProcessingSearch = false; + if (searchQueue.length > 0) { + requestAnimationFrame(() => processSearchQueue()); + } + } +}; + const searchHistory = async (query: string): Promise => { searchQuery.value = query; - if (!query.trim()) { - history.value = []; - offset = 0; - await loadHistoryChunk(); - return; + + if (searchController) { + searchController.abort(); } - const results = await $history.searchHistory(query); - history.value = results.map((item) => - Object.assign( - new HistoryItem( - item.source, - item.content_type, - item.content, - item.favicon, - item.source_icon, - item.language - ), - { id: item.id, timestamp: new Date(item.timestamp) } - ) - ); + searchController = new AbortController(); - if (groupedHistory.value.length > 0) { - handleSelection(0, 0, false); + searchQueue.push(query); + if (!isProcessingSearch) { + processSearchQueue(); } }; @@ -517,14 +537,14 @@ const setupEventListeners = async (): Promise => { switch (os.value) { case "macos": - keyboard.prevent.down([Key.LeftMeta, Key.K], () => {}); - keyboard.prevent.down([Key.RightMeta, Key.K], () => {}); + keyboard.prevent.down([Key.LeftMeta, Key.K], () => { }); + keyboard.prevent.down([Key.RightMeta, Key.K], () => { }); break; case "linux": case "windows": - keyboard.prevent.down([Key.LeftControl, Key.K], () => {}); - keyboard.prevent.down([Key.RightControl, Key.K], () => {}); + keyboard.prevent.down([Key.LeftControl, Key.K], () => { }); + keyboard.prevent.down([Key.RightControl, Key.K], () => { }); break; } }); @@ -552,14 +572,14 @@ const setupEventListeners = async (): Promise => { switch (os.value) { case "macos": - keyboard.prevent.down([Key.LeftMeta, Key.K], () => {}); - keyboard.prevent.down([Key.RightMeta, Key.K], () => {}); + keyboard.prevent.down([Key.LeftMeta, Key.K], () => { }); + keyboard.prevent.down([Key.RightMeta, Key.K], () => { }); break; case "linux": case "windows": - keyboard.prevent.down([Key.LeftControl, Key.K], () => {}); - keyboard.prevent.down([Key.RightControl, Key.K], () => {}); + keyboard.prevent.down([Key.LeftControl, Key.K], () => { }); + keyboard.prevent.down([Key.RightControl, Key.K], () => { }); break; } }; @@ -750,9 +770,9 @@ const infoRows = computed(() => { if (!getInfo.value) return []; const commonRows = [ - { - label: "Source", - value: getInfo.value.source, + { + label: "Source", + value: getInfo.value.source, isUrl: false, icon: selectedItem.value?.source_icon ? `data:image/png;base64,${selectedItem.value.source_icon}` : undefined }, @@ -785,7 +805,7 @@ const infoRows = computed(() => { ], [ContentType.Link]: [ ...((getInfo.value as InfoLink).title && - (getInfo.value as InfoLink).title !== "Loading..." + (getInfo.value as InfoLink).title !== "Loading..." ? [{ label: "Title", value: (getInfo.value as InfoLink).title || "" }] : []), { label: "URL", value: (getInfo.value as InfoLink).url, isUrl: true }, @@ -815,5 +835,5 @@ const infoRows = computed(() => { diff --git a/pages/settings.vue b/pages/settings.vue index 654fcfb..f67a52b 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -228,5 +228,5 @@ onUnmounted(() => { diff --git a/plugins/history.ts b/plugins/history.ts index 934ded5..2bd84c2 100644 --- a/plugins/history.ts +++ b/plugins/history.ts @@ -14,17 +14,27 @@ export default defineNuxtPlugin(() => { }, async searchHistory(query: string): Promise { - return await invoke("search_history", { query }); + try { + return await invoke("search_history", { query }); + } catch (error) { + console.error("Error searching history:", error); + return []; + } }, async loadHistoryChunk( offset: number, limit: number ): Promise { - return await invoke("load_history_chunk", { - offset, - limit, - }); + try { + return await invoke("load_history_chunk", { + offset, + limit, + }); + } catch (error) { + console.error("Error loading history chunk:", error); + return []; + } }, async deleteHistoryItem(id: string): Promise { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b0ed3af..356fe53 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -77,15 +77,17 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "applications" version = "0.3.0" -source = "git+https://github.com/HuakunShen/applications-rs?branch=load_icon#bcfbebb93a57918aca4ba51257b2788be90892da" +source = "git+https://github.com/HuakunShen/applications-rs?branch=fix/win-app-detection#ddcec7cdc3cc4f6678f1b1b525b3e509987b21ae" dependencies = [ "anyhow", "cocoa 0.25.0", "core-foundation 0.9.4", + "env_logger", "glob", "image", "ini", "lnk", + "log", "objc", "parselnk", "plist", @@ -1359,6 +1361,19 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -2121,6 +2136,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hermit-abi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" + [[package]] name = "hex" version = "0.4.3" @@ -2208,6 +2229,12 @@ version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + [[package]] name = "hyper" version = "1.4.1" @@ -2584,6 +2611,17 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi 0.5.0", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "itertools" version = "0.12.1" @@ -5957,6 +5995,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "texting_robots" version = "0.2.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index bf040c9..d8eddea 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -49,7 +49,7 @@ log = { version = "0.4.26", features = ["std"] } uuid = "1.16.0" include_dir = "0.7.4" # hyperpolyglot = { git = "https://github.com/0pandadev/hyperpolyglot" } -applications = { git = "https://github.com/HuakunShen/applications-rs", branch = "load_icon" } +applications = { git = "https://github.com/HuakunShen/applications-rs", branch = "fix/win-app-detection" } glob = "0.3.2" meta_fetcher = "0.1.1" parking_lot = "0.12.3" diff --git a/src-tauri/src/db/history.rs b/src-tauri/src/db/history.rs index 3b0a4b2..4f70310 100644 --- a/src-tauri/src/db/history.rs +++ b/src-tauri/src/db/history.rs @@ -111,18 +111,27 @@ pub async fn search_history( pool: tauri::State<'_, SqlitePool>, query: String ) -> Result, String> { + if query.trim().is_empty() { + return Ok(Vec::new()); + } + let query = format!("%{}%", query); + let rows = sqlx ::query( - "SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history WHERE content LIKE ? ORDER BY timestamp DESC" + "SELECT id, source, source_icon, content_type, content, favicon, timestamp, language + FROM history + WHERE content LIKE ? + ORDER BY timestamp DESC + LIMIT 100" ) .bind(query) .fetch_all(&*pool).await .map_err(|e| e.to_string())?; - let items = rows - .iter() - .map(|row| HistoryItem { + let mut items = Vec::with_capacity(rows.len()); + for row in rows.iter() { + items.push(HistoryItem { id: row.get("id"), source: row.get("source"), source_icon: row.get("source_icon"), @@ -131,8 +140,8 @@ pub async fn search_history( favicon: row.get("favicon"), timestamp: row.get("timestamp"), language: row.get("language"), - }) - .collect(); + }); + } Ok(items) } diff --git a/assets/css/index.scss b/styles/index.scss similarity index 95% rename from assets/css/index.scss rename to styles/index.scss index 15458ac..20a154d 100644 --- a/assets/css/index.scss +++ b/styles/index.scss @@ -176,3 +176,11 @@ main { } } } + +.search-loading { + display: flex; + justify-content: center; + padding: 20px; + font-size: 16px; + color: var(--text-secondary); +} \ No newline at end of file diff --git a/assets/css/settings.scss b/styles/settings.scss similarity index 100% rename from assets/css/settings.scss rename to styles/settings.scss