MCPcopy Create free account
hub / github.com/Qinbf/groundmap / ScrollMemory

Function ScrollMemory

web/components/ScrollMemory.tsx:100–225  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

98 * PageEditor 的布局,改其滚动结构时需复核。
99 */
100export function ScrollMemory() {
101 const pathname = usePathname();
102 const searchParams = useSearchParams();
103
104 const keyRef = useRef<string>(""); // 当前历史条目 key(滚动监听据此保存)
105 const lastDecidedKeyRef = useRef<string>(""); // 去重:同一条目只决策一次
106 const lastDecidedHashRef = useRef<string>(""); // 上次决策时的 location.hash(识别克隆条目)
107 const decidedHrefRef = useRef<string>(""); // 上次决策时的 pathname+search(用于分流)
108 const isRestoringRef = useRef(false); // 还原期间抑制回存,防夹断中间态写脏
109 const cancelRestoreRef = useRef<(() => void) | null>(null);
110 const runDecisionRef = useRef<() => void>(() => {});
111
112 // 导航决策:定位当前历史条目 → 有记忆则还原;否则置顶 / 让位给 hash。
113 // 不读 pathname,直接读 history.state 的 key,故任何触发源都能复用。
114 // 在每次 render 赋值(读的全是 ref,无闭包陈旧问题),供下面三个监听调用。
115 runDecisionRef.current = () => {
116 let key = currentEntryKey();
117 const hash = window.location.hash;
118 // 去重 vs 识别克隆条目:浏览器对原生 location.hash 跳转(如无 clipboard 时点 §N 自锚
119 // 走的 `window.location.hash = …` fallback)会**克隆**当前条目的 history.state,连
120 // __kbScrollKey 一起复制 → 新条目与原条目共用一个 key。
121 // - key 相同且 hash 也相同 → 真·同条目重复触发 → 去重 return(原行为)。
122 // - key 相同但 hash 变了 → 这是克隆出来的新条目 → 给它重发独立 key,使其有自己的
123 // scrollTop 槽位:避免原条目阅读位置被锚点位置覆盖、且后退回原条目时还原不被误挡。
124 if (key === lastDecidedKeyRef.current) {
125 if (hash === lastDecidedHashRef.current) return;
126 key = forceNewEntryKey();
127 }
128 const url = currentHref();
129 lastDecidedKeyRef.current = key;
130 lastDecidedHashRef.current = hash;
131 keyRef.current = key;
132 decidedHrefRef.current = url;
133
134 cancelRestoreRef.current?.(); // 取消在途还原(会解除 isRestoring)
135 cancelRestoreRef.current = null;
136 clearScrollClaim(); // 丢掉上一次导航残留的接管,避免误让位本次(尤其全新锚点导航)
137
138 const container = findContainer();
139 if (!container) return;
140
141 // 优先级:① 精确 entry-key 命中(后退/前进回到本条目)→ 无条件还原,最贴合浏览器
142 // 原生语义,即便带 hash 也还原到离开时的位置;② 否则若本次导航带显式 hash(点引用/
143 // 锚点链接)→ 让位给 HashScroller 跳锚点,不被 URL 兜底还原盖掉(显式锚点是明确意图,
144 // 优先于"这个页面上次读到哪");③ 无 hash 时才用 URL 兜底还原曾读过的页面。
145 const byEntry = getSavedScrollByEntry(key);
146 const byUrl = window.location.hash ? undefined : getSavedScrollByUrl(url);
147 const saved = byEntry ?? byUrl;
148 if (saved != null) {
149 // 还原阅读位置:接管让 HashScroller 让位(否则它会把页面又拽到锚点,盖掉真实阅读位置)。
150 const token = holdScrollControl();
151 isRestoringRef.current = true;
152 cancelRestoreRef.current = restoreWhenStable(container, saved, () => {
153 isRestoringRef.current = false;
154 cancelRestoreRef.current = null;
155 // 还原结束后延后释放接管(盖过 HashScroller 同布局稳定点的 tryScroll)。
156 window.setTimeout(() => releaseScrollControl(token), RELEASE_DELAY_MS);
157 });

Callers

nothing calls this directly

Calls 11

currentEntryKeyFunction · 0.90
forceNewEntryKeyFunction · 0.90
clearScrollClaimFunction · 0.90
getSavedScrollByEntryFunction · 0.90
getSavedScrollByUrlFunction · 0.90
holdScrollControlFunction · 0.90
releaseScrollControlFunction · 0.90
saveScrollFunction · 0.90
currentHrefFunction · 0.85
findContainerFunction · 0.85
restoreWhenStableFunction · 0.85

Tested by

no test coverage detected