Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ export const ja: typeof zhCN = {
desc: 'ローカルに保存された認識記録。',
filterAll: 'すべて',
summary: '合計 {{total}} 件 · 表示 {{shown}}',
searchPlaceholder: '文字起こしを検索…({{shortcut}})',
searchNoMatch: '「{{query}}」に一致する項目はありません。',
empty: '履歴がありません。{{trigger}} を押して録音してみましょう。',
loadFailed: '履歴の読み込みに失敗:{{err}}',
retry: '再試行',
Expand Down
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ export const ko: typeof zhCN = {
desc: '로컬에 저장된 인식 기록.',
filterAll: '전체',
summary: '총 {{total}}건 · 표시 {{shown}}',
searchPlaceholder: '기록 검색…({{shortcut}})',
searchNoMatch: '“{{query}}”과(와) 일치하는 항목이 없습니다.',
empty: '기록이 없습니다. {{trigger}} 를 눌러 한 번 녹음해 보세요.',
loadFailed: '기록 로드 실패: {{err}}',
retry: '다시 시도',
Expand Down
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ export const zhCN = {
desc: '本机保存的识别记录。',
filterAll: '全部',
summary: '共 {{total}} 条 · 显示 {{shown}}',
searchPlaceholder: '搜索转写内容…({{shortcut}})',
searchNoMatch: '没有匹配「{{query}}」的记录。',
empty: '还没有历史记录。按 {{trigger}} 录一段试试。',
loadFailed: '加载历史失败:{{err}}',
retry: '重试',
Expand Down
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ export const zhTW: typeof zhCN = {
desc: '本機保存的識別記錄。',
filterAll: '全部',
summary: '共 {{total}} 條 · 顯示 {{shown}}',
searchPlaceholder: '搜尋轉寫內容…({{shortcut}})',
searchNoMatch: '沒有符合「{{query}}」的記錄。',
empty: '還沒有歷史記錄。按 {{trigger}} 錄一段試試。',
loadFailed: '加載歷史失敗:{{err}}',
retry: '重試',
Expand Down
54 changes: 46 additions & 8 deletions openless-all/app/src/pages/History.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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<HTMLInputElement>(null);
const [items, setItems] = useState<DictationSession[]>([]);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -181,12 +203,26 @@ export function History() {
<div style={{ padding: '12px 14px', borderBottom: '0.5px solid var(--ol-line)' }}>
<div style={{
display: 'flex', alignItems: 'center', gap: 6,
padding: '6px 10px', fontSize: 12,
padding: '6px 10px',
border: '0.5px solid var(--ol-line-strong)', borderRadius: 8,
background: 'var(--ol-surface-2)', color: 'var(--ol-ink-3)',
}}>
<Icon name="search" size={12} />
<span style={{ flex: 1 }}>{t('history.summary', { total: items.length, shown: filtered.length })}</span>
<input
ref={searchRef}
type="search"
value={query}
onChange={e => 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',
}}
/>
</div>
<div style={{ marginTop: 8, fontSize: 11, color: 'var(--ol-ink-4)' }}>
{t('history.summary', { total: items.length, shown: filtered.length })}
</div>
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginTop: 10 }}>
{FILTERS.map(f => (
Expand Down Expand Up @@ -220,7 +256,9 @@ export function History() {
)}
{!loading && !loadError && filtered.length === 0 && (
<div style={{ padding: 16, fontSize: 12, color: 'var(--ol-ink-4)' }}>
{t('history.empty', { trigger: prefs ? formatComboLabel(prefs.dictationHotkey) : '' })}
{query.trim()
? t('history.searchNoMatch', { query: query.trim() })
: t('history.empty', { trigger: prefs ? formatComboLabel(prefs.dictationHotkey) : '' })}
</div>
)}
{!loadError && filtered.map(s => (
Expand Down
Loading