diff --git a/src/components/Article.tsx b/src/components/Article.tsx index 80cb251..10fd247 100644 --- a/src/components/Article.tsx +++ b/src/components/Article.tsx @@ -15,7 +15,7 @@ import { extractSection } from "../data-loader"; import mermaid from "mermaid"; import { getIndexedData } from "../data-loader/file-index"; import { resolvePath } from "./utils/path"; -import { useLocation } from "@solidjs/router"; +import { useNavigateWithParams } from "./useNavigateWithParams"; export interface ArticleProps { src: string; @@ -76,7 +76,7 @@ function scrollToHash(hash: string) { * 用于将特定 src 位置的 md 文件显示为 markdown 文章 */ export const Article: Component = (props) => { - const location = useLocation(); + const navigate = useNavigateWithParams(); const [content, { refetch }] = createResource( () => ({ src: props.src, section: props.section }), fetchArticleContent, @@ -97,10 +97,33 @@ export const Article: Component = (props) => { void mermaid.run(); // 内容渲染后检查 hash 并滚动 - scrollToHash(location.hash); + scrollToHash(window.location.hash); } }); + // Intercept markdown links to use client-side navigation with + // preserved URL search params. + createEffect(() => { + const dom = contentDom(); + if (!dom) return; + + const onClick = (e: MouseEvent) => { + const anchor = (e.target as HTMLElement).closest("a[href]"); + if (!anchor) return; + const href = anchor.getAttribute("href"); + if (!href) return; + // Only intercept same-origin navigation links (not external, not anchors) + if (href.startsWith("http") || href.startsWith("//")) return; + if (href.startsWith("#")) return; + + e.preventDefault(); + navigate(href); + }; + + dom.addEventListener("click", onClick); + onCleanup(() => dom.removeEventListener("click", onClick)); + }); + onCleanup(() => { // 清理时清空内容,触发内部组件的销毁 }); diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx index 4c22660..d8a083f 100644 --- a/src/components/FileTree.tsx +++ b/src/components/FileTree.tsx @@ -1,6 +1,6 @@ import { Component, createMemo, createSignal, Show } from "solid-js"; import { type FileNode, type TocNode } from "../data-loader"; -import { useNavigate } from "@solidjs/router"; +import { useNavigateWithParams } from "./useNavigateWithParams"; /** * 检查当前文件路径是否在文件夹内 @@ -22,7 +22,7 @@ export const FileTreeNode: Component<{ onClose: () => void; isHidden?: (node: FileNode) => boolean; }> = (props) => { - const navigate = useNavigate(); + const navigate = useNavigateWithParams(); const isDir = !!props.node.children; const isActive = createMemo(() => props.currentPath === props.node.path); // 默认收起,除非当前文件在该文件夹内 @@ -85,7 +85,7 @@ export const HeadingNode: Component<{ depth: number; isHidden?: (node: TocNode) => boolean; }> = (props) => { - const navigate = useNavigate(); + const navigate = useNavigateWithParams(); const anchor = props.node.id || ""; const href = `${props.basePath}#${anchor}`; const hasChildren = !!props.node.children; @@ -99,6 +99,7 @@ export const HeadingNode: Component<{ } }; const handleClick = (e: MouseEvent) => { + e.preventDefault(); navigate(href); // 滚动到目标元素,考虑导航栏高度偏移 requestAnimationFrame(() => { diff --git a/src/components/journal/types/link.tsx b/src/components/journal/types/link.tsx index 5c149b2..c1d7238 100644 --- a/src/components/journal/types/link.tsx +++ b/src/components/journal/types/link.tsx @@ -6,7 +6,7 @@ */ import { Component } from "solid-js"; -import { useNavigate } from "@solidjs/router"; +import { useNavigateWithParams } from "../../useNavigateWithParams"; import { z } from "zod"; import { registerMessageType } from "../registry"; import { journalSetState } from "../../stores/journalStream"; @@ -22,7 +22,7 @@ export type LinkPayload = z.infer; function fileNameFromPath(path: string): string { const parts = path.split("/").filter(Boolean); - return parts[parts.length - 1] || path; + return decodeURIComponent(parts[parts.length - 1] || path); } /** Encode a path, preserving / separators but encoding each segment */ @@ -42,7 +42,7 @@ function slugToTitle(slug: string): string { } const RevealLink: Component = (p) => { - const navigate = useNavigate(); + const navigate = useNavigateWithParams(); const target = () => { let t = encodePath(p.path); diff --git a/src/components/useNavigateWithParams.ts b/src/components/useNavigateWithParams.ts new file mode 100644 index 0000000..ce6b699 --- /dev/null +++ b/src/components/useNavigateWithParams.ts @@ -0,0 +1,21 @@ +/** + * useNavigateWithParams — wraps navigate() to preserve the current URL + * search params (e.g. ?session=abc&player=alice) when navigating to a + * new path. + */ + +import { useNavigate, useLocation } from "@solidjs/router"; + +export function useNavigateWithParams() { + const navigate = useNavigate(); + const location = useLocation(); + + return (to: string) => { + // Split hash from the path so we can re-attach it after search params + const hashIdx = to.indexOf("#"); + const path = hashIdx >= 0 ? to.slice(0, hashIdx) : to; + const hash = hashIdx >= 0 ? to.slice(hashIdx) : ""; + + navigate(path + location.search + hash); + }; +}