88 Search , X , ArrowRight ,
99 LayoutDashboard , Timer , FileText , Layers , Lightbulb , BarChart3 , Settings ,
1010 FilePlus , FolderPlus , Import , Download , Moon , CheckCircle ,
11- Palette , Brain , Database , Info ,
11+ Palette , Brain , Database , Info , GraduationCap ,
1212 type LucideIcon ,
1313} from 'lucide-react' ;
1414import { cn } from '@/lib/utils' ;
@@ -17,22 +17,50 @@ import { useToast } from '@/components/ui/Toast';
1717import { commandRegistry } from '@/lib/commandPalette/registry' ;
1818import { registerDefaultCommands } from '@/lib/commandPalette/defaultCommands' ;
1919import type { Command } from '@/lib/commandPalette/registry' ;
20+ import { useNoteStore } from '@/features/notes/store/useNoteStore' ;
21+ import type { SearchEntityType } from '@/types/models' ;
2022import { soundPlayer } from '@/lib/audio/SoundPlayer' ;
2123
2224const iconMap : Record < string , LucideIcon > = {
2325 LayoutDashboard, Timer, FileText, Layers, Lightbulb, BarChart3, Settings,
2426 FilePlus, FolderPlus, Import, Download, Moon, CheckCircle,
25- Palette, Brain, Database, Info,
27+ Palette, Brain, Database, Info, GraduationCap ,
2628} ;
2729
30+ /** 搜索结果实体类型 → 图标名(与 iconMap 对应) */
31+ const ENTITY_ICONS : Record < SearchEntityType , string > = {
32+ note : 'FileText' ,
33+ flashcard : 'Layers' ,
34+ feynman : 'Brain' ,
35+ inspiration : 'Lightbulb' ,
36+ classroom : 'GraduationCap' ,
37+ } ;
38+
39+ /** 搜索结果实体类型 → 目标路由(note 精确到单篇,其余落模块页) */
40+ function contentRoute ( type : SearchEntityType , entityId : string ) : string {
41+ switch ( type ) {
42+ case 'note' : return `/notes/${ entityId } ` ;
43+ case 'flashcard' : return '/flashcards' ;
44+ case 'feynman' : return '/feynman' ;
45+ case 'inspiration' : return '/inspiration' ;
46+ case 'classroom' : return '/classroom' ;
47+ default : return '/' ;
48+ }
49+ }
50+
51+ /** 去掉搜索高亮标记后的纯文本片段 */
52+ function stripMarkup ( html : string ) : string {
53+ return html . replace ( / < [ ^ > ] * > / g, ' ' ) . replace ( / \s + / g, ' ' ) . trim ( ) ;
54+ }
55+
2856function CommandIcon ( { name, className } : { name ?: string ; className ?: string } ) {
2957 if ( ! name || ! iconMap [ name ] ) return null ;
3058 const Icon = iconMap [ name ] ;
3159 return < Icon className = { className } /> ;
3260}
3361
3462const categoryLabels : Record < Command [ 'category' ] , string > = {
35- navigation : '导航' , action : '操作' , settings : '设置' ,
63+ navigation : '导航' , action : '操作' , settings : '设置' , content : '内容' ,
3664} ;
3765
3866/** 导航命令 ID → 目标路由(用于判断目标≠当前时播放切换音效) */
@@ -45,6 +73,7 @@ const categoryColors: Record<Command['category'], string> = {
4573 navigation : 'bg-accent-500/15 text-accent-500' ,
4674 action : 'bg-semantic-success/15 text-semantic-success' ,
4775 settings : 'bg-accent/15 text-accent-500' ,
76+ content : 'bg-brand-500/15 text-brand-600' ,
4877} ;
4978
5079function groupByCategory ( commands : Command [ ] ) : Array < { category : Command [ 'category' ] ; items : Command [ ] } > {
@@ -68,6 +97,8 @@ export default function CommandPalette() {
6897 const registeredRef = useRef ( false ) ;
6998 const listRef = useRef < HTMLDivElement > ( null ) ;
7099 const inputRef = useRef < HTMLInputElement > ( null ) ;
100+ // 全局内容搜索:复用笔记 store 的 searchNotes(dexie 全局索引,多实体)
101+ const searchResults = useNoteStore ( ( s ) => s . searchResults ) ;
71102
72103 useEffect ( ( ) => {
73104 if ( ! registeredRef . current ) {
@@ -76,7 +107,36 @@ export default function CommandPalette() {
76107 }
77108 } , [ ] ) ;
78109
79- const filtered = useMemo ( ( ) => commandRegistry . search ( query ) , [ query ] ) ;
110+ // 输入防抖 250ms 后执行全局内容搜索;清空输入时清除结果
111+ useEffect ( ( ) => {
112+ if ( ! query . trim ( ) ) {
113+ useNoteStore . getState ( ) . searchNotes ( '' ) ;
114+ return ;
115+ }
116+ const timer = setTimeout ( ( ) => {
117+ useNoteStore . getState ( ) . searchNotes ( query , { limit : 6 , fuzzy : true } ) ;
118+ } , 250 ) ;
119+ return ( ) => clearTimeout ( timer ) ;
120+ } , [ query ] ) ;
121+
122+ // 搜索结果包装为 Command(category=content),与命令列表统一键盘导航/渲染
123+ const contentCommands = useMemo < Command [ ] > ( ( ) => {
124+ if ( ! query . trim ( ) ) return [ ] ;
125+ return searchResults . map ( ( r ) => ( {
126+ id : `search-${ r . entityType } -${ r . entityId } ` ,
127+ label : r . title || '未命名' ,
128+ description : stripMarkup ( r . snippet ) . slice ( 0 , 60 ) ,
129+ icon : ENTITY_ICONS [ r . entityType ] ,
130+ category : 'content' as const ,
131+ execute : ( ) => navigate ( contentRoute ( r . entityType , r . entityId ) ) ,
132+ } ) ) ;
133+ } , [ query , searchResults , navigate ] ) ;
134+
135+ // 有输入时:内容结果置顶 + 命令匹配结果;无输入时:全部命令(分组展示)
136+ const filtered = useMemo (
137+ ( ) => ( query . trim ( ) ? [ ...contentCommands , ...commandRegistry . search ( query ) ] : commandRegistry . search ( query ) ) ,
138+ [ query , contentCommands ] ,
139+ ) ;
80140 const groups = useMemo ( ( ) => ( query ? null : groupByCategory ( filtered ) ) , [ query , filtered ] ) ;
81141 const flatList = filtered ;
82142
@@ -152,7 +212,7 @@ export default function CommandPalette() {
152212 < motion . div
153213 className = { cn (
154214 'bg-bg-elevated/90 backdrop-blur-2xl rounded-[var(--kb-radius-xl)] shadow-2xl' ,
155- 'w-[560px] max-h-[400px ] flex flex-col' ,
215+ 'w-[560px] max-h-[520px ] flex flex-col' ,
156216 'border border-border/40' ,
157217 ) }
158218 initial = { { opacity : 0 , scale : 0.95 , y : - 20 } }
@@ -171,7 +231,7 @@ export default function CommandPalette() {
171231 ref = { inputRef }
172232 value = { query }
173233 onChange = { ( e ) => { setQuery ( e . target . value ) ; setSelectedIndex ( 0 ) ; } }
174- placeholder = "输入命令或搜索功能 …"
234+ placeholder = "搜索内容或输入命令 …"
175235 size = "lg"
176236 prefix = { < Search className = "w-4 h-4" /> }
177237 suffix = { query ? (
@@ -193,7 +253,7 @@ export default function CommandPalette() {
193253 < motion . div className = "flex flex-col items-center justify-center py-8 gap-2"
194254 initial = { { opacity : 0 } } animate = { { opacity : 1 } } >
195255 < Search className = "w-8 h-8 text-text-tertiary" />
196- < p className = "text-b2 text-text-tertiary" > 未找到匹配命令 </ p >
256+ < p className = "text-b2 text-text-tertiary" > 未找到匹配内容或命令 </ p >
197257 </ motion . div >
198258 ) : groups ? (
199259 groups . map ( ( group ) => (
0 commit comments