diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index 04e0bea6..326e2c07 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -265,6 +265,8 @@ export const en: typeof zhCN = { desc: 'Locally stored transcripts.', filterAll: 'All', summary: '{{total}} total · showing {{shown}}', + searchPlaceholder: 'Search transcripts… ({{shortcut}})', + searchNoMatch: 'No entries match “{{query}}”.', empty: 'No history yet. Press {{trigger}} to record one.', loadFailed: 'Failed to load history: {{err}}', retry: 'Retry', diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index 8e026a22..c3d695c0 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -267,6 +267,8 @@ export const ja: typeof zhCN = { desc: 'ローカルに保存された認識記録。', filterAll: 'すべて', summary: '合計 {{total}} 件 · 表示 {{shown}}', + searchPlaceholder: '文字起こしを検索…({{shortcut}})', + searchNoMatch: '「{{query}}」に一致する項目はありません。', empty: '履歴がありません。{{trigger}} を押して録音してみましょう。', loadFailed: '履歴の読み込みに失敗:{{err}}', retry: '再試行', diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index 49aa5576..0633be29 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -267,6 +267,8 @@ export const ko: typeof zhCN = { desc: '로컬에 저장된 인식 기록.', filterAll: '전체', summary: '총 {{total}}건 · 표시 {{shown}}', + searchPlaceholder: '기록 검색…({{shortcut}})', + searchNoMatch: '“{{query}}”과(와) 일치하는 항목이 없습니다.', empty: '기록이 없습니다. {{trigger}} 를 눌러 한 번 녹음해 보세요.', loadFailed: '기록 로드 실패: {{err}}', retry: '다시 시도', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index 6b7a898b..d7844014 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -263,6 +263,8 @@ export const zhCN = { desc: '本机保存的识别记录。', filterAll: '全部', summary: '共 {{total}} 条 · 显示 {{shown}}', + searchPlaceholder: '搜索转写内容…({{shortcut}})', + searchNoMatch: '没有匹配「{{query}}」的记录。', empty: '还没有历史记录。按 {{trigger}} 录一段试试。', loadFailed: '加载历史失败:{{err}}', retry: '重试', diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index f8739be5..f01e6be2 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -265,6 +265,8 @@ export const zhTW: typeof zhCN = { desc: '本機保存的識別記錄。', filterAll: '全部', summary: '共 {{total}} 條 · 顯示 {{shown}}', + searchPlaceholder: '搜尋轉寫內容…({{shortcut}})', + searchNoMatch: '沒有符合「{{query}}」的記錄。', empty: '還沒有歷史記錄。按 {{trigger}} 錄一段試試。', loadFailed: '加載歷史失敗:{{err}}', retry: '重試', diff --git a/openless-all/app/src/pages/History.tsx b/openless-all/app/src/pages/History.tsx index f030fe9b..d3c7d0a4 100644 --- a/openless-all/app/src/pages/History.tsx +++ b/openless-all/app/src/pages/History.tsx @@ -1,7 +1,7 @@ // History.tsx — 接 Tauri 后端 list_history / delete_history_entry / clear_history。 // 真实数据来自 ~/Library/Application Support/OpenLess/history.json。 -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Icon } from '../components/Icon'; import { detectOS } from '../components/WindowChrome'; @@ -38,6 +38,9 @@ export function History() { const FILTERS = useFilters(); const MODE_LABEL = useModeLabel(); const [filter, setFilter] = useState<'all' | PolishMode>('all'); + // issue #612:历史页顶部原为静态 div,只显示统计、不可输入。改为真实搜索框。 + const [query, setQuery] = useState(''); + const searchRef = useRef(null); const [items, setItems] = useState([]); const [selectedId, setSelectedId] = useState(null); const [loading, setLoading] = useState(true); @@ -80,10 +83,29 @@ export function History() { void refresh(); }, [refresh]); - const filtered = useMemo( - () => (filter === 'all' ? items : items.filter(s => s.mode === filter)), - [items, filter], - ); + // ⌘K / Ctrl+K 聚焦搜索框(issue #612 验收可选项)。 + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')) { + e.preventDefault(); + searchRef.current?.focus(); + } + }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, []); + + const filtered = useMemo(() => { + const byMode = filter === 'all' ? items : items.filter(s => s.mode === filter); + const q = query.trim().toLowerCase(); + if (!q) return byMode; + return byMode.filter( + s => + s.finalText.toLowerCase().includes(q) || + s.rawTranscript.toLowerCase().includes(q) || + (s.appName ?? '').toLowerCase().includes(q), + ); + }, [items, filter, query]); const item = useMemo( () => filtered.find(s => s.id === selectedId) || filtered[0], [filtered, selectedId], @@ -181,12 +203,26 @@ export function History() {
- {t('history.summary', { total: items.length, shown: filtered.length })} + setQuery(e.target.value)} + placeholder={t('history.searchPlaceholder', { shortcut: os === 'mac' ? '⌘K' : 'Ctrl+K' })} + style={{ + flex: 1, minWidth: 0, outline: 'none', border: 0, + background: 'transparent', fontSize: 12, color: 'var(--ol-ink-2)', + fontFamily: 'inherit', + }} + /> +
+
+ {t('history.summary', { total: items.length, shown: filtered.length })}
{FILTERS.map(f => ( @@ -220,7 +256,9 @@ export function History() { )} {!loading && !loadError && filtered.length === 0 && (
- {t('history.empty', { trigger: prefs ? formatComboLabel(prefs.dictationHotkey) : '' })} + {query.trim() + ? t('history.searchNoMatch', { query: query.trim() }) + : t('history.empty', { trigger: prefs ? formatComboLabel(prefs.dictationHotkey) : '' })}
)} {!loadError && filtered.map(s => (