D164: 登记Tolaria光湖定制版源码补丁
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled

This commit is contained in:
冰朔 2026-07-03 13:43:58 +08:00
parent 64340534c0
commit a3cce320ab
2 changed files with 435 additions and 0 deletions

View File

@ -0,0 +1,44 @@
---
id: DEV-TOL-SRC-001
protocol: HLDP
product: Tolaria光湖定制版
upstream: https://github.com/refactoringhq/tolaria
upstream_commit: af6891b0af725a219427eb0ff056cbc719e40812
local_commit: 9bd612c
status: installed
maintainer: ICE-GL-ZY001
receipt: CC-074
---
# Tolaria光湖定制版 · 源码补丁入口
## 当前补丁
`patches/0001-feat-add-plain-click-wikilinks-and-Guanghu-portal-bl.patch`
包含:
1. 富文本wikilink普通鼠标单击跳转。
2. 中文、Emoji与Notion长编号后缀回归测试。
3. `guanghu-portal` Markdown持久化组件。
4. 光湖入口卡富文本渲染样式。
## 恢复方法
```bash
git clone https://github.com/refactoringhq/tolaria.git
cd tolaria
git checkout af6891b0af725a219427eb0ff056cbc719e40812
git am /path/to/0001-feat-add-plain-click-wikilinks-and-Guanghu-portal-bl.patch
```
## 已验证
- 4873项前端测试通过。
- TypeScript/Vite生产构建通过。
- Tauri macOS原生构建通过。
- 安装位置:`/Applications/Tolaria.app`
- 官方回退版:`/Applications/Tolaria-Official-2026.7.1.app`
> 认知链与桌面验收见 CC-074。

View File

