/**
* 拦截文章正文/目录里的 hash 锚点点击,阻止 Dioxus 接管导致整页刷新。
*
* 背景:Dioxus 0.7 的 NativeInterpreter 通过事件委托在根节点上监听 click。
* 只要 app 任意位置用了 onclick(本项目 post_nav_links.rs 有),Dioxus 就启用
* 全局 click 委托。点击 dangerous_inner_html 注入的原生 时:
* 1. click 冒泡到根 → handleEvent 同步 POST /__events
* 2. 目标 没有 Dioxus onclick → response 不含 preventDefault
* 3. 兜底分支 handleClickNavigate: event.preventDefault() + browser_open IPC
* 4. browser_open 走 History::external → window.location.set_href("#id") → 整页刷新
*
* 结果:原生 fragment-scroll 被 preventDefault 拦掉,IPC 又把 hash 当外部 URL
* 整页加载,表现为「点击锚点刷新文章页且不滚动」。WASM 未下载完时是纯 SSR HTML,
* 走浏览器原生 fragment-scroll 正常——所以问题只在 hydration 后出现。
*
* 修复:在 capture 阶段(早于 Dioxus 的 bubble 委托监听器)拦截指向同页 hash 的
* 点击,stopPropagation 阻止事件到达 Dioxus 的委托监听器,自行滚动到标题
* (scrollToHeading 手动扣除 header 高度)并用 history.replaceState 更新 URL hash
* (不触发 popstate/hashchange,避免抖动)。
*/
import { scrollToHeading } from './scroll-to-heading';
let installed = false;
/**
* 安装全局 hash 锚点点击拦截器。幂等:重复调用安全。
*
* 用 capture 阶段 + 在 window 上注册,保证在 Dioxus 的根监听器之前执行;
* stopPropagation 阻止事件继续传播,Dioxus 的 handleEvent/handleClickNavigate
* 不会收到这个事件,browser_open IPC 不触发,整页刷新被避免。
*/
export function initAnchorClick(): void {
if (installed) return;
installed = true;
window.addEventListener(
'click',
(event) => {
// 只处理无修饰键的左键点击(中键/ctrl/cmd 点击交给浏览器新窗口行为)。
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return;
}
const target = event.target;
if (!(target instanceof Element)) return;
// 从点击点向上找最近的 (点击可能落在 内的子元素上)。
const anchor = target.closest('a');
if (!anchor) return;
const href = anchor.getAttribute('href');
// 只接管同页 hash 锚点:href 以 # 开头且非空(排除纯 "#" 和 "#top" 之外的真锚点)。
// 外链、路径链接、空 hash 都交给浏览器/Dioxus 原行为。
if (!href?.startsWith('#') || href === '#') return;
const id = decodeURIComponent(href.slice(1));
const el = document.getElementById(id);
if (!el) return; // 目标不存在:放行,交还原生行为(不滚动但不刷新)
// 阻止事件继续传播到 Dioxus 的委托监听器 → 避免 handleClickNavigate。
event.stopPropagation();
event.preventDefault();
// 平滑滚动到目标标题,手动扣除 sticky header 高度(见 scroll-to-heading.ts)。
scrollToHeading(el, true);
// 更新 URL hash 但不产生历史记录抖动:replaceState 不触发 popstate/hashchange。
// 这样地址栏显示当前章节,刷新页面也能 scrollToHash 回到原位。
if (window.location.hash !== href) {
history.replaceState(null, '', href);
}
},
true, // capture:在 Dioxus 的 bubble 监听器之前执行
);
}