fix: preserve source-relative Tolaria page routes
This commit is contained in:
parent
2457821eeb
commit
d7afc7943d
@ -45,6 +45,9 @@
|
||||
- 增强引用提示面板、段落节奏、代码块、表格与行内代码层次。
|
||||
9. `0009-test-lock-in-readable-editor-theme-defaults.patch`
|
||||
- 固化更宽的阅读列与带颜色的行内代码主题默认值。
|
||||
10. `0010-fix-resolve-relative-page-links-from-source-notes.patch`
|
||||
- 按当前笔记所在目录归一化 `../` 与 `./` 页面路径;
|
||||
- 避免多个页面都叫 `README.md` 时跳到错误页面。
|
||||
|
||||
## 恢复与验证
|
||||
|
||||
@ -58,12 +61,13 @@ git am /path/to/0006-fix-make-Guanghu-home-and-themes-visible.patch
|
||||
git am /path/to/0007-feat-complete-portable-page-block-set.patch
|
||||
git am /path/to/0008-fix-restore-internal-note-links-and-reading-rhythm.patch
|
||||
git am /path/to/0009-test-lock-in-readable-editor-theme-defaults.patch
|
||||
git am /path/to/0010-fix-resolve-relative-page-links-from-source-notes.patch
|
||||
pnpm build
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
`0001`、`0002` 保留为早期两补丁拆分档案;只有在不使用 `0003`
|
||||
原型系列时才单独应用。上述 `0003 → 0009` 顺序已于 2026-07-17
|
||||
原型系列时才单独应用。上述 `0003 → 0010` 顺序已于 2026-07-17
|
||||
在服务器最新 `bingshuo/guanghu@514ab19` 的新克隆上完整复现。
|
||||
|
||||
## 研发规则
|
||||
|
||||
@ -0,0 +1,176 @@
|
||||
From 63a544d9fbb30bdcdb7377947fe22947a5276005 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com>
|
||||
Date: Fri, 17 Jul 2026 11:26:04 +0800
|
||||
Subject: [PATCH] fix: resolve relative page links from source notes
|
||||
|
||||
---
|
||||
src/components/SingleEditorView.tsx | 2 +-
|
||||
.../useEditorLinkActivation.test.tsx | 22 +++++++---
|
||||
src/components/useEditorLinkActivation.ts | 44 ++++++++++++++++---
|
||||
3 files changed, 57 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/src/components/SingleEditorView.tsx b/src/components/SingleEditorView.tsx
|
||||
index df43bd1..0440e5a 100644
|
||||
--- a/src/components/SingleEditorView.tsx
|
||||
+++ b/src/components/SingleEditorView.tsx
|
||||
@@ -1208,7 +1208,7 @@ export function SingleEditorView({ editor, entries, onNavigateWikilink, onChange
|
||||
handleMouseMove: handleCodeBlockCopyMouseMove,
|
||||
} = useCodeBlockCopyTarget(containerRef)
|
||||
useBlockNoteSideMenuHoverGuard(containerRef)
|
||||
- useEditorLinkActivation(containerRef, onNavigateWikilink, vaultPath)
|
||||
+ useEditorLinkActivation(containerRef, onNavigateWikilink, vaultPath, sourceEntry?.path)
|
||||
|
||||
useEffect(() => {
|
||||
_wikilinkEntriesRef.current = entries
|
||||
diff --git a/src/components/useEditorLinkActivation.test.tsx b/src/components/useEditorLinkActivation.test.tsx
|
||||
index bbfcaee..321bc00 100644
|
||||
--- a/src/components/useEditorLinkActivation.test.tsx
|
||||
+++ b/src/components/useEditorLinkActivation.test.tsx
|
||||
@@ -20,17 +20,19 @@ const mockOpenLocalFile = vi.mocked(openLocalFile)
|
||||
function Harness({
|
||||
onNavigateWikilink,
|
||||
vaultPath,
|
||||
+ sourcePath,
|
||||
}: {
|
||||
onNavigateWikilink: (target: string) => void
|
||||
vaultPath?: string
|
||||
+ sourcePath?: string
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
- useEditorLinkActivation(containerRef, onNavigateWikilink, vaultPath)
|
||||
+ useEditorLinkActivation(containerRef, onNavigateWikilink, vaultPath, sourcePath)
|
||||
return <div ref={containerRef} data-testid="editor-link-container" />
|
||||
}
|
||||
|
||||
-function renderHarness(onNavigateWikilink = vi.fn(), vaultPath?: string) {
|
||||
- render(<Harness onNavigateWikilink={onNavigateWikilink} vaultPath={vaultPath} />)
|
||||
+function renderHarness(onNavigateWikilink = vi.fn(), vaultPath?: string, sourcePath?: string) {
|
||||
+ render(<Harness onNavigateWikilink={onNavigateWikilink} vaultPath={vaultPath} sourcePath={sourcePath} />)
|
||||
return {
|
||||
container: screen.getByTestId('editor-link-container') as HTMLDivElement,
|
||||
onNavigateWikilink,
|
||||
@@ -129,7 +131,7 @@ describe('useEditorLinkActivation', () => {
|
||||
})
|
||||
|
||||
it('opens relative Markdown note links inside Tolaria on a plain click', async () => {
|
||||
- const { container, onNavigateWikilink } = renderHarness()
|
||||
+ const { container, onNavigateWikilink } = renderHarness(vi.fn(), '/vault', '/vault/docs/NEXT.md')
|
||||
const link = appendUrl(container, '../Migration/Related%20Page%20abc123.md#current-status')
|
||||
|
||||
const plainClick = dispatchMouseEvent(link, 'click')
|
||||
@@ -137,7 +139,17 @@ describe('useEditorLinkActivation', () => {
|
||||
expect(plainClick.defaultPrevented).toBe(true)
|
||||
expect(mockOpenLocalFile).not.toHaveBeenCalled()
|
||||
await Promise.resolve()
|
||||
- expect(onNavigateWikilink).toHaveBeenCalledWith('../Migration/Related Page abc123')
|
||||
+ expect(onNavigateWikilink).toHaveBeenCalledWith('Migration/Related Page abc123')
|
||||
+ })
|
||||
+
|
||||
+ it('resolves duplicate README links from the current note directory', async () => {
|
||||
+ const { container, onNavigateWikilink } = renderHarness(vi.fn(), '/vault', '/vault/docs/NEXT.md')
|
||||
+ const link = appendUrl(container, '../apps/tolaria/modules/02-knowledge-rendering/README.md')
|
||||
+
|
||||
+ dispatchMouseEvent(link, 'click')
|
||||
+
|
||||
+ await Promise.resolve()
|
||||
+ expect(onNavigateWikilink).toHaveBeenCalledWith('apps/tolaria/modules/02-knowledge-rendering/README')
|
||||
})
|
||||
|
||||
it('consumes Markdown note link mousedown before editor internals can select it', async () => {
|
||||
diff --git a/src/components/useEditorLinkActivation.ts b/src/components/useEditorLinkActivation.ts
|
||||
index 79a2dad..31c49e0 100644
|
||||
--- a/src/components/useEditorLinkActivation.ts
|
||||
+++ b/src/components/useEditorLinkActivation.ts
|
||||
@@ -34,14 +34,46 @@ function decodeLinkPath(path: string) {
|
||||
}
|
||||
}
|
||||
|
||||
-function resolveMarkdownNoteTarget(href: string) {
|
||||
+function normalizePathSegments(path: string) {
|
||||
+ const segments: string[] = []
|
||||
+ for (const segment of path.split('/')) {
|
||||
+ if (!segment || segment === '.') continue
|
||||
+ if (segment === '..') {
|
||||
+ segments.pop()
|
||||
+ continue
|
||||
+ }
|
||||
+ segments.push(segment)
|
||||
+ }
|
||||
+ return segments.join('/')
|
||||
+}
|
||||
+
|
||||
+function sourceRelativeDirectory(vaultPath?: string, sourcePath?: string) {
|
||||
+ if (!vaultPath || !sourcePath) return null
|
||||
+
|
||||
+ const normalizedVault = vaultPath.replace(/\\/gu, '/').replace(/\/+$/u, '')
|
||||
+ const normalizedSource = sourcePath.replace(/\\/gu, '/')
|
||||
+ const prefix = `${normalizedVault}/`
|
||||
+ if (!normalizedSource.toLowerCase().startsWith(prefix.toLowerCase())) return null
|
||||
+
|
||||
+ const relativeSource = normalizedSource.slice(prefix.length)
|
||||
+ const slashIndex = relativeSource.lastIndexOf('/')
|
||||
+ return slashIndex === -1 ? '' : relativeSource.slice(0, slashIndex)
|
||||
+}
|
||||
+
|
||||
+function resolveMarkdownNoteTarget(href: string, vaultPath?: string, sourcePath?: string) {
|
||||
const path = href.split(/[?#]/u, 1)[0]?.trim() ?? ''
|
||||
if (!path || path.startsWith('//') || /^[a-z][a-z\d+.-]*:/iu.test(path)) return null
|
||||
|
||||
const decodedPath = decodeLinkPath(path).replace(/\\/gu, '/')
|
||||
if (!/\.md(?:own)?$/iu.test(decodedPath)) return null
|
||||
|
||||
- return decodedPath.replace(/\.md(?:own)?$/iu, '')
|
||||
+ const pathWithoutExtension = decodedPath.replace(/\.md(?:own)?$/iu, '')
|
||||
+ if (pathWithoutExtension.startsWith('/')) return normalizePathSegments(pathWithoutExtension)
|
||||
+
|
||||
+ const sourceDirectory = sourceRelativeDirectory(vaultPath, sourcePath)
|
||||
+ return normalizePathSegments(
|
||||
+ sourceDirectory === null ? pathWithoutExtension : `${sourceDirectory}/${pathWithoutExtension}`,
|
||||
+ )
|
||||
}
|
||||
|
||||
function blurActiveEditable(container: HTMLElement) {
|
||||
@@ -91,6 +123,7 @@ function handleEditorLinkClick(
|
||||
container: HTMLElement,
|
||||
onNavigateWikilink: (target: string) => void,
|
||||
vaultPath?: string,
|
||||
+ sourcePath?: string,
|
||||
) {
|
||||
const target = elementFromEventTarget(event.target)
|
||||
if (!target || isInsideCodeContext(target)) return
|
||||
@@ -104,7 +137,7 @@ function handleEditorLinkClick(
|
||||
const href = resolveAnchorHref(target)
|
||||
if (!href) return
|
||||
|
||||
- const markdownTarget = resolveMarkdownNoteTarget(href)
|
||||
+ const markdownTarget = resolveMarkdownNoteTarget(href, vaultPath, sourcePath)
|
||||
if (markdownTarget) {
|
||||
activateWikilink(event, container, markdownTarget, onNavigateWikilink)
|
||||
return
|
||||
@@ -146,6 +179,7 @@ export function useEditorLinkActivation(
|
||||
containerRef: RefObject<HTMLDivElement | null>,
|
||||
onNavigateWikilink: (target: string) => void,
|
||||
vaultPath?: string,
|
||||
+ sourcePath?: string,
|
||||
) {
|
||||
useEffect(() => {
|
||||
const container = containerRef.current
|
||||
@@ -187,7 +221,7 @@ export function useEditorLinkActivation(
|
||||
}
|
||||
|
||||
clearHandledMouseDownUrl()
|
||||
- handleEditorLinkClick(event, container, onNavigateWikilink, vaultPath)
|
||||
+ handleEditorLinkClick(event, container, onNavigateWikilink, vaultPath, sourcePath)
|
||||
}
|
||||
|
||||
container.addEventListener('mousedown', handleMouseDown, true)
|
||||
@@ -207,5 +241,5 @@ export function useEditorLinkActivation(
|
||||
clearHandledMouseDownUrl()
|
||||
resetModifierState()
|
||||
}
|
||||
- }, [containerRef, onNavigateWikilink, vaultPath])
|
||||
+ }, [containerRef, onNavigateWikilink, sourcePath, vaultPath])
|
||||
}
|
||||
--
|
||||
2.50.1 (Apple Git-155)
|
||||
|
||||
@ -30,6 +30,7 @@ Tolaria 页面路由的兼容层;当前页面还把部分参考路径放在代
|
||||
|
||||
- `apps/tolaria/patches/0008-fix-restore-internal-note-links-and-reading-rhythm.patch`
|
||||
- `apps/tolaria/patches/0009-test-lock-in-readable-editor-theme-defaults.patch`
|
||||
- `apps/tolaria/patches/0010-fix-resolve-relative-page-links-from-source-notes.patch`
|
||||
- 桌面验收包:`HoloLake Era 0.1.4 Review`
|
||||
- Bundle ID:`com.guanghu.desktop.review.20260717.page`
|
||||
- DMG SHA-256:`846884e8…c66bc303a`
|
||||
@ -37,6 +38,7 @@ Tolaria 页面路由的兼容层;当前页面还把部分参考路径放在代
|
||||
## 验证
|
||||
|
||||
- 回归测试先失败后修复:相对 Markdown 页面链接普通单击跳转;
|
||||
- 原生点击发现重复 `README.md` 会误跳后,再补回归测试:路径按来源页面目录归一化并跳到唯一正确页面;
|
||||
- 定向 Vitest:16 项通过;
|
||||
- ESLint、TypeScript 与 Vite production build 通过;
|
||||
- Tauri Apple Silicon App 与 DMG 构建通过;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user