@ -0,0 +1,391 @@
From 9bd612ca137fb1f40b5ddd022a68a73b2f38eb98 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A?= <zhuyuan@guanghu.local>
Date: Fri, 3 Jul 2026 13:43:14 +0800
Subject: [PATCH] feat: add plain-click wikilinks and Guanghu portal block
---
src/components/Editor.css | 44 +++++++++
src/components/editorSchema.tsx | 39 ++++++++
.../useEditorLinkActivation.test.tsx | 11 +--
src/components/useEditorLinkActivation.ts | 2 -
src/utils/editorDurableMarkdown.ts | 2 +
src/utils/guanghuPortalMarkdown.test.ts | 38 ++++++++
src/utils/guanghuPortalMarkdown.ts | 93 +++++++++++++++++++
src/utils/wikilink.test.ts | 23 +++++
8 files changed, 243 insertions(+), 9 deletions(-)
create mode 100644 src/utils/guanghuPortalMarkdown.test.ts
create mode 100644 src/utils/guanghuPortalMarkdown.ts
diff --git a/src/components/Editor.css b/src/components/Editor.css
index 39866cc..499b72f 100644
--- a/src/components/Editor.css
+++ b/src/components/Editor.css
@@ -22,6 +22,50 @@
opacity: 0.55;
}
+.guanghu-portal-card {
+ width: 100%;
+ margin: 0.5rem 0;
+ padding: 1rem 1.1rem;
+ border: 1px solid color-mix(in srgb, var(--colors-accent) 38%, var(--colors-border));
+ border-radius: 0.85rem;
+ background:
+ radial-gradient(circle at top right, color-mix(in srgb, var(--colors-accent) 16%, transparent), transparent 48%),
+ var(--colors-surface);
+ box-shadow: 0 0.35rem 1.25rem color-mix(in srgb, black 10%, transparent);
+}
+
+.guanghu-portal-card__eyebrow {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem;
+ margin-bottom: 0.55rem;
+ color: var(--colors-text-muted);
+ font-size: 0.72rem;
+ letter-spacing: 0.08em;
+}
+
+.guanghu-portal-card__status {
+ padding: 0.18rem 0.55rem;
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--colors-accent) 18%, transparent);
+ color: var(--colors-text);
+ letter-spacing: normal;
+}
+
+.guanghu-portal-card__title {
+ display: inline-flex;
+ font-size: 1.08rem;
+ font-weight: 650;
+}
+
+.guanghu-portal-card__description {
+ margin: 0.45rem 0 0;
+ color: var(--colors-text-muted);
+ font-size: 0.9rem;
+ line-height: 1.55;
+}
+
/* Breadcrumb bar: border can still react to the data attribute, but the
breadcrumb filename/title stays visible at all times. */
.breadcrumb-bar {
diff --git a/src/components/editorSchema.tsx b/src/components/editorSchema.tsx
index 769cec2..2984eff 100644
--- a/src/components/editorSchema.tsx
+++ b/src/components/editorSchema.tsx
@@ -24,6 +24,7 @@ import { MATH_BLOCK_TYPE, MATH_INLINE_TYPE, renderMathToHtml } from '../utils/ma
import { MERMAID_BLOCK_TYPE, mermaidFenceSource } from '../utils/mermaidMarkdown'
import { TLDRAW_BLOCK_TYPE, TLDRAW_DEFAULT_HEIGHT } from '../utils/tldrawMarkdown'
import { MARKDOWN_HIGHLIGHT_STYLE } from '../utils/markdownHighlightMarkdown'
+import { GUANGHU_PORTAL_BLOCK_TYPE } from '../utils/guanghuPortalMarkdown'
import type { VaultEntry } from '../types'
import { createTolariaCodeBlockOptions } from './codeBlockOptions'
import { NoteTitleIcon } from './NoteTitleIcon'
@@ -313,6 +314,42 @@ const MermaidBlock = createReactBlockSpec(
},
)
+const GuanghuPortalBlock = createReactBlockSpec(
+ {
+ type: GUANGHU_PORTAL_BLOCK_TYPE,
+ propSchema: {
+ source: { default: '' },
+ title: { default: '光湖入口' },
+ target: { default: '' },
+ description: { default: '' },
+ status: { default: '' },
+ },
+ content: 'none',
+ },
+ {
+ runsBefore: ['codeBlock'],
+ render: props => (
+ <article className="guanghu-portal-card" contentEditable={false}>
+ <div className="guanghu-portal-card__eyebrow">
+ <span>HLDP · 光湖入口</span>
+ {props.block.props.status ? (
+ <span className="guanghu-portal-card__status">{props.block.props.status}</span>
+ ) : null}
+ </div>
+ <span
+ className="guanghu-portal-card__title wikilink"
+ data-target={props.block.props.target}
+ >
+ {props.block.props.title}
+ </span>
+ {props.block.props.description ? (
+ <p className="guanghu-portal-card__description">{props.block.props.description}</p>
+ ) : null}
+ </article>
+ ),
+ },
+)
+
export function mediaBlockPropsForPreviewRuntime<T extends MediaBlockPreviewProps>(
props: T,
externalMediaPreview: boolean,
@@ -412,6 +449,7 @@ const TldrawBlock = createReactBlockSpec(
const codeBlock = createCodeBlockSpec(createTolariaCodeBlockOptions())
const audioBlock = AudioBlockSpec()
const mathBlock = MathBlock()
+const guanghuPortalBlock = GuanghuPortalBlock()
const mermaidBlock = MermaidBlock()
const tldrawBlock = TldrawBlock()
const videoBlock = VideoBlockSpec()
@@ -446,6 +484,7 @@ export const schema = BlockNoteSchema.create({
},
blockSpecs: {
audio: audioBlock,
+ guanghuPortalBlock,
mathBlock,
mermaidBlock,
tldrawBlock,
diff --git a/src/components/useEditorLinkActivation.test.tsx b/src/components/useEditorLinkActivation.test.tsx
index 2234d5c..ab3b62a 100644
--- a/src/components/useEditorLinkActivation.test.tsx
+++ b/src/components/useEditorLinkActivation.test.tsx
@@ -77,22 +77,18 @@ describe('useEditorLinkActivation', () => {
mockOpenLocalFile.mockClear()
})
- it('navigates wikilinks only on Cmd+click after the native click stack settles', async () => {
+ it('navigates wikilinks on a plain click after the native click stack settles', async () => {
const { container, onNavigateWikilink } = renderHarness()
const wikilink = appendWikilink(container, 'Alpha Project')
dispatchMouseEvent(wikilink, 'click')
expect(onNavigateWikilink).not.toHaveBeenCalled()
- const modifiedClick = dispatchMouseEvent(wikilink, 'click', { metaKey: true })
- expect(modifiedClick.defaultPrevented).toBe(true)
- expect(onNavigateWikilink).not.toHaveBeenCalled()
-
await Promise.resolve()
expect(onNavigateWikilink).toHaveBeenCalledWith('Alpha Project')
})
- it('consumes plain wikilink mousedown and click events before editor internals see stale link nodes', () => {
+ it('consumes plain wikilink events before editor internals see stale link nodes', async () => {
const { container, onNavigateWikilink } = renderHarness()
const wikilink = appendWikilink(container, 'Alpha Project')
@@ -101,7 +97,8 @@ describe('useEditorLinkActivation', () => {
expect(mouseDown.defaultPrevented).toBe(true)
expect(click.defaultPrevented).toBe(true)
- expect(onNavigateWikilink).not.toHaveBeenCalled()
+ await Promise.resolve()
+ expect(onNavigateWikilink).toHaveBeenCalledWith('Alpha Project')
})
it('blurs an active editor before navigating a Cmd-clicked wikilink', async () => {
diff --git a/src/components/useEditorLinkActivation.ts b/src/components/useEditorLinkActivation.ts
index aa80834..f29c624 100644
--- a/src/components/useEditorLinkActivation.ts
+++ b/src/components/useEditorLinkActivation.ts
@@ -56,8 +56,6 @@ function activateWikilink(
) {
consumeEditorLinkEvent(event)
- if (!hasFollowModifier(event)) return
-
blurActiveEditable(container)
scheduleAfterNativeClick(() => onNavigateWikilink(target))
}
diff --git a/src/utils/editorDurableMarkdown.ts b/src/utils/editorDurableMarkdown.ts
index f0234f1..6e6d30a 100644
--- a/src/utils/editorDurableMarkdown.ts
+++ b/src/utils/editorDurableMarkdown.ts
@@ -15,8 +15,10 @@ import { restoreMarkdownHighlightsInBlocks } from './markdownHighlightMarkdown'
import { serializeMathAwareBlocks } from './mathMarkdown'
import { mermaidMarkdownCodec } from './mermaidMarkdown'
import { tldrawMarkdownCodec } from './tldrawMarkdown'
+import { guanghuPortalMarkdownCodec } from './guanghuPortalMarkdown'
const EDITOR_DURABLE_MARKDOWN_CODECS = [
+ guanghuPortalMarkdownCodec,
mermaidMarkdownCodec,
tldrawMarkdownCodec,
] as const
diff --git a/src/utils/guanghuPortalMarkdown.test.ts b/src/utils/guanghuPortalMarkdown.test.ts
new file mode 100644
index 0000000..9bb518a
--- /dev/null
+++ b/src/utils/guanghuPortalMarkdown.test.ts
@@ -0,0 +1,38 @@
+import { describe, expect, it } from 'vitest'
+import {
+ GUANGHU_PORTAL_BLOCK_TYPE,
+ guanghuPortalMarkdownCodec,
+} from './guanghuPortalMarkdown'
+import {
+ injectDurableMarkdownBlocks,
+ preProcessDurableMarkdownBlocks,
+ serializeDurableMarkdownBlocks,
+} from './durableMarkdownBlocks'
+
+const SOURCE = `\`\`\`guanghu-portal
+{"title":"铸渊主控台","target":"🌳 铸渊 · Tolaria编号嵌套树 · NAV-CODE-TREE-001","description":"进入现实执行层","status":"运行中"}
+\`\`\`
+`
+
+describe('guanghuPortalMarkdownCodec', () => {
+ it('round-trips a Guanghu portal card through the rich editor pipeline', () => {
+ const processed = preProcessDurableMarkdownBlocks({
+ markdown: SOURCE,
+ codecs: [guanghuPortalMarkdownCodec],
+ })
+ const blocks = injectDurableMarkdownBlocks({
+ blocks: [{ type: 'paragraph', content: [{ type: 'text', text: processed.trim() }] }],
+ codecs: [guanghuPortalMarkdownCodec],
+ }) as Array<{ type: string; props: Record<string, string> }>
+
+ expect(blocks[0]?.type).toBe(GUANGHU_PORTAL_BLOCK_TYPE)
+ expect(blocks[0]?.props.title).toBe('铸渊主控台')
+ expect(blocks[0]?.props.target).toContain('铸渊')
+
+ expect(serializeDurableMarkdownBlocks({
+ blocks,
+ codecs: [guanghuPortalMarkdownCodec],
+ serializeOrdinaryBlocks: () => '',
+ })).toBe(SOURCE)
+ })
+})
diff --git a/src/utils/guanghuPortalMarkdown.ts b/src/utils/guanghuPortalMarkdown.ts
new file mode 100644
index 0000000..f083b25
--- /dev/null
+++ b/src/utils/guanghuPortalMarkdown.ts
@@ -0,0 +1,93 @@
+import {
+ type BlockLike,
+ type DurableBlockCodec,
+ type DurableFencePayloadInput,
+} from './durableMarkdownBlocks'
+
+export const GUANGHU_PORTAL_BLOCK_TYPE = 'guanghuPortalBlock'
+
+const TOKEN_PREFIX = '@@TOLARIA_GUANGHU_PORTAL:'
+const TOKEN_SUFFIX = '@@'
+
+export interface GuanghuPortalPayload {
+ source: string
+ title: string
+ target: string
+ description: string
+ status: string
+}
+
+function isRecord(value: unknown): value is Record<string, unknown> {
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
+}
+
+function readString(record: Record<string, unknown>, key: string): string {
+ const value = record[key]
+ return typeof value === 'string' ? value : ''
+}
+
+function decodePayload(payload: unknown): GuanghuPortalPayload | null {
+ if (!isRecord(payload)) return null
+ const source = readString(payload, 'source')
+ const title = readString(payload, 'title')
+ const target = readString(payload, 'target')
+ if (!source || !title || !target) return null
+ return {
+ source,
+ title,
+ target,
+ description: readString(payload, 'description'),
+ status: readString(payload, 'status'),
+ }
+}
+
+function readFenceMetadata(info: string): Record<string, never> | null {
+ return info.trim().toLowerCase() === 'guanghu-portal' ? {} : null
+}
+
+function buildPayload({ lines, start, end }: DurableFencePayloadInput): GuanghuPortalPayload {
+ const source = lines.slice(start, end + 1).join('')
+ const body = lines.slice(start + 1, end).join('').trim()
+ try {
+ const decoded = decodePayload({ ...JSON.parse(body), source })
+ if (decoded) return decoded
+ } catch {
+ // Invalid card data remains visible as an ordinary code block.
+ }
+ return { source, title: '光湖入口', target: '', description: body, status: '配置错误' }
+}
+
+function buildBlock(block: BlockLike, payload: GuanghuPortalPayload): BlockLike {
+ if (!payload.target) return block
+ return {
+ ...block,
+ type: GUANGHU_PORTAL_BLOCK_TYPE,
+ props: {
+ ...(block.props ?? {}),
+ source: payload.source,
+ title: payload.title,
+ target: payload.target,
+ description: payload.description,
+ status: payload.status,
+ },
+ content: undefined,
+ children: [],
+ }
+}
+
+function isPortalBlock(block: BlockLike): boolean {
+ return block.type === GUANGHU_PORTAL_BLOCK_TYPE
+ && typeof block.props?.source === 'string'
+ && typeof block.props?.target === 'string'
+}
+
+export const guanghuPortalMarkdownCodec: DurableBlockCodec = {
+ tokenPrefix: TOKEN_PREFIX,
+ tokenSuffix: TOKEN_SUFFIX,
+ readFenceMetadata,
+ buildPayload,
+ decodePayload,
+ buildBlock: (block, payload) => buildBlock(block, payload as GuanghuPortalPayload),
+ isBlock: isPortalBlock,
+ serializeBlock: block => block.props?.source ?? '',
+}
diff --git a/src/utils/wikilink.test.ts b/src/utils/wikilink.test.ts
index ed7864e..4aa125e 100644
--- a/src/utils/wikilink.test.ts
+++ b/src/utils/wikilink.test.ts
@@ -273,6 +273,29 @@ describe('canonicalWikilinkTargetForTitle', () => {
})
})
+describe('Unicode wikilink resolution', () => {
+ it('resolves Chinese and emoji titles across equivalent Unicode forms', () => {
+ const entry = makeEntry({
+ path: '/vault/第五域/🌳 铸渊 · 导航地图.md',
+ filename: '🌳 铸渊 · 导航地图.md',
+ title: '🌳 铸渊 · 导航地图',
+ })
+
+ expect(resolveEntry([entry], '🌳 铸渊 · 导航地图')).toBe(entry)
+ expect(resolveEntry([entry], '🌳 鑄淵 · 導航地圖')).toBeUndefined()
+ })
+
+ it('resolves an imported file with an id suffix by its heading title', () => {
+ const entry = makeEntry({
+ path: '/vault/🧭 霜砚 · 总控人格系统 · 母语导航页 b8463f86479348139b5744dd5e995c53.md',
+ filename: '🧭 霜砚 · 总控人格系统 · 母语导航页 b8463f86479348139b5744dd5e995c53.md',
+ title: '🧭 霜砚 · 总控人格系统 · 母语导航页',
+ })
+
+ expect(resolveEntry([entry], '🧭 霜砚 · 总控人格系统 · 母语导航页')).toBe(entry)
+ })
+})
+
describe('formatWikilinkRef', () => {
it('wraps a canonical target in wikilink syntax', () => {
expect(formatWikilinkRef('projects/alpha')).toBe('[[projects/alpha]]')
--
2.50.1 (Apple Git-155)