11"use client" ;
22
3- import { useEffect , useMemo , useState } from "react" ;
3+ import { useEffect , useMemo , useRef , useState } from "react" ;
4+ import { trackEvent } from "@/lib/analytics" ;
45import { useDocsSearch } from "fumadocs-core/search/client" ;
56import { useI18n } from "fumadocs-ui/provider" ;
67import {
@@ -57,7 +58,16 @@ export function CustomSearchDialog({
5758 const [ tag , setTag ] = useState ( defaultTag ) ;
5859
5960 // Extract onOpenChange to use in dependency array cleanly
60- const { onOpenChange, ...otherProps } = props ;
61+ const { onOpenChange, open, ...otherProps } = props ;
62+
63+ // 记录上次 open 状态,只在从关闭→打开时触发埋点,避免渲染抖动多次上报
64+ const prevOpenRef = useRef < boolean | undefined > ( undefined ) ;
65+ useEffect ( ( ) => {
66+ if ( open === true && prevOpenRef . current !== true ) {
67+ trackEvent ( "search_open" , { path : window . location . pathname } ) ;
68+ }
69+ prevOpenRef . current = open ;
70+ } , [ open ] ) ;
6171
6272 const { search, setSearch, query } = useDocsSearch (
6373 type === "fetch"
@@ -82,7 +92,7 @@ export function CustomSearchDialog({
8292 if ( ! search ) return ;
8393
8494 const timer = setTimeout ( ( ) => {
85- // Umami 埋点: 搜索结果点击
95+ // Umami 埋点: 搜索词输入(debounce 1s,非搜索结果点击)
8696 if ( window . umami ) {
8797 window . umami . track ( "search_query" , { query : search } ) ;
8898 }
@@ -103,36 +113,37 @@ export function CustomSearchDialog({
103113
104114 // 使用 useMemo 劫持 search items,注入埋点逻辑
105115 const trackedItems = useMemo ( ( ) => {
106- const data = query . data !== "empty" && query . data ? query . data : defaultItems ;
116+ const data =
117+ query . data !== "empty" && query . data ? query . data : defaultItems ;
107118 if ( ! data ) return [ ] ;
108119
109120 return data . map ( ( item : unknown , index : number ) => {
110- const searchItem = item as SearchItem ;
111- return {
112- ...searchItem ,
113- onSelect : ( value : string ) => {
114- // Umami 埋点: 搜索结果点击
115- if ( window . umami ) {
116- window . umami . track ( "search_result_click" , {
117- query : search ,
118- rank : index + 1 ,
119- url : searchItem . url ,
120- } ) ;
121- }
122-
123- // Call original onSelect if it exists
124- if ( searchItem . onSelect ) searchItem . onSelect ( value ) ;
125-
126- // Handle navigation if URL exists
127- if ( searchItem . url ) {
128- // 显式执行路由跳转和关闭弹窗,确保点击行为能够同时触发埋点和导航
129- router . push ( searchItem . url ) ;
130- if ( onOpenChange ) {
131- onOpenChange ( false ) ;
132- }
121+ const searchItem = item as SearchItem ;
122+ return {
123+ ...searchItem ,
124+ onSelect : ( value : string ) => {
125+ // Umami 埋点: 搜索结果点击
126+ if ( window . umami ) {
127+ window . umami . track ( "search_result_click" , {
128+ query : search ,
129+ rank : index + 1 ,
130+ url : searchItem . url ,
131+ } ) ;
132+ }
133+
134+ // Call original onSelect if it exists
135+ if ( searchItem . onSelect ) searchItem . onSelect ( value ) ;
136+
137+ // Handle navigation if URL exists
138+ if ( searchItem . url ) {
139+ // 显式执行路由跳转和关闭弹窗,确保点击行为能够同时触发埋点和导航
140+ router . push ( searchItem . url ) ;
141+ if ( onOpenChange ) {
142+ onOpenChange ( false ) ;
133143 }
134- } ,
135- } ;
144+ }
145+ } ,
146+ } ;
136147 } ) ;
137148 } , [ query . data , defaultItems , search , router , onOpenChange ] ) ;
138149
@@ -142,6 +153,7 @@ export function CustomSearchDialog({
142153 onSearchChange = { setSearch }
143154 isLoading = { query . isLoading }
144155 onOpenChange = { onOpenChange }
156+ open = { open }
145157 { ...otherProps }
146158 >
147159 < SearchDialogOverlay />
0 commit comments