File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -92,7 +92,7 @@ export function CustomSearchDialog({
9292 if ( ! search ) return ;
9393
9494 const timer = setTimeout ( ( ) => {
95- // Umami 埋点: 搜索结果点击
95+ // Umami 埋点: 搜索词输入(debounce 1s,非搜索结果点击)
9696 if ( window . umami ) {
9797 window . umami . track ( "search_query" , { query : search } ) ;
9898 }
Original file line number Diff line number Diff line change 11"use client" ;
22
3- import { useState } from "react" ;
3+ import { useEffect , useRef , useState } from "react" ;
44import { trackEvent } from "@/lib/analytics" ;
55
66/**
@@ -9,14 +9,23 @@ import { trackEvent } from "@/lib/analytics";
99 */
1010export function DocShareButton ( ) {
1111 const [ copied , setCopied ] = useState ( false ) ;
12+ // timer ref:每次新点击 / 组件卸载时清掉旧 timer,避免 setState on unmounted
13+ const resetTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
14+
15+ useEffect ( ( ) => {
16+ return ( ) => {
17+ if ( resetTimerRef . current ) clearTimeout ( resetTimerRef . current ) ;
18+ } ;
19+ } , [ ] ) ;
1220
1321 const handleCopy = async ( ) => {
1422 const url = window . location . href ;
1523 try {
1624 await navigator . clipboard . writeText ( url ) ;
1725 setCopied ( true ) ;
18- // 2s 后恢复按钮文案
19- setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
26+ // 旧 timer 先清掉,避免连点两次后提前恢复文案
27+ if ( resetTimerRef . current ) clearTimeout ( resetTimerRef . current ) ;
28+ resetTimerRef . current = setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
2029 } catch {
2130 // clipboard 不可用时静默失败
2231 }
@@ -27,6 +36,7 @@ export function DocShareButton() {
2736
2837 return (
2938 < button
39+ type = "button"
3040 onClick = { handleCopy }
3141 className = "inline-flex items-center gap-2 rounded-md px-4 h-11 text-base font-medium hover:bg-muted/80 hover:text-foreground"
3242 aria-label = "复制页面链接"
Original file line number Diff line number Diff line change @@ -18,13 +18,22 @@ export function DocsPageViewTracker() {
1818 if ( ! pathname ) return ;
1919
2020 const dedupeKey = `pv:${ pathname } ` ;
21- // sessionStorage 去重:同一 session 内同一路径不重复上报
22- if ( sessionStorage . getItem ( dedupeKey ) ) return ;
21+ // sessionStorage 可能因为存储禁用 / 配额超限 / Safari 隐私模式抛错;
22+ // 埋点的去重不能因此报错破坏导航,用 try/catch 降级到内存去重
23+ try {
24+ if ( sessionStorage . getItem ( dedupeKey ) ) return ;
25+ } catch {
26+ // 读失败时跳过 session 去重,后面的内存去重仍然生效
27+ }
2328 // 内存去重:防止 React StrictMode 双重 effect 重复调用
2429 if ( lastTrackedRef . current === pathname ) return ;
2530
2631 lastTrackedRef . current = pathname ;
27- sessionStorage . setItem ( dedupeKey , "1" ) ;
32+ try {
33+ sessionStorage . setItem ( dedupeKey , "1" ) ;
34+ } catch {
35+ // 写失败时下一个 session / 刷新会再报一次,可接受
36+ }
2837
2938 trackEvent ( "page_view" , {
3039 path : pathname ,
You can’t perform that action at this time.
0 commit comments