From 722a8110e69b2667bb9671a7093ca405e4e9148f Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 12 Jul 2026 10:46:35 +0800 Subject: [PATCH] feat(file-tree): use anchor tags for better navigation Convert div elements to anchor tags in FileTree and HeadingNode to enable native browser features like middle-click to open in new tabs. The implementation preserves SPA navigation for left-clicks while ensuring search parameters are preserved in the href. --- src/components/FileTree.tsx | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx index df0e96f..76a7c3e 100644 --- a/src/components/FileTree.tsx +++ b/src/components/FileTree.tsx @@ -1,4 +1,5 @@ import { Component, createMemo, createSignal, Show } from "solid-js"; +import { useLocation } from "@solidjs/router"; import { type FileNode, type TocNode } from "../data-loader"; import { useNavigateWithParams } from "./useNavigateWithParams"; import { useScrollContainer } from "../App"; @@ -24,6 +25,7 @@ export const FileTreeNode: Component<{ isHidden?: (node: FileNode) => boolean; }> = (props) => { const navigate = useNavigateWithParams(); + const location = useLocation(); const isDir = !!props.node.children; const isActive = createMemo(() => props.currentPath === props.node.path); // 默认收起,除非当前文件在该文件夹内 @@ -31,21 +33,28 @@ export const FileTreeNode: Component<{ isDir && isPathInDir(props.currentPath, props.node.path), ); - const handleClick = () => { + const href = () => props.node.path + location.search; + + const handleClick = (e: MouseEvent) => { if (isDir) { + e.preventDefault(); setIsExpanded(!isExpanded()); - } else { + } else if (e.button === 0) { + // Left-click: use SPA navigation + e.preventDefault(); navigate(props.node.path); props.onClose(); } + // Middle-click / right-click: let the browser handle the natively }; const indent = props.depth * 12; return (
-
📄 {props.node.name} -
+
{props.node.children!.map((child) => ( @@ -87,8 +96,9 @@ export const HeadingNode: Component<{ isHidden?: (node: TocNode) => boolean; }> = (props) => { const navigate = useNavigateWithParams(); + const location = useLocation(); const anchor = props.node.id || ""; - const href = `${props.basePath}#${anchor}`; + const href = () => `${props.basePath}${location.search}#${anchor}`; const hasChildren = !!props.node.children; // 默认收起,除非当前锚点在该节点内 const [isExpanded, setIsExpanded] = createSignal(props.depth <= 0); @@ -102,8 +112,12 @@ export const HeadingNode: Component<{ const scrollContainer = useScrollContainer(); const handleClick = (e: MouseEvent) => { - e.preventDefault(); - navigate(href); + if (e.button === 0) { + // Left-click: use SPA navigation + e.preventDefault(); + navigate(href()); + } + // Middle-click / right-click: let the browser handle the natively // 滚动到目标元素,考虑导航栏高度偏移 requestAnimationFrame(() => { const element = document.getElementById(anchor); @@ -141,7 +155,7 @@ export const HeadingNode: Component<{