/** * special Provider:Windows 常用快捷位置。 * 列表由 Rust 提供(quickpanel_get_special_locations),1 分钟缓存。 */ import { bestScore } from '../engine' import type { QPItem, QPProvider } from './types' import { buildItemForms } from './utils' // Rust 端通过 tauri-specta 生成的命令绑定与类型(bindings.ts) import { commands, type SpecialLocation } from '@/lib/bindings' let specialCache: SpecialLocation[] | null = null let specialCacheTime = 0 const SPECIAL_CACHE_TTL = 60_000 // 1 分钟缓存 async function loadSpecials(): Promise { if (specialCache && Date.now() - specialCacheTime < SPECIAL_CACHE_TTL) { return specialCache } try { const list = await commands.quickpanelGetSpecialLocations() specialCache = list specialCacheTime = Date.now() return list } catch (e) { console.error('[quickpanel] 获取快捷位置失败:', e) return [] } } export class SpecialProvider implements QPProvider { id = 'special' label = '快捷' priority = 60 async search(query: string): Promise { const list = await loadSpecials() if (!list.length) return [] if (!query.trim()) return [] // 空查询不占用列表,由用户主动搜索 const open = async (s: SpecialLocation) => { try { await commands.quickpanelOpenSpecial(s.kind, s.target, s.args) } catch (e) { console.error('[quickpanel] 打开快捷位置失败:', e) } } const items: QPItem[] = list.map(s => ({ id: `sp-${s.id}`, title: s.title, subtitle: s.subtitle, group: '快捷', action: () => open(s), subActions: s.kind === 'file' ? [ { id: 'open', label: '打开', action: () => open(s) }, { id: 'reveal', label: '在资源管理器中显示', action: async () => { try { await commands.quickpanelRevealInExplorer(s.target) } catch (e) { console.error('[quickpanel] 资源管理器显示失败:', e) } }, }, { id: 'copy-path', label: '复制路径', action: async () => { try { await navigator.clipboard.writeText(s.target) } catch { /* 忽略 */ } }, }, ] : undefined, })) const scored: Array<{ item: QPItem; score: number }> = [] items.forEach((item, idx) => { const forms = buildItemForms(item.title, list[idx].keywords) const score = bestScore(query, forms) if (score >= 0) scored.push({ item, score }) }) scored.sort((a, b) => b.score - a.score) return scored.slice(0, 8).map(s => ({ ...s.item, score: s.score })) } }