Compare commits
8 Commits
windows-in
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
789bd691f5 | ||
|
|
40a4e347fc | ||
|
|
d7afc7943d | ||
|
|
2457821eeb | ||
|
|
6fb1c02ddd | ||
|
|
025f22bf8f | ||
|
|
e2a1e88e0f | ||
|
|
d10824e685 |
71
.github/workflows/build-windows-manual.yml
vendored
71
.github/workflows/build-windows-manual.yml
vendored
@ -1,6 +1,11 @@
|
|||||||
name: Build Windows Internal Installer
|
name: Build Windows Internal Installer
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/build-windows-manual.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
@ -19,7 +24,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
build-windows:
|
build-windows:
|
||||||
name: Build Windows x64 internal installer
|
name: Build Windows x64 internal installer
|
||||||
runs-on: windows-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@ -35,6 +40,11 @@ jobs:
|
|||||||
node-version: "22"
|
node-version: "22"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install Linux cross-build dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y nsis lld llvm clang
|
||||||
|
|
||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@ -44,69 +54,58 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~\.cargo\registry
|
~/.cargo/registry
|
||||||
~\.cargo\git
|
~/.cargo/git
|
||||||
src-tauri\target
|
src-tauri/target
|
||||||
|
.xwin-cache
|
||||||
key: ${{ runner.os }}-internal-windows-cargo-x86_64-pc-windows-msvc-${{ env.RUST_TARGET_CACHE_VERSION }}-${{ hashFiles('src-tauri/Cargo.lock') }}
|
key: ${{ runner.os }}-internal-windows-cargo-x86_64-pc-windows-msvc-${{ env.RUST_TARGET_CACHE_VERSION }}-${{ hashFiles('src-tauri/Cargo.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-internal-windows-cargo-x86_64-pc-windows-msvc-${{ env.RUST_TARGET_CACHE_VERSION }}-
|
${{ runner.os }}-internal-windows-cargo-x86_64-pc-windows-msvc-${{ env.RUST_TARGET_CACHE_VERSION }}-
|
||||||
|
|
||||||
- name: Cache Tauri Windows tools
|
- name: Install cargo-xwin
|
||||||
uses: actions/cache@v4
|
run: cargo install --locked cargo-xwin
|
||||||
with:
|
|
||||||
path: ~\AppData\Local\tauri
|
|
||||||
key: ${{ runner.os }}-tauri-tools-nsis-3.11-nsis-tauri-utils-0.5.3
|
|
||||||
|
|
||||||
- name: Prefetch Tauri NSIS toolchain
|
|
||||||
shell: pwsh
|
|
||||||
run: ./.github/scripts/prefetch-tauri-nsis.ps1
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Clear cached Windows bundle artifacts
|
- name: Clear cached Windows bundle artifacts
|
||||||
shell: pwsh
|
|
||||||
run: |
|
run: |
|
||||||
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "src-tauri/target/x86_64-pc-windows-msvc/release/bundle"
|
rm -rf src-tauri/target/x86_64-pc-windows-msvc/release/bundle
|
||||||
|
|
||||||
- name: Set internal app version
|
- name: Set internal app version
|
||||||
shell: pwsh
|
|
||||||
run: |
|
run: |
|
||||||
$version = "${{ inputs.version }}"
|
VERSION="${{ inputs.version }}"
|
||||||
$tauri = Get-Content "src-tauri/tauri.conf.json" | ConvertFrom-Json
|
if [ -z "$VERSION" ]; then VERSION="0.1.1"; fi
|
||||||
$tauri.version = $version
|
jq --arg v "$VERSION" '.version = $v' src-tauri/tauri.conf.json > tmp.json && mv tmp.json src-tauri/tauri.conf.json
|
||||||
$tauri | ConvertTo-Json -Depth 100 | Set-Content "src-tauri/tauri.conf.json"
|
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" src-tauri/Cargo.toml
|
||||||
(Get-Content "src-tauri/Cargo.toml") -replace '^version = ".*"$', "version = `"$version`"" | Set-Content "src-tauri/Cargo.toml"
|
|
||||||
|
|
||||||
- name: Build unsigned NSIS installer for internal testing
|
- name: Build unsigned NSIS installer for internal testing
|
||||||
shell: pwsh
|
|
||||||
env:
|
env:
|
||||||
VITE_SENTRY_DSN: ""
|
VITE_SENTRY_DSN: ""
|
||||||
SENTRY_DSN: ""
|
SENTRY_DSN: ""
|
||||||
VITE_POSTHOG_KEY: ""
|
VITE_POSTHOG_KEY: ""
|
||||||
VITE_POSTHOG_HOST: ""
|
VITE_POSTHOG_HOST: ""
|
||||||
|
XWIN_CACHE_DIR: ${{ github.workspace }}/.xwin-cache
|
||||||
run: |
|
run: |
|
||||||
pnpm tauri build --target x86_64-pc-windows-msvc --bundles nsis
|
pnpm tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
|
||||||
|
|
||||||
- name: Validate installer
|
- name: Validate installer
|
||||||
shell: pwsh
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
$bundleDir = "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis"
|
shopt -s nullglob
|
||||||
$installers = @(Get-ChildItem -Path $bundleDir -Filter "*-setup.exe" -File -ErrorAction SilentlyContinue)
|
installers=(src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*-setup.exe)
|
||||||
if ($installers.Count -eq 0) {
|
if [ ${#installers[@]} -eq 0 ]; then
|
||||||
throw "Windows build produced no NSIS setup executable."
|
echo "::error::Windows build produced no NSIS setup executable."
|
||||||
}
|
exit 1
|
||||||
foreach ($installer in $installers) {
|
fi
|
||||||
if ($installer.Name -notlike "*${{ inputs.version }}*") {
|
for installer in "${installers[@]}"; do
|
||||||
throw "Windows installer has unexpected version in filename: $($installer.Name)"
|
echo "Built Windows internal installer: $installer"
|
||||||
}
|
done
|
||||||
Write-Host "Built Windows internal installer: $($installer.FullName)"
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Upload Windows internal installer
|
- name: Upload Windows internal installer
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.artifact_name }}
|
name: Guanghu-Windows-x64-internal
|
||||||
path: src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*-setup.exe
|
path: src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*-setup.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|||||||
@ -12,7 +12,8 @@
|
|||||||
|
|
||||||
## 当前界面迭代 · 冰湖视觉层
|
## 当前界面迭代 · 冰湖视觉层
|
||||||
|
|
||||||
`patches/` 中的补丁按编号顺序应用:
|
`patches/` 中的补丁以系列入口恢复。`0003` 已包含 `0001`、`0002`,
|
||||||
|
因此不能将 `0001`、`0002`、`0003` 连续重复应用:
|
||||||
|
|
||||||
1. `0001-feat-add-Guanghu-ice-lake-visual-layer.patch`
|
1. `0001-feat-add-Guanghu-ice-lake-visual-layer.patch`
|
||||||
- 为浅色与深色模式增加冰湖光层、网格层和语义颜色;
|
- 为浅色与深色模式增加冰湖光层、网格层和语义颜色;
|
||||||
@ -28,20 +29,51 @@
|
|||||||
- 收敛为轻量、可切换的光湖配套主题;
|
- 收敛为轻量、可切换的光湖配套主题;
|
||||||
- 修正 macOS 顶栏间距、深色应用图标和桌面窗口拖拽;
|
- 修正 macOS 顶栏间距、深色应用图标和桌面窗口拖拽;
|
||||||
- 增强本地 smoke 测试在大规模模拟笔记列表中的可重复性。
|
- 增强本地 smoke 测试在大规模模拟笔记列表中的可重复性。
|
||||||
|
5. `0005-Add-portable-callout-page-blocks.patch`
|
||||||
|
- 在 BlockNote 编辑器中增加 info / success / warning / danger 彩色提示块;
|
||||||
|
- 以可携带 Markdown callout 语法保存,重新打开不丢失。
|
||||||
|
6. `0006-fix-make-Guanghu-home-and-themes-visible.patch`
|
||||||
|
- 首次启动默认显示 HoloLake Era 世界首页;
|
||||||
|
- 使湖光浅色与湖心深色同步实际亮/暗模式,不再只改按钮文字。
|
||||||
|
7. `0007-feat-complete-portable-page-block-set.patch`
|
||||||
|
- 恢复 toggle heading / toggle list 折叠块入口;
|
||||||
|
- 增加批注块、关系卡和路径包,继续复用可携带 Markdown callout 协议;
|
||||||
|
- 保留现有代码块、引用、Mermaid、表格、白板与媒体块。
|
||||||
|
8. `0008-fix-restore-internal-note-links-and-reading-rhythm.patch`
|
||||||
|
- 将 Notion 导出的相对 Markdown 页面链接接回 Tolaria 内部导航;
|
||||||
|
- 普通单击直接打开目标笔记,外部网址仍保留 `⌘ + 点击`;
|
||||||
|
- 增强引用提示面板、段落节奏、代码块、表格与行内代码层次。
|
||||||
|
9. `0009-test-lock-in-readable-editor-theme-defaults.patch`
|
||||||
|
- 固化更宽的阅读列与带颜色的行内代码主题默认值。
|
||||||
|
10. `0010-fix-resolve-relative-page-links-from-source-notes.patch`
|
||||||
|
- 按当前笔记所在目录归一化 `../` 与 `./` 页面路径;
|
||||||
|
- 避免多个页面都叫 `README.md` 时跳到错误页面。
|
||||||
|
11. `0011-fix-resolve-links-inside-mounted-workspaces.patch`
|
||||||
|
- 使用来源页面所属的挂载工作区解析相对路径;
|
||||||
|
- 主仓与挂载知识库同时打开时仍能跳到正确页面。
|
||||||
|
|
||||||
## 恢复与验证
|
## 恢复与验证
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://guanghubingshuo.com/code/bingshuo/guanghu.git
|
git clone https://guanghubingshuo.com/code/bingshuo/guanghu.git
|
||||||
cd guanghu
|
cd guanghu
|
||||||
git am /path/to/0001-feat-add-Guanghu-ice-lake-visual-layer.patch
|
|
||||||
git am /path/to/0002-test-align-updater-coverage-with-Guanghu-policy.patch
|
|
||||||
git am /path/to/0003-20260713-hololake-era-shell-prototype-series.patch
|
git am /path/to/0003-20260713-hololake-era-shell-prototype-series.patch
|
||||||
git am /path/to/0004-20260715-tolaria-lightweight-themes-drag-fix.patch
|
git am /path/to/0004-20260715-tolaria-lightweight-themes-drag-fix.patch
|
||||||
|
git am /path/to/0005-Add-portable-callout-page-blocks.patch
|
||||||
|
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
|
||||||
|
git am /path/to/0011-fix-resolve-links-inside-mounted-workspaces.patch
|
||||||
pnpm build
|
pnpm build
|
||||||
pnpm lint
|
pnpm lint
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`0001`、`0002` 保留为早期两补丁拆分档案;只有在不使用 `0003`
|
||||||
|
原型系列时才单独应用。上述 `0003 → 0011` 顺序已于 2026-07-17
|
||||||
|
在服务器最新 `bingshuo/guanghu@514ab19` 的新克隆上完整复现。
|
||||||
|
|
||||||
## 研发规则
|
## 研发规则
|
||||||
|
|
||||||
- 新的页面、渲染模块和交互协议先在本目录登记,再以独立补丁落到可编译基线;
|
- 新的页面、渲染模块和交互协议先在本目录登记,再以独立补丁落到可编译基线;
|
||||||
|
|||||||
@ -4,3 +4,31 @@
|
|||||||
表格、Mermaid、媒体、白板与后续 Notion 式内容块。它只渲染正式事实源,不复制出第二套知识库。
|
表格、Mermaid、媒体、白板与后续 Notion 式内容块。它只渲染正式事实源,不复制出第二套知识库。
|
||||||
|
|
||||||
当前冰湖 UI 补丁位于 [`../../patches/`](../../patches/)。
|
当前冰湖 UI 补丁位于 [`../../patches/`](../../patches/)。
|
||||||
|
|
||||||
|
## 当前恢复状态
|
||||||
|
|
||||||
|
- 彩色 callout、批注、关系卡、路径包与折叠块已经恢复;
|
||||||
|
- 普通引用现在显示为有背景、有边界的提示面板;
|
||||||
|
- 标题、正文、代码块和表格之间已恢复清楚的阅读节奏;
|
||||||
|
- Notion 导出的相对 `.md` 页面链接会在 Tolaria 内普通单击跳转;
|
||||||
|
- 外部网址仍使用 `⌘ + 点击`,避免编辑时误开浏览器。
|
||||||
|
|
||||||
|
## 已完成内容块
|
||||||
|
|
||||||
|
冰朔在 2026-07-16 反馈:当前页面太单一,缺少类似 Notion 的内容块能力,尤其是彩色提示块和高亮代码块。
|
||||||
|
|
||||||
|
首批完成项:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 高亮代码块
|
||||||
|
2. 彩色提示块
|
||||||
|
3. 折叠块
|
||||||
|
4. 引用 / 标注块
|
||||||
|
5. 关系卡
|
||||||
|
6. 路径面包屑
|
||||||
|
7. 媒体 / 图表块
|
||||||
|
```
|
||||||
|
|
||||||
|
当前验收入口见:
|
||||||
|
|
||||||
|
- [NEXT-TODO-20260717 · 明日待办](../../../../docs/NEXT-TODO-20260717.md)
|
||||||
|
|||||||
635
apps/tolaria/patches/0005-Add-portable-callout-page-blocks.patch
Normal file
635
apps/tolaria/patches/0005-Add-portable-callout-page-blocks.patch
Normal file
@ -0,0 +1,635 @@
|
|||||||
|
From edf46c67334ed8bd5adbb0be719ab0dd51ffa266 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com>
|
||||||
|
Date: Thu, 16 Jul 2026 22:30:25 +0800
|
||||||
|
Subject: [PATCH 5/7] Add portable callout page blocks
|
||||||
|
|
||||||
|
---
|
||||||
|
src/components/Editor.css | 62 +++++++++++
|
||||||
|
src/components/editorSchema.tsx | 46 ++++++++
|
||||||
|
.../tolariaEditorFormatting.test.tsx | 58 ++++++++++
|
||||||
|
.../tolariaEditorFormattingConfig.ts | 101 +++++++++++++++++-
|
||||||
|
src/utils/blockNoteDirectMarkdown.test.ts | 12 +++
|
||||||
|
src/utils/blockNoteDirectMarkdown.ts | 12 +++
|
||||||
|
src/utils/calloutMarkdown.ts | 70 ++++++++++++
|
||||||
|
src/utils/editorDurableMarkdown.test.ts | 17 +++
|
||||||
|
src/utils/editorDurableMarkdown.ts | 3 +-
|
||||||
|
9 files changed, 379 insertions(+), 2 deletions(-)
|
||||||
|
create mode 100644 src/utils/calloutMarkdown.ts
|
||||||
|
|
||||||
|
diff --git a/src/components/Editor.css b/src/components/Editor.css
|
||||||
|
index 24f0738..5db3fe1 100644
|
||||||
|
--- a/src/components/Editor.css
|
||||||
|
+++ b/src/components/Editor.css
|
||||||
|
@@ -80,6 +80,68 @@
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
+.tolaria-callout {
|
||||||
|
+ --tolaria-callout-accent: #2563eb;
|
||||||
|
+ --tolaria-callout-bg: color-mix(in srgb, #2563eb 9%, var(--bg-primary));
|
||||||
|
+ display: grid;
|
||||||
|
+ grid-template-columns: 4px minmax(0, 1fr);
|
||||||
|
+ gap: 12px;
|
||||||
|
+ width: 100%;
|
||||||
|
+ margin: 0.45rem 0;
|
||||||
|
+ padding: 11px 13px 11px 0;
|
||||||
|
+ border: 1px solid color-mix(in srgb, var(--tolaria-callout-accent) 28%, var(--border));
|
||||||
|
+ border-radius: 8px;
|
||||||
|
+ background: var(--tolaria-callout-bg);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout[data-kind="success"] {
|
||||||
|
+ --tolaria-callout-accent: #168a48;
|
||||||
|
+ --tolaria-callout-bg: color-mix(in srgb, #168a48 10%, var(--bg-primary));
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout[data-kind="warning"] {
|
||||||
|
+ --tolaria-callout-accent: #b7791f;
|
||||||
|
+ --tolaria-callout-bg: color-mix(in srgb, #b7791f 13%, var(--bg-primary));
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout[data-kind="danger"] {
|
||||||
|
+ --tolaria-callout-accent: #dc2626;
|
||||||
|
+ --tolaria-callout-bg: color-mix(in srgb, #dc2626 10%, var(--bg-primary));
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout__marker {
|
||||||
|
+ width: 4px;
|
||||||
|
+ min-height: 100%;
|
||||||
|
+ border-radius: 999px;
|
||||||
|
+ background: var(--tolaria-callout-accent);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout__body {
|
||||||
|
+ display: grid;
|
||||||
|
+ gap: 4px;
|
||||||
|
+ min-width: 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout__label {
|
||||||
|
+ color: var(--tolaria-callout-accent);
|
||||||
|
+ font-size: 11px;
|
||||||
|
+ font-weight: 700;
|
||||||
|
+ line-height: 1.2;
|
||||||
|
+ text-transform: uppercase;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout__content {
|
||||||
|
+ min-width: 0;
|
||||||
|
+ color: var(--text-primary);
|
||||||
|
+ font-size: 14px;
|
||||||
|
+ line-height: 1.6;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout__content:empty::before {
|
||||||
|
+ color: var(--text-muted);
|
||||||
|
+ content: 'Write a note';
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/* 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 2984eff..7d62518 100644
|
||||||
|
--- a/src/components/editorSchema.tsx
|
||||||
|
+++ b/src/components/editorSchema.tsx
|
||||||
|
@@ -25,6 +25,11 @@ 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 {
|
||||||
|
+ calloutKind,
|
||||||
|
+ TOLARIA_CALLOUT_BLOCK_TYPE,
|
||||||
|
+ type TolariaCalloutKind,
|
||||||
|
+} from '../utils/calloutMarkdown'
|
||||||
|
import type { VaultEntry } from '../types'
|
||||||
|
import { createTolariaCodeBlockOptions } from './codeBlockOptions'
|
||||||
|
import { NoteTitleIcon } from './NoteTitleIcon'
|
||||||
|
@@ -350,6 +355,45 @@ const GuanghuPortalBlock = createReactBlockSpec(
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
+const CALLOUT_LABELS: Record<TolariaCalloutKind, string> = {
|
||||||
|
+ danger: 'Danger',
|
||||||
|
+ info: 'Info',
|
||||||
|
+ success: 'Success',
|
||||||
|
+ warning: 'Warning',
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+function TolariaCalloutRenderer({
|
||||||
|
+ block,
|
||||||
|
+ contentRef,
|
||||||
|
+}: {
|
||||||
|
+ block: { props: { kind?: string } }
|
||||||
|
+ contentRef: (node: HTMLElement | null) => void
|
||||||
|
+}) {
|
||||||
|
+ const kind = calloutKind(block.props.kind)
|
||||||
|
+ return (
|
||||||
|
+ <aside className="tolaria-callout" data-kind={kind}>
|
||||||
|
+ <span className="tolaria-callout__marker" aria-hidden="true" />
|
||||||
|
+ <div className="tolaria-callout__body">
|
||||||
|
+ <span className="tolaria-callout__label">{CALLOUT_LABELS[kind]}</span>
|
||||||
|
+ <div ref={contentRef} className="tolaria-callout__content" />
|
||||||
|
+ </div>
|
||||||
|
+ </aside>
|
||||||
|
+ )
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+const TolariaCalloutBlock = createReactBlockSpec(
|
||||||
|
+ {
|
||||||
|
+ type: TOLARIA_CALLOUT_BLOCK_TYPE,
|
||||||
|
+ propSchema: {
|
||||||
|
+ kind: { default: 'info' },
|
||||||
|
+ },
|
||||||
|
+ content: 'inline',
|
||||||
|
+ },
|
||||||
|
+ {
|
||||||
|
+ render: TolariaCalloutRenderer,
|
||||||
|
+ },
|
||||||
|
+)
|
||||||
|
+
|
||||||
|
export function mediaBlockPropsForPreviewRuntime<T extends MediaBlockPreviewProps>(
|
||||||
|
props: T,
|
||||||
|
externalMediaPreview: boolean,
|
||||||
|
@@ -450,6 +494,7 @@ const codeBlock = createCodeBlockSpec(createTolariaCodeBlockOptions())
|
||||||
|
const audioBlock = AudioBlockSpec()
|
||||||
|
const mathBlock = MathBlock()
|
||||||
|
const guanghuPortalBlock = GuanghuPortalBlock()
|
||||||
|
+const tolariaCalloutBlock = TolariaCalloutBlock()
|
||||||
|
const mermaidBlock = MermaidBlock()
|
||||||
|
const tldrawBlock = TldrawBlock()
|
||||||
|
const videoBlock = VideoBlockSpec()
|
||||||
|
@@ -485,6 +530,7 @@ export const schema = BlockNoteSchema.create({
|
||||||
|
blockSpecs: {
|
||||||
|
audio: audioBlock,
|
||||||
|
guanghuPortalBlock,
|
||||||
|
+ [TOLARIA_CALLOUT_BLOCK_TYPE]: tolariaCalloutBlock,
|
||||||
|
mathBlock,
|
||||||
|
mermaidBlock,
|
||||||
|
tldrawBlock,
|
||||||
|
diff --git a/src/components/tolariaEditorFormatting.test.tsx b/src/components/tolariaEditorFormatting.test.tsx
|
||||||
|
index 5c5143b..7bd06c2 100644
|
||||||
|
--- a/src/components/tolariaEditorFormatting.test.tsx
|
||||||
|
+++ b/src/components/tolariaEditorFormatting.test.tsx
|
||||||
|
@@ -7,7 +7,9 @@ vi.mock('../lib/telemetry', () => ({
|
||||||
|
}))
|
||||||
|
|
||||||
|
import {
|
||||||
|
+ addItemsAfterKey,
|
||||||
|
addItemsToMediaGroup,
|
||||||
|
+ createCalloutSlashMenuItems,
|
||||||
|
createMathSlashMenuItem,
|
||||||
|
filterTolariaFormattingToolbarItems,
|
||||||
|
filterTolariaSlashMenuItems,
|
||||||
|
@@ -18,6 +20,7 @@ import {
|
||||||
|
import { trackEvent } from '../lib/telemetry'
|
||||||
|
import { MATH_BLOCK_TYPE } from '../utils/mathMarkdown'
|
||||||
|
import { mermaidFenceSource } from '../utils/mermaidMarkdown'
|
||||||
|
+import { TOLARIA_CALLOUT_BLOCK_TYPE } from '../utils/calloutMarkdown'
|
||||||
|
|
||||||
|
describe('tolariaEditorFormatting', () => {
|
||||||
|
it('keeps the markdown-safe toolbar controls and block type select', () => {
|
||||||
|
@@ -73,6 +76,7 @@ describe('tolariaEditorFormatting', () => {
|
||||||
|
{ key: 'heading_4', title: 'Heading 4', onItemClick: () => {} },
|
||||||
|
{ key: 'bullet_list', title: 'Bullet List', subtext: 'Default list copy', onItemClick: () => {} },
|
||||||
|
{ key: 'code_block', title: 'Code Block', subtext: 'Default code copy', onItemClick: () => {} },
|
||||||
|
+ { key: 'tolaria_callout_info', title: 'Info callout', onItemClick: () => {} },
|
||||||
|
{ key: 'heading_5', title: 'Heading 5', onItemClick: () => {} },
|
||||||
|
{ key: 'heading_6', title: 'Heading 6', onItemClick: () => {} },
|
||||||
|
] satisfies TolariaSlashMenuTestItem[])
|
||||||
|
@@ -82,12 +86,14 @@ describe('tolariaEditorFormatting', () => {
|
||||||
|
'heading_4',
|
||||||
|
'bullet_list',
|
||||||
|
'code_block',
|
||||||
|
+ 'tolaria_callout_info',
|
||||||
|
])
|
||||||
|
expect(items.map((item) => item.subtext)).toEqual([
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
+ undefined,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
@@ -224,6 +230,58 @@ describe('tolariaEditorFormatting', () => {
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
+ it('places callout commands after quote blocks', () => {
|
||||||
|
+ type TolariaSlashMenuTestItem = {
|
||||||
|
+ key: string
|
||||||
|
+ title: string
|
||||||
|
+ group: string
|
||||||
|
+ onItemClick: () => void
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ const items = addItemsAfterKey([
|
||||||
|
+ { key: 'paragraph', title: 'Paragraph', group: 'Basic blocks', onItemClick: () => {} },
|
||||||
|
+ { key: 'quote', title: 'Quote', group: 'Basic blocks', onItemClick: () => {} },
|
||||||
|
+ { key: 'code_block', title: 'Code Block', group: 'Basic blocks', onItemClick: () => {} },
|
||||||
|
+ ] satisfies TolariaSlashMenuTestItem[], 'quote', [
|
||||||
|
+ { key: 'tolaria_callout_info', title: 'Info callout', group: 'Basic blocks', onItemClick: () => {} },
|
||||||
|
+ ])
|
||||||
|
+
|
||||||
|
+ expect(items.map(item => item.key)).toEqual([
|
||||||
|
+ 'paragraph',
|
||||||
|
+ 'quote',
|
||||||
|
+ 'tolaria_callout_info',
|
||||||
|
+ 'code_block',
|
||||||
|
+ ])
|
||||||
|
+ })
|
||||||
|
+
|
||||||
|
+ it('keeps callout slash commands searchable and inserts an editable callout block', async () => {
|
||||||
|
+ const block = { id: 'active-block' }
|
||||||
|
+ const editor = {
|
||||||
|
+ getTextCursorPosition: () => ({ block }),
|
||||||
|
+ replaceBlocks: () => {},
|
||||||
|
+ }
|
||||||
|
+ const replaceBlocks = vi.spyOn(editor, 'replaceBlocks')
|
||||||
|
+ const items = createCalloutSlashMenuItems(editor as never)
|
||||||
|
+ const warningItem = items.find(item => item.key === 'tolaria_callout_warning')
|
||||||
|
+
|
||||||
|
+ expect(warningItem).toEqual(expect.objectContaining({
|
||||||
|
+ title: 'Warning callout',
|
||||||
|
+ aliases: expect.arrayContaining(['warning', '警告']),
|
||||||
|
+ }))
|
||||||
|
+
|
||||||
|
+ warningItem?.onItemClick()
|
||||||
|
+
|
||||||
|
+ expect(replaceBlocks).toHaveBeenCalledWith([block], [{
|
||||||
|
+ type: TOLARIA_CALLOUT_BLOCK_TYPE,
|
||||||
|
+ props: { kind: 'warning' },
|
||||||
|
+ content: [{
|
||||||
|
+ type: 'text',
|
||||||
|
+ text: 'Warning callout',
|
||||||
|
+ styles: {},
|
||||||
|
+ }],
|
||||||
|
+ }])
|
||||||
|
+ })
|
||||||
|
+
|
||||||
|
it('creates a math slash command with a default display equation', () => {
|
||||||
|
const block = { id: 'active-block' }
|
||||||
|
const editor = {
|
||||||
|
diff --git a/src/components/tolariaEditorFormattingConfig.ts b/src/components/tolariaEditorFormattingConfig.ts
|
||||||
|
index b55dede..614cbf5 100644
|
||||||
|
--- a/src/components/tolariaEditorFormattingConfig.ts
|
||||||
|
+++ b/src/components/tolariaEditorFormattingConfig.ts
|
||||||
|
@@ -6,9 +6,11 @@ import {
|
||||||
|
import { createElement, type ReactElement } from 'react'
|
||||||
|
import {
|
||||||
|
CodeBlock,
|
||||||
|
+ CheckCircle,
|
||||||
|
File,
|
||||||
|
FlowArrow,
|
||||||
|
ImageSquare,
|
||||||
|
+ Info,
|
||||||
|
ListBullets,
|
||||||
|
ListChecks,
|
||||||
|
ListNumbers,
|
||||||
|
@@ -26,6 +28,8 @@ import {
|
||||||
|
TextHFour,
|
||||||
|
TextHFive,
|
||||||
|
TextHSix,
|
||||||
|
+ Warning,
|
||||||
|
+ XCircle,
|
||||||
|
Video,
|
||||||
|
type Icon as PhosphorIcon,
|
||||||
|
} from '@phosphor-icons/react'
|
||||||
|
@@ -38,6 +42,10 @@ import {
|
||||||
|
import { MATH_BLOCK_TYPE } from '../utils/mathMarkdown'
|
||||||
|
import { MERMAID_BLOCK_TYPE, mermaidFenceSource } from '../utils/mermaidMarkdown'
|
||||||
|
import { TLDRAW_BLOCK_TYPE, TLDRAW_DEFAULT_HEIGHT } from '../utils/tldrawMarkdown'
|
||||||
|
+import {
|
||||||
|
+ TOLARIA_CALLOUT_BLOCK_TYPE,
|
||||||
|
+ type TolariaCalloutKind,
|
||||||
|
+} from '../utils/calloutMarkdown'
|
||||||
|
|
||||||
|
type TolariaSlashMenuItem = DefaultReactSuggestionItem & { key: string }
|
||||||
|
type TolariaBlockTypeSelectItem = RichEditorBlockTypeDefinition & {
|
||||||
|
@@ -55,6 +63,12 @@ type BlockSlashMenuItemConfig = {
|
||||||
|
title: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
+type CalloutSlashMenuItemConfig = {
|
||||||
|
+ aliases: string[]
|
||||||
|
+ key: string
|
||||||
|
+ kind: TolariaCalloutKind
|
||||||
|
+ title: string
|
||||||
|
+}
|
||||||
|
type TolariaSlashMenuLabels = {
|
||||||
|
mathTitle: string
|
||||||
|
}
|
||||||
|
@@ -115,6 +129,10 @@ const TOLARIA_SLASH_MENU_ICONS: Partial<Record<string, PhosphorIcon>> = {
|
||||||
|
numbered_list: ListNumbers,
|
||||||
|
paragraph: Paragraph,
|
||||||
|
quote: Quotes,
|
||||||
|
+ tolaria_callout_danger: XCircle,
|
||||||
|
+ tolaria_callout_info: Info,
|
||||||
|
+ tolaria_callout_success: CheckCircle,
|
||||||
|
+ tolaria_callout_warning: Warning,
|
||||||
|
table: Table,
|
||||||
|
toggle_heading: TextHOne,
|
||||||
|
toggle_heading_2: TextHTwo,
|
||||||
|
@@ -164,6 +182,64 @@ function createMermaidSlashMenuItem(
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
+function createCalloutSlashMenuItem(
|
||||||
|
+ editor: Parameters<typeof getDefaultReactSlashMenuItems>[0],
|
||||||
|
+ config: CalloutSlashMenuItemConfig,
|
||||||
|
+): TolariaSlashMenuItem {
|
||||||
|
+ const blockEditor = editor as unknown as SlashInsertEditor
|
||||||
|
+
|
||||||
|
+ return {
|
||||||
|
+ key: config.key,
|
||||||
|
+ title: config.title,
|
||||||
|
+ aliases: config.aliases,
|
||||||
|
+ group: 'Basic blocks',
|
||||||
|
+ onItemClick: () => {
|
||||||
|
+ const block = blockEditor.getTextCursorPosition().block
|
||||||
|
+ blockEditor.replaceBlocks([block], [{
|
||||||
|
+ type: TOLARIA_CALLOUT_BLOCK_TYPE,
|
||||||
|
+ props: { kind: config.kind },
|
||||||
|
+ content: [{
|
||||||
|
+ type: 'text',
|
||||||
|
+ text: config.title,
|
||||||
|
+ styles: {},
|
||||||
|
+ }],
|
||||||
|
+ }])
|
||||||
|
+ trackEvent('editor_callout_slash_command_used', { kind: config.kind })
|
||||||
|
+ },
|
||||||
|
+ } as TolariaSlashMenuItem
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+export function createCalloutSlashMenuItems(
|
||||||
|
+ editor: Parameters<typeof getDefaultReactSlashMenuItems>[0],
|
||||||
|
+): TolariaSlashMenuItem[] {
|
||||||
|
+ return [
|
||||||
|
+ createCalloutSlashMenuItem(editor, {
|
||||||
|
+ key: 'tolaria_callout_info',
|
||||||
|
+ title: 'Info callout',
|
||||||
|
+ aliases: ['callout', 'note', 'info', 'tip', '提示', '高亮'],
|
||||||
|
+ kind: 'info',
|
||||||
|
+ }),
|
||||||
|
+ createCalloutSlashMenuItem(editor, {
|
||||||
|
+ key: 'tolaria_callout_success',
|
||||||
|
+ title: 'Success callout',
|
||||||
|
+ aliases: ['callout', 'success', 'done', 'ok', '完成', '成功'],
|
||||||
|
+ kind: 'success',
|
||||||
|
+ }),
|
||||||
|
+ createCalloutSlashMenuItem(editor, {
|
||||||
|
+ key: 'tolaria_callout_warning',
|
||||||
|
+ title: 'Warning callout',
|
||||||
|
+ aliases: ['callout', 'warning', 'alert', 'todo', '警告', '注意'],
|
||||||
|
+ kind: 'warning',
|
||||||
|
+ }),
|
||||||
|
+ createCalloutSlashMenuItem(editor, {
|
||||||
|
+ key: 'tolaria_callout_danger',
|
||||||
|
+ title: 'Danger callout',
|
||||||
|
+ aliases: ['callout', 'danger', 'risk', 'error', '风险', '危险'],
|
||||||
|
+ kind: 'danger',
|
||||||
|
+ }),
|
||||||
|
+ ]
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
export function createMathSlashMenuItem(
|
||||||
|
editor: Parameters<typeof getDefaultReactSlashMenuItems>[0],
|
||||||
|
labels: TolariaSlashMenuLabels = { mathTitle: 'Math' },
|
||||||
|
@@ -218,6 +294,23 @@ export function addItemsToMediaGroup(
|
||||||
|
return nextItems
|
||||||
|
}
|
||||||
|
|
||||||
|
+export function addItemsAfterKey(
|
||||||
|
+ items: TolariaSlashMenuItem[],
|
||||||
|
+ insertAfterKey: string,
|
||||||
|
+ insertedItems: TolariaSlashMenuItem[],
|
||||||
|
+): TolariaSlashMenuItem[] {
|
||||||
|
+ const nextItems = [...items]
|
||||||
|
+ const insertIndex = nextItems.findIndex((item) => item.key === insertAfterKey)
|
||||||
|
+
|
||||||
|
+ if (insertIndex === -1) {
|
||||||
|
+ nextItems.unshift(...insertedItems)
|
||||||
|
+ return nextItems
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ nextItems.splice(insertIndex + 1, 0, ...insertedItems)
|
||||||
|
+ return nextItems
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
function createTolariaSlashMenuIcon(Icon: PhosphorIcon) {
|
||||||
|
return createElement(
|
||||||
|
'span',
|
||||||
|
@@ -273,8 +366,14 @@ export function getTolariaSlashMenuItems(
|
||||||
|
query: string,
|
||||||
|
labels?: TolariaSlashMenuLabels,
|
||||||
|
) {
|
||||||
|
+ const defaultItems = getDefaultReactSlashMenuItems(editor) as TolariaSlashMenuItem[]
|
||||||
|
+ const itemsWithCallouts = addItemsAfterKey(
|
||||||
|
+ defaultItems,
|
||||||
|
+ 'quote',
|
||||||
|
+ createCalloutSlashMenuItems(editor),
|
||||||
|
+ )
|
||||||
|
const items = addItemsToMediaGroup(
|
||||||
|
- getDefaultReactSlashMenuItems(editor) as TolariaSlashMenuItem[],
|
||||||
|
+ itemsWithCallouts,
|
||||||
|
[
|
||||||
|
createMermaidSlashMenuItem(editor),
|
||||||
|
createMathSlashMenuItem(editor, labels),
|
||||||
|
diff --git a/src/utils/blockNoteDirectMarkdown.test.ts b/src/utils/blockNoteDirectMarkdown.test.ts
|
||||||
|
index 524b29c..cabba60 100644
|
||||||
|
--- a/src/utils/blockNoteDirectMarkdown.test.ts
|
||||||
|
+++ b/src/utils/blockNoteDirectMarkdown.test.ts
|
||||||
|
@@ -5,6 +5,7 @@ import {
|
||||||
|
serializeBlockNoteMarkdown,
|
||||||
|
type DirectMarkdownCapableSerializer,
|
||||||
|
} from './blockNoteDirectMarkdown'
|
||||||
|
+import { TOLARIA_CALLOUT_BLOCK_TYPE } from './calloutMarkdown'
|
||||||
|
import { serializeRichEditorBodyToMarkdown } from './richEditorMarkdown'
|
||||||
|
|
||||||
|
function makeEditor(document: unknown[]): DirectMarkdownCapableSerializer & { document: unknown[] } {
|
||||||
|
@@ -117,6 +118,17 @@ describe('BlockNote direct Markdown serialization', () => {
|
||||||
|
].join('\n'))
|
||||||
|
})
|
||||||
|
|
||||||
|
+ it('serializes Tolaria callout blocks as portable Markdown callouts', () => {
|
||||||
|
+ expect(blocksToMarkdownDirect([
|
||||||
|
+ {
|
||||||
|
+ type: TOLARIA_CALLOUT_BLOCK_TYPE,
|
||||||
|
+ props: { kind: 'success' },
|
||||||
|
+ content: [{ type: 'text', text: 'Package is ready', styles: {} }],
|
||||||
|
+ children: [],
|
||||||
|
+ },
|
||||||
|
+ ]).markdown).toBe('> [!success] Package is ready')
|
||||||
|
+ })
|
||||||
|
+
|
||||||
|
it('caches unchanged block objects across rich-editor body serialization', () => {
|
||||||
|
const block = {
|
||||||
|
type: 'paragraph',
|
||||||
|
diff --git a/src/utils/blockNoteDirectMarkdown.ts b/src/utils/blockNoteDirectMarkdown.ts
|
||||||
|
index c075831..2ac99a0 100644
|
||||||
|
--- a/src/utils/blockNoteDirectMarkdown.ts
|
||||||
|
+++ b/src/utils/blockNoteDirectMarkdown.ts
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import { calloutKind, TOLARIA_CALLOUT_BLOCK_TYPE } from './calloutMarkdown'
|
||||||
|
+
|
||||||
|
interface TextStyles {
|
||||||
|
[style: string]: string | boolean | undefined
|
||||||
|
}
|
||||||
|
@@ -228,6 +230,15 @@ function quoteMarkdown(block: BlockLike): string {
|
||||||
|
return text.split('\n').map(line => `> ${line}`).join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
+function calloutMarkdown(block: BlockLike): string {
|
||||||
|
+ const kind = calloutKind(block.props?.kind)
|
||||||
|
+ const text = serializeInlineContent(contentArray(block.content)) || 'Write a note'
|
||||||
|
+ return `[!${kind}] ${text}`
|
||||||
|
+ .split('\n')
|
||||||
|
+ .map(line => `> ${line}`)
|
||||||
|
+ .join('\n')
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
function tableCellMarkdown(cell: TableCellValue): string {
|
||||||
|
const text = typeof cell === 'string'
|
||||||
|
? cell
|
||||||
|
@@ -273,6 +284,7 @@ const BLOCK_MARKDOWN_HANDLERS: Record<string, BlockMarkdownHandler> = {
|
||||||
|
heading: headingMarkdown,
|
||||||
|
quote: quoteMarkdown,
|
||||||
|
table: tableMarkdown,
|
||||||
|
+ [TOLARIA_CALLOUT_BLOCK_TYPE]: calloutMarkdown,
|
||||||
|
}
|
||||||
|
|
||||||
|
function blockMarkdownWithoutChildren(block: BlockLike, context: SerializeContext): string | null {
|
||||||
|
diff --git a/src/utils/calloutMarkdown.ts b/src/utils/calloutMarkdown.ts
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..b6881b6
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/utils/calloutMarkdown.ts
|
||||||
|
@@ -0,0 +1,70 @@
|
||||||
|
+import type { BlockLike } from './durableMarkdownBlocks'
|
||||||
|
+
|
||||||
|
+export const TOLARIA_CALLOUT_BLOCK_TYPE = 'tolariaCallout'
|
||||||
|
+
|
||||||
|
+export const TOLARIA_CALLOUT_KINDS = ['info', 'success', 'warning', 'danger'] as const
|
||||||
|
+
|
||||||
|
+export type TolariaCalloutKind = typeof TOLARIA_CALLOUT_KINDS[number]
|
||||||
|
+
|
||||||
|
+const CALLOUT_MARKER_RE = /^\[!(info|success|warning|danger)\]\s*(.*)$/iu
|
||||||
|
+
|
||||||
|
+function isCalloutKind(value: unknown): value is TolariaCalloutKind {
|
||||||
|
+ return typeof value === 'string' && TOLARIA_CALLOUT_KINDS.includes(value as TolariaCalloutKind)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+function readTextOnlyContent(content: BlockLike['content']): string | null {
|
||||||
|
+ if (!Array.isArray(content) || content.length === 0) return null
|
||||||
|
+
|
||||||
|
+ let text = ''
|
||||||
|
+ for (const item of content) {
|
||||||
|
+ if (item.type !== 'text' || typeof item.text !== 'string') return null
|
||||||
|
+ text += item.text
|
||||||
|
+ }
|
||||||
|
+ return text
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+function calloutMatchFromQuote(block: BlockLike) {
|
||||||
|
+ if (block.type !== 'quote') return null
|
||||||
|
+
|
||||||
|
+ const text = readTextOnlyContent(block.content)
|
||||||
|
+ if (text === null) return null
|
||||||
|
+
|
||||||
|
+ const match = CALLOUT_MARKER_RE.exec(text)
|
||||||
|
+ if (!match) return null
|
||||||
|
+
|
||||||
|
+ const kind = match[1]?.toLowerCase()
|
||||||
|
+ if (!isCalloutKind(kind)) return null
|
||||||
|
+
|
||||||
|
+ return {
|
||||||
|
+ kind,
|
||||||
|
+ text: match[2] ?? '',
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+export function injectTolariaCalloutBlocks(blocks: unknown[]): unknown[] {
|
||||||
|
+ return (blocks as BlockLike[]).map((block) => {
|
||||||
|
+ const match = calloutMatchFromQuote(block)
|
||||||
|
+ const children = Array.isArray(block.children)
|
||||||
|
+ ? injectTolariaCalloutBlocks(block.children) as BlockLike[]
|
||||||
|
+ : block.children
|
||||||
|
+
|
||||||
|
+ if (!match) return { ...block, children }
|
||||||
|
+
|
||||||
|
+ return {
|
||||||
|
+ ...block,
|
||||||
|
+ type: TOLARIA_CALLOUT_BLOCK_TYPE,
|
||||||
|
+ props: {
|
||||||
|
+ ...(block.props ?? {}),
|
||||||
|
+ kind: match.kind,
|
||||||
|
+ },
|
||||||
|
+ content: match.text
|
||||||
|
+ ? [{ type: 'text', text: match.text, styles: {} }]
|
||||||
|
+ : [],
|
||||||
|
+ children,
|
||||||
|
+ }
|
||||||
|
+ })
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+export function calloutKind(value: unknown): TolariaCalloutKind {
|
||||||
|
+ return isCalloutKind(value) ? value : 'info'
|
||||||
|
+}
|
||||||
|
diff --git a/src/utils/editorDurableMarkdown.test.ts b/src/utils/editorDurableMarkdown.test.ts
|
||||||
|
index f5a230d..4e6679e 100644
|
||||||
|
--- a/src/utils/editorDurableMarkdown.test.ts
|
||||||
|
+++ b/src/utils/editorDurableMarkdown.test.ts
|
||||||
|
@@ -6,6 +6,7 @@ import {
|
||||||
|
preProcessDurableEditorMarkdown,
|
||||||
|
serializeDurableEditorBlocks,
|
||||||
|
} from './editorDurableMarkdown'
|
||||||
|
+import { TOLARIA_CALLOUT_BLOCK_TYPE } from './calloutMarkdown'
|
||||||
|
import { MERMAID_BLOCK_TYPE } from './mermaidMarkdown'
|
||||||
|
import { TLDRAW_BLOCK_TYPE } from './tldrawMarkdown'
|
||||||
|
|
||||||
|
@@ -70,4 +71,20 @@ describe('editor durable markdown blocks', () => {
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
+
|
||||||
|
+ it('restores Markdown callouts as Tolaria page blocks', () => {
|
||||||
|
+ const blocks = injectDurableEditorMarkdownBlocks([
|
||||||
|
+ {
|
||||||
|
+ type: 'quote',
|
||||||
|
+ content: [{ type: 'text', text: '[!warning] Check signing before sharing.', styles: {} }],
|
||||||
|
+ children: [],
|
||||||
|
+ },
|
||||||
|
+ ]) as Array<{ type: string; props?: Record<string, string>; content?: Array<{ text?: string }> }>
|
||||||
|
+
|
||||||
|
+ expect(blocks[0]).toMatchObject({
|
||||||
|
+ type: TOLARIA_CALLOUT_BLOCK_TYPE,
|
||||||
|
+ props: { kind: 'warning' },
|
||||||
|
+ content: [{ text: 'Check signing before sharing.' }],
|
||||||
|
+ })
|
||||||
|
+ })
|
||||||
|
})
|
||||||
|
diff --git a/src/utils/editorDurableMarkdown.ts b/src/utils/editorDurableMarkdown.ts
|
||||||
|
index 6e6d30a..baa5670 100644
|
||||||
|
--- a/src/utils/editorDurableMarkdown.ts
|
||||||
|
+++ b/src/utils/editorDurableMarkdown.ts
|
||||||
|
@@ -16,6 +16,7 @@ import { serializeMathAwareBlocks } from './mathMarkdown'
|
||||||
|
import { mermaidMarkdownCodec } from './mermaidMarkdown'
|
||||||
|
import { tldrawMarkdownCodec } from './tldrawMarkdown'
|
||||||
|
import { guanghuPortalMarkdownCodec } from './guanghuPortalMarkdown'
|
||||||
|
+import { injectTolariaCalloutBlocks } from './calloutMarkdown'
|
||||||
|
|
||||||
|
const EDITOR_DURABLE_MARKDOWN_CODECS = [
|
||||||
|
guanghuPortalMarkdownCodec,
|
||||||
|
@@ -36,7 +37,7 @@ export function injectDurableEditorMarkdownBlocks(blocks: unknown[]): unknown[]
|
||||||
|
blocks,
|
||||||
|
codecs: EDITOR_DURABLE_MARKDOWN_CODECS,
|
||||||
|
})
|
||||||
|
- return injectFileAttachmentBlocks(withDurableBlocks)
|
||||||
|
+ return injectTolariaCalloutBlocks(injectFileAttachmentBlocks(withDurableBlocks))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeDurableEditorBlocks(
|
||||||
|
--
|
||||||
|
2.50.1 (Apple Git-155)
|
||||||
|
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
From 9fc5e885742a48c2794a4b4fcd8ddae66501aa28 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 10:30:04 +0800
|
||||||
|
Subject: [PATCH 6/7] fix: make Guanghu home and themes visible
|
||||||
|
|
||||||
|
---
|
||||||
|
src/App.tsx | 4 ++--
|
||||||
|
src/components/HoloLakeHome.tsx | 1 +
|
||||||
|
src/hooks/useGuanghuTheme.test.ts | 30 ++++++++++++++++++++++++++++++
|
||||||
|
src/hooks/useGuanghuTheme.ts | 12 +++++++++++-
|
||||||
|
src/lib/guanghuTheme.ts | 6 +++---
|
||||||
|
5 files changed, 47 insertions(+), 6 deletions(-)
|
||||||
|
create mode 100644 src/hooks/useGuanghuTheme.test.ts
|
||||||
|
|
||||||
|
diff --git a/src/App.tsx b/src/App.tsx
|
||||||
|
index 2b4ef17..66c879d 100644
|
||||||
|
--- a/src/App.tsx
|
||||||
|
+++ b/src/App.tsx
|
||||||
|
@@ -170,8 +170,7 @@ function App() {
|
||||||
|
|
||||||
|
function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | null }) {
|
||||||
|
const aiWorkspaceWindow = false
|
||||||
|
- const [showHoloLakeHome, setShowHoloLakeHome] = useState(false)
|
||||||
|
- const { theme: guanghuTheme, cycleTheme } = useGuanghuTheme()
|
||||||
|
+ const [showHoloLakeHome, setShowHoloLakeHome] = useState(true)
|
||||||
|
const { dragRegionRef } = useDragRegion<HTMLElement>()
|
||||||
|
const [selection, setSelection] = useState<SidebarSelection>(DEFAULT_SELECTION)
|
||||||
|
const [noteListFilter, setNoteListFilter] = useState<NoteListFilter>('open')
|
||||||
|
@@ -426,6 +425,7 @@ function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | nu
|
||||||
|
settings,
|
||||||
|
settingsLoaded,
|
||||||
|
})
|
||||||
|
+ const { theme: guanghuTheme, cycleTheme } = useGuanghuTheme()
|
||||||
|
const quickPromptTarget = lastAiWorkspaceTarget ?? aiAgentPreferences.defaultAiTarget
|
||||||
|
const quickPromptTargetReady = aiTargetReady(quickPromptTarget, aiAgentsStatus)
|
||||||
|
|
||||||
|
diff --git a/src/components/HoloLakeHome.tsx b/src/components/HoloLakeHome.tsx
|
||||||
|
index 86b2891..49127ca 100644
|
||||||
|
--- a/src/components/HoloLakeHome.tsx
|
||||||
|
+++ b/src/components/HoloLakeHome.tsx
|
||||||
|
@@ -4,6 +4,7 @@ type HoloLakeHomeProps = {
|
||||||
|
|
||||||
|
const modules = [
|
||||||
|
['知识湖', '把代码仓库、文档与路径转译成可阅读、可追溯的知识页面。', '进入知识库'],
|
||||||
|
+ ['页面组件', '提示块、批注、关系卡、路径包、折叠块与高亮代码已接入笔记编辑器。', '在笔记中输入 /'],
|
||||||
|
['协作空间', '人类与人格体在同一上下文中对话、确认、留存回执。', '筹备中'],
|
||||||
|
['授权中心', '任何真实操作先形成可读计划,再由人类一次确认。', '筹备中'],
|
||||||
|
]
|
||||||
|
diff --git a/src/hooks/useGuanghuTheme.test.ts b/src/hooks/useGuanghuTheme.test.ts
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..8fa948a
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/hooks/useGuanghuTheme.test.ts
|
||||||
|
@@ -0,0 +1,30 @@
|
||||||
|
+import { act, renderHook } from '@testing-library/react'
|
||||||
|
+import { beforeEach, describe, expect, it } from 'vitest'
|
||||||
|
+import { GUANGHU_THEME_STORAGE_KEY } from '../lib/guanghuTheme'
|
||||||
|
+import { THEME_MODE_STORAGE_KEY } from '../lib/themeMode'
|
||||||
|
+import { useGuanghuTheme } from './useGuanghuTheme'
|
||||||
|
+
|
||||||
|
+describe('useGuanghuTheme', () => {
|
||||||
|
+ beforeEach(() => {
|
||||||
|
+ window.localStorage.clear()
|
||||||
|
+ document.documentElement.removeAttribute('data-guanghu-theme')
|
||||||
|
+ document.documentElement.removeAttribute('data-theme')
|
||||||
|
+ document.documentElement.classList.remove('dark')
|
||||||
|
+ })
|
||||||
|
+
|
||||||
|
+ it('makes the lake presets visibly switch the document between light and dark', () => {
|
||||||
|
+ window.localStorage.setItem(GUANGHU_THEME_STORAGE_KEY, 'lake-light')
|
||||||
|
+ const { result } = renderHook(() => useGuanghuTheme())
|
||||||
|
+
|
||||||
|
+ expect(document.documentElement).toHaveAttribute('data-guanghu-theme', 'lake-light')
|
||||||
|
+ expect(document.documentElement).toHaveAttribute('data-theme', 'light')
|
||||||
|
+ expect(window.localStorage.getItem(THEME_MODE_STORAGE_KEY)).toBe('light')
|
||||||
|
+
|
||||||
|
+ act(() => result.current.cycleTheme())
|
||||||
|
+
|
||||||
|
+ expect(document.documentElement).toHaveAttribute('data-guanghu-theme', 'lake-dark')
|
||||||
|
+ expect(document.documentElement).toHaveAttribute('data-theme', 'dark')
|
||||||
|
+ expect(document.documentElement).toHaveClass('dark')
|
||||||
|
+ expect(window.localStorage.getItem(THEME_MODE_STORAGE_KEY)).toBe('dark')
|
||||||
|
+ })
|
||||||
|
+})
|
||||||
|
diff --git a/src/hooks/useGuanghuTheme.ts b/src/hooks/useGuanghuTheme.ts
|
||||||
|
index 2c42f2d..ec175ce 100644
|
||||||
|
--- a/src/hooks/useGuanghuTheme.ts
|
||||||
|
+++ b/src/hooks/useGuanghuTheme.ts
|
||||||
|
@@ -6,6 +6,16 @@ import {
|
||||||
|
writeGuanghuTheme,
|
||||||
|
type GuanghuTheme,
|
||||||
|
} from '../lib/guanghuTheme'
|
||||||
|
+import { applyThemeModeToDocument, writeStoredThemeMode } from '../lib/themeMode'
|
||||||
|
+
|
||||||
|
+function applyGuanghuTheme(theme: GuanghuTheme): void {
|
||||||
|
+ document.documentElement.setAttribute('data-guanghu-theme', theme)
|
||||||
|
+ if (theme === 'native') return
|
||||||
|
+
|
||||||
|
+ const mode = theme === 'lake-light' ? 'light' : 'dark'
|
||||||
|
+ applyThemeModeToDocument(document, mode)
|
||||||
|
+ writeStoredThemeMode(window.localStorage, mode)
|
||||||
|
+}
|
||||||
|
|
||||||
|
export function useGuanghuTheme(): { theme: GuanghuTheme; cycleTheme: () => void } {
|
||||||
|
const [theme, setTheme] = useState<GuanghuTheme>(() => (
|
||||||
|
@@ -13,7 +23,7 @@ export function useGuanghuTheme(): { theme: GuanghuTheme; cycleTheme: () => void
|
||||||
|
))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
- document.documentElement.setAttribute('data-guanghu-theme', theme)
|
||||||
|
+ applyGuanghuTheme(theme)
|
||||||
|
writeGuanghuTheme(window.localStorage, theme)
|
||||||
|
}, [theme])
|
||||||
|
|
||||||
|
diff --git a/src/lib/guanghuTheme.ts b/src/lib/guanghuTheme.ts
|
||||||
|
index 711a589..7ed2806 100644
|
||||||
|
--- a/src/lib/guanghuTheme.ts
|
||||||
|
+++ b/src/lib/guanghuTheme.ts
|
||||||
|
@@ -4,9 +4,9 @@ export const GUANGHU_THEMES = ['native', 'lake-light', 'lake-dark'] as const
|
||||||
|
export type GuanghuTheme = typeof GUANGHU_THEMES[number]
|
||||||
|
|
||||||
|
export const GUANGHU_THEME_LABELS: Record<GuanghuTheme, string> = {
|
||||||
|
- native: '原生',
|
||||||
|
- 'lake-light': '湖光浅色',
|
||||||
|
- 'lake-dark': '湖心深色',
|
||||||
|
+ native: '跟随原生',
|
||||||
|
+ 'lake-light': '湖光浅色(亮)',
|
||||||
|
+ 'lake-dark': '湖心深色(暗)',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeGuanghuTheme(value: unknown): GuanghuTheme {
|
||||||
|
--
|
||||||
|
2.50.1 (Apple Git-155)
|
||||||
|
|
||||||
@ -0,0 +1,216 @@
|
|||||||
|
From 2b57543d7855487e75d52fce12714501811fb650 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 10:30:04 +0800
|
||||||
|
Subject: [PATCH 7/7] feat: complete portable page block set
|
||||||
|
|
||||||
|
---
|
||||||
|
src/components/Editor.css | 16 +++++++++++
|
||||||
|
src/components/editorSchema.tsx | 3 +++
|
||||||
|
.../tolariaEditorFormatting.test.tsx | 12 ++++++++-
|
||||||
|
.../tolariaEditorFormattingConfig.ts | 27 ++++++++++++++++---
|
||||||
|
src/utils/blockNoteDirectMarkdown.test.ts | 9 +++++++
|
||||||
|
src/utils/calloutMarkdown.ts | 12 +++++++--
|
||||||
|
6 files changed, 72 insertions(+), 7 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/components/Editor.css b/src/components/Editor.css
|
||||||
|
index 5db3fe1..11a0813 100644
|
||||||
|
--- a/src/components/Editor.css
|
||||||
|
+++ b/src/components/Editor.css
|
||||||
|
@@ -109,6 +109,22 @@
|
||||||
|
--tolaria-callout-bg: color-mix(in srgb, #dc2626 10%, var(--bg-primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
+.tolaria-callout[data-kind="annotation"] {
|
||||||
|
+ --tolaria-callout-accent: #8b5cf6;
|
||||||
|
+ --tolaria-callout-bg: color-mix(in srgb, #8b5cf6 11%, var(--bg-primary));
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout[data-kind="relationship"] {
|
||||||
|
+ --tolaria-callout-accent: #0f9f8f;
|
||||||
|
+ --tolaria-callout-bg: color-mix(in srgb, #0f9f8f 11%, var(--bg-primary));
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.tolaria-callout[data-kind="path"] {
|
||||||
|
+ --tolaria-callout-accent: #4f7ecb;
|
||||||
|
+ --tolaria-callout-bg: color-mix(in srgb, #4f7ecb 11%, var(--bg-primary));
|
||||||
|
+ border-style: dashed;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
.tolaria-callout__marker {
|
||||||
|
width: 4px;
|
||||||
|
min-height: 100%;
|
||||||
|
diff --git a/src/components/editorSchema.tsx b/src/components/editorSchema.tsx
|
||||||
|
index 7d62518..75ceddf 100644
|
||||||
|
--- a/src/components/editorSchema.tsx
|
||||||
|
+++ b/src/components/editorSchema.tsx
|
||||||
|
@@ -356,8 +356,11 @@ const GuanghuPortalBlock = createReactBlockSpec(
|
||||||
|
)
|
||||||
|
|
||||||
|
const CALLOUT_LABELS: Record<TolariaCalloutKind, string> = {
|
||||||
|
+ annotation: '批注',
|
||||||
|
danger: 'Danger',
|
||||||
|
info: 'Info',
|
||||||
|
+ path: '路径',
|
||||||
|
+ relationship: '关系',
|
||||||
|
success: 'Success',
|
||||||
|
warning: 'Warning',
|
||||||
|
}
|
||||||
|
diff --git a/src/components/tolariaEditorFormatting.test.tsx b/src/components/tolariaEditorFormatting.test.tsx
|
||||||
|
index 7bd06c2..b32ebb7 100644
|
||||||
|
--- a/src/components/tolariaEditorFormatting.test.tsx
|
||||||
|
+++ b/src/components/tolariaEditorFormatting.test.tsx
|
||||||
|
@@ -60,7 +60,7 @@ describe('tolariaEditorFormatting', () => {
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
- it('filters unsupported toggle slash-menu variants and removes command descriptions', () => {
|
||||||
|
+ it('keeps Notion-style toggles, filters oversized headings, and removes command descriptions', () => {
|
||||||
|
type TolariaSlashMenuTestItem = {
|
||||||
|
key: string
|
||||||
|
title: string
|
||||||
|
@@ -82,6 +82,8 @@ describe('tolariaEditorFormatting', () => {
|
||||||
|
] satisfies TolariaSlashMenuTestItem[])
|
||||||
|
|
||||||
|
expect(items.map((item) => item.key)).toEqual([
|
||||||
|
+ 'toggle_heading',
|
||||||
|
+ 'toggle_list',
|
||||||
|
'heading',
|
||||||
|
'heading_4',
|
||||||
|
'bullet_list',
|
||||||
|
@@ -94,6 +96,8 @@ describe('tolariaEditorFormatting', () => {
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
+ undefined,
|
||||||
|
+ undefined,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
@@ -280,6 +284,12 @@ describe('tolariaEditorFormatting', () => {
|
||||||
|
styles: {},
|
||||||
|
}],
|
||||||
|
}])
|
||||||
|
+
|
||||||
|
+ expect(items.map(item => item.key)).toEqual(expect.arrayContaining([
|
||||||
|
+ 'tolaria_callout_annotation',
|
||||||
|
+ 'tolaria_callout_relationship',
|
||||||
|
+ 'tolaria_callout_path',
|
||||||
|
+ ]))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a math slash command with a default display equation', () => {
|
||||||
|
diff --git a/src/components/tolariaEditorFormattingConfig.ts b/src/components/tolariaEditorFormattingConfig.ts
|
||||||
|
index 614cbf5..41d9225 100644
|
||||||
|
--- a/src/components/tolariaEditorFormattingConfig.ts
|
||||||
|
+++ b/src/components/tolariaEditorFormattingConfig.ts
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
||||||
|
CheckCircle,
|
||||||
|
File,
|
||||||
|
FlowArrow,
|
||||||
|
+ GitBranch,
|
||||||
|
ImageSquare,
|
||||||
|
Info,
|
||||||
|
ListBullets,
|
||||||
|
@@ -21,6 +22,7 @@ import {
|
||||||
|
ScribbleLoop,
|
||||||
|
Smiley,
|
||||||
|
SpeakerHigh,
|
||||||
|
+ Tag,
|
||||||
|
Table,
|
||||||
|
TextHOne,
|
||||||
|
TextHTwo,
|
||||||
|
@@ -90,10 +92,6 @@ const UNSUPPORTED_FORMATTING_TOOLBAR_KEYS = new Set([
|
||||||
|
const UNSUPPORTED_SLASH_MENU_KEYS = new Set([
|
||||||
|
'heading_5',
|
||||||
|
'heading_6',
|
||||||
|
- 'toggle_heading',
|
||||||
|
- 'toggle_heading_2',
|
||||||
|
- 'toggle_heading_3',
|
||||||
|
- 'toggle_list',
|
||||||
|
])
|
||||||
|
|
||||||
|
const TOLARIA_BLOCK_TYPE_SELECT_ICONS: Record<RichEditorBlockTypeKey, PhosphorIcon> = {
|
||||||
|
@@ -131,6 +129,9 @@ const TOLARIA_SLASH_MENU_ICONS: Partial<Record<string, PhosphorIcon>> = {
|
||||||
|
quote: Quotes,
|
||||||
|
tolaria_callout_danger: XCircle,
|
||||||
|
tolaria_callout_info: Info,
|
||||||
|
+ tolaria_callout_annotation: Tag,
|
||||||
|
+ tolaria_callout_path: FlowArrow,
|
||||||
|
+ tolaria_callout_relationship: GitBranch,
|
||||||
|
tolaria_callout_success: CheckCircle,
|
||||||
|
tolaria_callout_warning: Warning,
|
||||||
|
table: Table,
|
||||||
|
@@ -237,6 +238,24 @@ export function createCalloutSlashMenuItems(
|
||||||
|
aliases: ['callout', 'danger', 'risk', 'error', '风险', '危险'],
|
||||||
|
kind: 'danger',
|
||||||
|
}),
|
||||||
|
+ createCalloutSlashMenuItem(editor, {
|
||||||
|
+ key: 'tolaria_callout_annotation',
|
||||||
|
+ title: '批注 / Annotation',
|
||||||
|
+ aliases: ['annotation', 'comment', 'note', '批注', '注释'],
|
||||||
|
+ kind: 'annotation',
|
||||||
|
+ }),
|
||||||
|
+ createCalloutSlashMenuItem(editor, {
|
||||||
|
+ key: 'tolaria_callout_relationship',
|
||||||
|
+ title: '关系卡 / Relationship',
|
||||||
|
+ aliases: ['relationship', 'relation', 'wikilink', '关系', '关联'],
|
||||||
|
+ kind: 'relationship',
|
||||||
|
+ }),
|
||||||
|
+ createCalloutSlashMenuItem(editor, {
|
||||||
|
+ key: 'tolaria_callout_path',
|
||||||
|
+ title: '路径包 / Path map',
|
||||||
|
+ aliases: ['path', 'breadcrumb', 'route', 'map', '路径', '面包屑', '导航'],
|
||||||
|
+ kind: 'path',
|
||||||
|
+ }),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/src/utils/blockNoteDirectMarkdown.test.ts b/src/utils/blockNoteDirectMarkdown.test.ts
|
||||||
|
index cabba60..358df27 100644
|
||||||
|
--- a/src/utils/blockNoteDirectMarkdown.test.ts
|
||||||
|
+++ b/src/utils/blockNoteDirectMarkdown.test.ts
|
||||||
|
@@ -127,6 +127,15 @@ describe('BlockNote direct Markdown serialization', () => {
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
]).markdown).toBe('> [!success] Package is ready')
|
||||||
|
+
|
||||||
|
+ expect(blocksToMarkdownDirect([
|
||||||
|
+ {
|
||||||
|
+ type: TOLARIA_CALLOUT_BLOCK_TYPE,
|
||||||
|
+ props: { kind: 'relationship' },
|
||||||
|
+ content: [{ type: 'text', text: '[[TCS]] → [[HoloLake]]', styles: {} }],
|
||||||
|
+ children: [],
|
||||||
|
+ },
|
||||||
|
+ ]).markdown).toBe('> [!relationship] [[TCS]] → [[HoloLake]]')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('caches unchanged block objects across rich-editor body serialization', () => {
|
||||||
|
diff --git a/src/utils/calloutMarkdown.ts b/src/utils/calloutMarkdown.ts
|
||||||
|
index b6881b6..2fa2449 100644
|
||||||
|
--- a/src/utils/calloutMarkdown.ts
|
||||||
|
+++ b/src/utils/calloutMarkdown.ts
|
||||||
|
@@ -2,11 +2,19 @@ import type { BlockLike } from './durableMarkdownBlocks'
|
||||||
|
|
||||||
|
export const TOLARIA_CALLOUT_BLOCK_TYPE = 'tolariaCallout'
|
||||||
|
|
||||||
|
-export const TOLARIA_CALLOUT_KINDS = ['info', 'success', 'warning', 'danger'] as const
|
||||||
|
+export const TOLARIA_CALLOUT_KINDS = [
|
||||||
|
+ 'info',
|
||||||
|
+ 'success',
|
||||||
|
+ 'warning',
|
||||||
|
+ 'danger',
|
||||||
|
+ 'annotation',
|
||||||
|
+ 'relationship',
|
||||||
|
+ 'path',
|
||||||
|
+] as const
|
||||||
|
|
||||||
|
export type TolariaCalloutKind = typeof TOLARIA_CALLOUT_KINDS[number]
|
||||||
|
|
||||||
|
-const CALLOUT_MARKER_RE = /^\[!(info|success|warning|danger)\]\s*(.*)$/iu
|
||||||
|
+const CALLOUT_MARKER_RE = /^\[!(info|success|warning|danger|annotation|relationship|path)\]\s*(.*)$/iu
|
||||||
|
|
||||||
|
function isCalloutKind(value: unknown): value is TolariaCalloutKind {
|
||||||
|
return typeof value === 'string' && TOLARIA_CALLOUT_KINDS.includes(value as TolariaCalloutKind)
|
||||||
|
--
|
||||||
|
2.50.1 (Apple Git-155)
|
||||||
|
|
||||||
@ -0,0 +1,246 @@
|
|||||||
|
From 5dcdcd8a6b2a983af46d37f5e89d522402badcbd 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:12:33 +0800
|
||||||
|
Subject: [PATCH 8/9] fix: restore internal note links and reading rhythm
|
||||||
|
|
||||||
|
---
|
||||||
|
src/components/Editor.css | 6 ++--
|
||||||
|
src/components/EditorTheme.css | 32 ++++++++++++++++--
|
||||||
|
.../useEditorLinkActivation.test.tsx | 25 ++++++++++++++
|
||||||
|
src/components/useEditorLinkActivation.ts | 33 ++++++++++++++++++-
|
||||||
|
src/theme.json | 20 +++++------
|
||||||
|
5 files changed, 99 insertions(+), 17 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/components/Editor.css b/src/components/Editor.css
|
||||||
|
index 11a0813..910def1 100644
|
||||||
|
--- a/src/components/Editor.css
|
||||||
|
+++ b/src/components/Editor.css
|
||||||
|
@@ -87,10 +87,10 @@
|
||||||
|
grid-template-columns: 4px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
- margin: 0.45rem 0;
|
||||||
|
- padding: 11px 13px 11px 0;
|
||||||
|
+ margin: 0.8rem 0;
|
||||||
|
+ padding: 15px 16px 15px 0;
|
||||||
|
border: 1px solid color-mix(in srgb, var(--tolaria-callout-accent) 28%, var(--border));
|
||||||
|
- border-radius: 8px;
|
||||||
|
+ border-radius: 10px;
|
||||||
|
background: var(--tolaria-callout-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/src/components/EditorTheme.css b/src/components/EditorTheme.css
|
||||||
|
index 862e474..3885574 100644
|
||||||
|
--- a/src/components/EditorTheme.css
|
||||||
|
+++ b/src/components/EditorTheme.css
|
||||||
|
@@ -350,6 +350,12 @@
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
+.editor__blocknote-container .bn-editor a[href*=".md"] {
|
||||||
|
+ cursor: pointer;
|
||||||
|
+ text-decoration-thickness: 1px;
|
||||||
|
+ text-underline-offset: 0.18em;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/* --- Wikilinks (override from Editor.css with theme vars) --- */
|
||||||
|
.editor__blocknote-container .wikilink {
|
||||||
|
color: var(--inline-styles-wikilink-color);
|
||||||
|
@@ -657,9 +663,15 @@ body.tldraw-whiteboard-fullscreen-open {
|
||||||
|
background-color: var(--editor-code-block-background) !important;
|
||||||
|
border: 1px solid var(--editor-code-block-border);
|
||||||
|
color: var(--editor-code-block-text) !important;
|
||||||
|
+ border-radius: 10px;
|
||||||
|
+ box-shadow: 0 1px 0 color-mix(in srgb, var(--editor-code-block-border) 55%, transparent);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
+.editor__blocknote-container .bn-block-outer:has(> .bn-block > [data-content-type="codeBlock"]) {
|
||||||
|
+ margin-block: 10px 16px;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
.editor__code-block-copy {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 4;
|
||||||
|
@@ -681,6 +693,8 @@ body.tldraw-whiteboard-fullscreen-open {
|
||||||
|
|
||||||
|
.editor__blocknote-container .bn-block-content[data-content-type="codeBlock"] > pre {
|
||||||
|
color: inherit;
|
||||||
|
+ scrollbar-color: color-mix(in srgb, var(--accent-blue) 48%, transparent) transparent;
|
||||||
|
+ scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor__blocknote-container .bn-block-content[data-content-type="codeBlock"] > div > select {
|
||||||
|
@@ -706,12 +720,24 @@ body.tldraw-whiteboard-fullscreen-open {
|
||||||
|
/* --- Blockquote --- */
|
||||||
|
.editor__blocknote-container [data-content-type="blockquote"],
|
||||||
|
.editor__blocknote-container blockquote {
|
||||||
|
- border-inline-start: var(--blockquote-border-left-width) solid var(--blockquote-border-left-color);
|
||||||
|
- padding-inline-start: var(--blockquote-padding-left);
|
||||||
|
+ background: color-mix(in srgb, var(--accent-blue) 12%, var(--bg-primary));
|
||||||
|
+ border: 1px solid color-mix(in srgb, var(--accent-blue) 28%, var(--border-primary));
|
||||||
|
+ border-inline-start: 4px solid var(--blockquote-border-left-color);
|
||||||
|
+ border-radius: 10px;
|
||||||
|
+ padding: 16px 18px;
|
||||||
|
margin-top: var(--blockquote-margin-vertical);
|
||||||
|
margin-bottom: var(--blockquote-margin-vertical);
|
||||||
|
color: var(--blockquote-color);
|
||||||
|
- font-style: var(--blockquote-font-style);
|
||||||
|
+ font-style: normal;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.editor__blocknote-container .bn-block-outer:has(> .bn-block > [data-content-type="table"]) {
|
||||||
|
+ margin-block: 12px 18px;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.editor__blocknote-container [data-content-type="table"] table {
|
||||||
|
+ border-radius: 8px;
|
||||||
|
+ overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Table --- */
|
||||||
|
diff --git a/src/components/useEditorLinkActivation.test.tsx b/src/components/useEditorLinkActivation.test.tsx
|
||||||
|
index ab3b62a..bbfcaee 100644
|
||||||
|
--- a/src/components/useEditorLinkActivation.test.tsx
|
||||||
|
+++ b/src/components/useEditorLinkActivation.test.tsx
|
||||||
|
@@ -128,6 +128,31 @@ describe('useEditorLinkActivation', () => {
|
||||||
|
expect(modifiedClick.defaultPrevented).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
+ it('opens relative Markdown note links inside Tolaria on a plain click', async () => {
|
||||||
|
+ const { container, onNavigateWikilink } = renderHarness()
|
||||||
|
+ const link = appendUrl(container, '../Migration/Related%20Page%20abc123.md#current-status')
|
||||||
|
+
|
||||||
|
+ const plainClick = dispatchMouseEvent(link, 'click')
|
||||||
|
+
|
||||||
|
+ expect(plainClick.defaultPrevented).toBe(true)
|
||||||
|
+ expect(mockOpenLocalFile).not.toHaveBeenCalled()
|
||||||
|
+ await Promise.resolve()
|
||||||
|
+ expect(onNavigateWikilink).toHaveBeenCalledWith('../Migration/Related Page abc123')
|
||||||
|
+ })
|
||||||
|
+
|
||||||
|
+ it('consumes Markdown note link mousedown before editor internals can select it', async () => {
|
||||||
|
+ const { container, onNavigateWikilink } = renderHarness()
|
||||||
|
+ const link = appendUrl(container, 'docs/Related.md')
|
||||||
|
+
|
||||||
|
+ const mouseDown = dispatchMouseEvent(link, 'mousedown')
|
||||||
|
+ const click = dispatchMouseEvent(link, 'click')
|
||||||
|
+
|
||||||
|
+ expect(mouseDown.defaultPrevented).toBe(true)
|
||||||
|
+ expect(click.defaultPrevented).toBe(true)
|
||||||
|
+ await Promise.resolve()
|
||||||
|
+ expect(onNavigateWikilink).toHaveBeenCalledWith('docs/Related')
|
||||||
|
+ })
|
||||||
|
+
|
||||||
|
it('opens modified URL mousedown before editor internals see stale link nodes', () => {
|
||||||
|
const { container } = renderHarness()
|
||||||
|
const link = appendUrl(container, 'https://example.com')
|
||||||
|
diff --git a/src/components/useEditorLinkActivation.ts b/src/components/useEditorLinkActivation.ts
|
||||||
|
index f29c624..79a2dad 100644
|
||||||
|
--- a/src/components/useEditorLinkActivation.ts
|
||||||
|
+++ b/src/components/useEditorLinkActivation.ts
|
||||||
|
@@ -26,6 +26,24 @@ function resolveAnchorHref(target: HTMLElement) {
|
||||||
|
return target.closest<HTMLAnchorElement>('a[href]')?.getAttribute('href')?.trim() ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
+function decodeLinkPath(path: string) {
|
||||||
|
+ try {
|
||||||
|
+ return decodeURIComponent(path)
|
||||||
|
+ } catch {
|
||||||
|
+ return path
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+function resolveMarkdownNoteTarget(href: 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, '')
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
function blurActiveEditable(container: HTMLElement) {
|
||||||
|
const active = document.activeElement
|
||||||
|
if (!(active instanceof HTMLElement) || !container.contains(active)) return
|
||||||
|
@@ -84,7 +102,15 @@ function handleEditorLinkClick(
|
||||||
|
}
|
||||||
|
|
||||||
|
const href = resolveAnchorHref(target)
|
||||||
|
- if (href) activateUrl(event, href, vaultPath)
|
||||||
|
+ if (!href) return
|
||||||
|
+
|
||||||
|
+ const markdownTarget = resolveMarkdownNoteTarget(href)
|
||||||
|
+ if (markdownTarget) {
|
||||||
|
+ activateWikilink(event, container, markdownTarget, onNavigateWikilink)
|
||||||
|
+ return
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ activateUrl(event, href, vaultPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditorLinkMouseDown(event: MouseEvent, vaultPath?: string): string | null {
|
||||||
|
@@ -97,6 +123,11 @@ function handleEditorLinkMouseDown(event: MouseEvent, vaultPath?: string): strin
|
||||||
|
}
|
||||||
|
|
||||||
|
const href = resolveAnchorHref(target)
|
||||||
|
+ if (href && resolveMarkdownNoteTarget(href)) {
|
||||||
|
+ consumeEditorLinkEvent(event)
|
||||||
|
+ return null
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
if (hasFollowModifier(event) && href) {
|
||||||
|
activateUrl(event, href, vaultPath)
|
||||||
|
return href
|
||||||
|
diff --git a/src/theme.json b/src/theme.json
|
||||||
|
index 84b5e4c..92999d2 100644
|
||||||
|
--- a/src/theme.json
|
||||||
|
+++ b/src/theme.json
|
||||||
|
@@ -1,12 +1,12 @@
|
||||||
|
{
|
||||||
|
"editor": {
|
||||||
|
"fontFamily": "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
|
||||||
|
- "fontSize": 15,
|
||||||
|
- "lineHeight": 1.5,
|
||||||
|
- "maxWidth": 820,
|
||||||
|
+ "fontSize": 16,
|
||||||
|
+ "lineHeight": 1.65,
|
||||||
|
+ "maxWidth": 880,
|
||||||
|
"paddingHorizontal": 40,
|
||||||
|
- "paddingVertical": 20,
|
||||||
|
- "paragraphSpacing": 8,
|
||||||
|
+ "paddingVertical": 28,
|
||||||
|
+ "paragraphSpacing": 12,
|
||||||
|
"dividerFollowedByHeadingMarginTop": 8
|
||||||
|
},
|
||||||
|
"headings": {
|
||||||
|
@@ -80,11 +80,11 @@
|
||||||
|
"code": {
|
||||||
|
"fontFamily": "'SF Mono', 'Fira Code', monospace",
|
||||||
|
"fontSize": 14,
|
||||||
|
- "backgroundColor": "var(--bg-hover-subtle)",
|
||||||
|
- "paddingHorizontal": 4,
|
||||||
|
+ "backgroundColor": "color-mix(in srgb, var(--accent-blue) 11%, var(--bg-hover-subtle))",
|
||||||
|
+ "paddingHorizontal": 5,
|
||||||
|
"paddingVertical": 2,
|
||||||
|
"borderRadius": 3,
|
||||||
|
- "color": "var(--text-secondary)"
|
||||||
|
+ "color": "var(--accent-blue)"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"color": "var(--accent-blue)",
|
||||||
|
@@ -100,8 +100,8 @@
|
||||||
|
"blockquote": {
|
||||||
|
"borderLeftWidth": 3,
|
||||||
|
"borderLeftColor": "var(--accent-blue)",
|
||||||
|
- "paddingLeft": 16,
|
||||||
|
- "marginVertical": 12,
|
||||||
|
+ "paddingLeft": 18,
|
||||||
|
+ "marginVertical": 16,
|
||||||
|
"color": "var(--text-secondary)",
|
||||||
|
"fontStyle": "italic"
|
||||||
|
},
|
||||||
|
--
|
||||||
|
2.50.1 (Apple Git-155)
|
||||||
|
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
From 59ce6261d3dff070a138934d8038e0a935c003c7 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:15:45 +0800
|
||||||
|
Subject: [PATCH 9/9] test: lock in readable editor theme defaults
|
||||||
|
|
||||||
|
---
|
||||||
|
src/hooks/useTheme.test.ts | 6 +++---
|
||||||
|
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/hooks/useTheme.test.ts b/src/hooks/useTheme.test.ts
|
||||||
|
index aecc7bd..27d1f6c 100644
|
||||||
|
--- a/src/hooks/useTheme.test.ts
|
||||||
|
+++ b/src/hooks/useTheme.test.ts
|
||||||
|
@@ -3,11 +3,11 @@ import { renderHook } from '@testing-library/react'
|
||||||
|
import { useEditorTheme } from './useTheme'
|
||||||
|
|
||||||
|
describe('useEditorTheme', () => {
|
||||||
|
- it('keeps inline code on the muted editor surface without exporting code block overrides', () => {
|
||||||
|
+ it('gives inline code a subtle accent surface without exporting code block overrides', () => {
|
||||||
|
const { result } = renderHook(() => useEditorTheme())
|
||||||
|
|
||||||
|
expect(result.current.cssVars['--inline-styles-code-background-color']).toBe(
|
||||||
|
- 'var(--bg-hover-subtle)'
|
||||||
|
+ 'color-mix(in srgb, var(--accent-blue) 11%, var(--bg-hover-subtle))'
|
||||||
|
)
|
||||||
|
expect(result.current.cssVars['--code-blocks-background-color']).toBeUndefined()
|
||||||
|
})
|
||||||
|
@@ -27,6 +27,6 @@ describe('useEditorTheme', () => {
|
||||||
|
it('exports the default editor max width', () => {
|
||||||
|
const { result } = renderHook(() => useEditorTheme())
|
||||||
|
|
||||||
|
- expect(result.current.cssVars['--editor-max-width']).toBe('820px')
|
||||||
|
+ expect(result.current.cssVars['--editor-max-width']).toBe('880px')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
--
|
||||||
|
2.50.1 (Apple Git-155)
|
||||||
|
|
||||||
@ -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)
|
||||||
|
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
From 387ce84da12f33c64d179d7e2582124ae952091f 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:31:05 +0800
|
||||||
|
Subject: [PATCH] fix: resolve links inside mounted workspaces
|
||||||
|
|
||||||
|
---
|
||||||
|
src/components/SingleEditorView.tsx | 3 ++-
|
||||||
|
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/src/components/SingleEditorView.tsx b/src/components/SingleEditorView.tsx
|
||||||
|
index 0440e5a..a0e2dc8 100644
|
||||||
|
--- a/src/components/SingleEditorView.tsx
|
||||||
|
+++ b/src/components/SingleEditorView.tsx
|
||||||
|
@@ -1208,7 +1208,8 @@ export function SingleEditorView({ editor, entries, onNavigateWikilink, onChange
|
||||||
|
handleMouseMove: handleCodeBlockCopyMouseMove,
|
||||||
|
} = useCodeBlockCopyTarget(containerRef)
|
||||||
|
useBlockNoteSideMenuHoverGuard(containerRef)
|
||||||
|
- useEditorLinkActivation(containerRef, onNavigateWikilink, vaultPath, sourceEntry?.path)
|
||||||
|
+ const sourceVaultPath = sourceEntry?.workspace?.path ?? vaultPath
|
||||||
|
+ useEditorLinkActivation(containerRef, onNavigateWikilink, sourceVaultPath, sourceEntry?.path)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
_wikilinkEntriesRef.current = entries
|
||||||
|
--
|
||||||
|
2.50.1 (Apple Git-155)
|
||||||
|
|
||||||
105
docs/NEXT-TODO-20260717.md
Normal file
105
docs/NEXT-TODO-20260717.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# NEXT-TODO-20260717 · 明日待办
|
||||||
|
|
||||||
|
> 日期: 2026-07-17
|
||||||
|
>
|
||||||
|
> 适用仓库: `bingshuo/hololake-platform`
|
||||||
|
>
|
||||||
|
> 状态: PAGE_BLOCKS_COMPLETE_RUNNER_PENDING
|
||||||
|
|
||||||
|
## 1 · 补 Gitea Actions Runner
|
||||||
|
|
||||||
|
当前事实:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Windows 内测构建 workflow 已进入仓库。
|
||||||
|
Actions 页面能识别 build-windows-manual.yml。
|
||||||
|
当前阻塞点不是应用源码,而是服务器没有在线 runner。
|
||||||
|
页面提示: No matching online runner with label: ubuntu-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
明日任务:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 进入服务器或 Gitea 管理界面。
|
||||||
|
2. 获取 runner registration token。
|
||||||
|
3. 安装并注册 act_runner。
|
||||||
|
4. 给 runner 配置 ubuntu-latest 标签。
|
||||||
|
5. 启动 runner daemon / systemd 服务。
|
||||||
|
6. 回到 hololake-platform Actions 页面重新运行 Build Windows Internal Installer。
|
||||||
|
7. 确认 artifact: Guanghu-Windows-x64-internal。
|
||||||
|
```
|
||||||
|
|
||||||
|
参考文档:
|
||||||
|
|
||||||
|
- [Gitea Actions Runner · Windows 内测安装包构建](ops/GITEA-ACTIONS-RUNNER-WINDOWS-INTERNAL.md)
|
||||||
|
|
||||||
|
## 2 · 光湖 App 页面内容块增强
|
||||||
|
|
||||||
|
2026-07-17 恢复进度:
|
||||||
|
|
||||||
|
```text
|
||||||
|
已完成并登记为平台仓补丁 0005—0007。
|
||||||
|
已完成:彩色提示块、批注块、关系卡、路径包、折叠块恢复、主题可见性修复、默认世界首页。
|
||||||
|
现有能力已确认:代码块、引用、Mermaid、表格、白板、图片/音视频。
|
||||||
|
前端定向测试:5 个文件,24 项通过。
|
||||||
|
TypeScript、ESLint 与 Vite production build 通过。
|
||||||
|
```
|
||||||
|
|
||||||
|
冰朔反馈:
|
||||||
|
|
||||||
|
```text
|
||||||
|
现在软件页面太单一。
|
||||||
|
页面连颜色都没有。
|
||||||
|
想用高亮代码块也用不出来。
|
||||||
|
需要类似 Notion 页面那种更多样的内容组件。
|
||||||
|
```
|
||||||
|
|
||||||
|
判断:
|
||||||
|
|
||||||
|
```text
|
||||||
|
这不是重做大皮肤。
|
||||||
|
这是 HLP-MOD-0002 视觉与知识渲染模块的下一步。
|
||||||
|
目标是让光湖 App 的知识页、研发页、工单页和回执页能真正承载结构化内容。
|
||||||
|
```
|
||||||
|
|
||||||
|
第一批组件清单:
|
||||||
|
|
||||||
|
| 组件 | 作用 | 最小验收 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 彩色提示块 | info / warning / success / danger / note | 可以插入、渲染、保存、重新打开后不丢 |
|
||||||
|
| 高亮代码块 | 支持语言选择、复制、浅/深色主题 | TypeScript / Bash / JSON / YAML 至少可读 |
|
||||||
|
| 折叠块 | 类似 Notion toggle | 可展开/收起,刷新后状态或内容不丢 |
|
||||||
|
| 引用 / 标注块 | 放重点句、系统定义、冰朔判断 | 有清晰边框、背景和文本层级 |
|
||||||
|
| 关系卡 | 显示仓库、模块、工单、回执的关系 | 可展示编号、标题、状态、跳转路径 |
|
||||||
|
| 路径面包屑 | 显示当前资料所在世界/模块/文件 | 不占大面积,能帮助定位 |
|
||||||
|
| 媒体 / 图表块 | 图片、Mermaid、表格、白板入口 | 至少不破坏现有编辑器布局 |
|
||||||
|
|
||||||
|
开发顺序建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 先做高亮代码块和彩色提示块。
|
||||||
|
2. 再做折叠块和引用/标注块。
|
||||||
|
3. 最后做关系卡、路径面包屑和媒体/图表块。
|
||||||
|
```
|
||||||
|
|
||||||
|
边界:
|
||||||
|
|
||||||
|
```text
|
||||||
|
不做重型首页。
|
||||||
|
不做全局大换肤。
|
||||||
|
不把光湖 App 变成第二套 Notion。
|
||||||
|
只在现有 Tolaria / BlockNote 编辑体验上补原生轻量内容块。
|
||||||
|
每个组件独立验收、独立开关、可回退。
|
||||||
|
```
|
||||||
|
|
||||||
|
相关模块:
|
||||||
|
|
||||||
|
- [HLP-MOD-0002 · 视觉与知识渲染](../apps/tolaria/modules/02-knowledge-rendering/README.md)
|
||||||
|
- [HLP-DEV-0001 · Tolaria UI 基线](../research/progress/HLP-DEV-0001-ICE-GL-INFINITY-TOLARIA-UI-BASELINE.md)
|
||||||
|
- [HLP-DEV-0002 · HoloLake Era 原型评审](../research/progress/HLP-DEV-0002-ICE-GL-INFINITY-HOLOLAKE-ERA-PROTOTYPE-REVIEW.md)
|
||||||
|
|
||||||
|
## 3 · 明日开工入口
|
||||||
|
|
||||||
|
1. [先补 Gitea Actions Runner](ops/GITEA-ACTIONS-RUNNER-WINDOWS-INTERNAL.md)
|
||||||
|
2. [继续验收视觉与知识渲染模块](../apps/tolaria/modules/02-knowledge-rendering/README.md)
|
||||||
|
3. [回到本页继续执行](NEXT-TODO-20260717.md)
|
||||||
162
docs/ops/GITEA-ACTIONS-RUNNER-WINDOWS-INTERNAL.md
Normal file
162
docs/ops/GITEA-ACTIONS-RUNNER-WINDOWS-INTERNAL.md
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# Gitea Actions Runner · Windows 内测安装包构建
|
||||||
|
|
||||||
|
> 状态: RUNNER_REQUIRED
|
||||||
|
>
|
||||||
|
> 适用仓库: `bingshuo/hololake-platform`
|
||||||
|
>
|
||||||
|
> 工作流: `.github/workflows/build-windows-manual.yml`
|
||||||
|
>
|
||||||
|
> 当前用途: 生成 `Guanghu-Windows-x64-internal` 内测安装包 artifact
|
||||||
|
|
||||||
|
## 1 · 当前问题
|
||||||
|
|
||||||
|
`Build Windows Internal Installer` 工作流已经进入仓库,但 Gitea Actions 页面显示:
|
||||||
|
|
||||||
|
```text
|
||||||
|
No matching online runner with label: ubuntu-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
这表示问题不在应用源码,也不在 Windows 安装包工作流本身,而是服务器当前没有在线 Gitea Actions runner 接收 `ubuntu-latest` 标签的任务。
|
||||||
|
|
||||||
|
## 2 · 为什么先用 ubuntu-latest
|
||||||
|
|
||||||
|
本工作流当前使用 Linux runner 交叉构建 Windows x64 NSIS 内测安装包:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 服务器只需要一台 Linux runner 就能先产出 Windows NSIS 内测包。
|
||||||
|
2. 不要求 Windows 代码签名证书。
|
||||||
|
3. 不要求 Windows 桌面机器长期在线。
|
||||||
|
4. 比正式 Windows runner 更容易先补齐。
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:这是内测 unsigned installer 路线,不是正式发布链。
|
||||||
|
|
||||||
|
## 3 · 最小 runner 要求
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
runner:
|
||||||
|
label: ubuntu-latest
|
||||||
|
os: linux
|
||||||
|
arch: x64
|
||||||
|
disk: "80GB+ recommended"
|
||||||
|
memory: "4GB minimum, 8GB recommended"
|
||||||
|
network: "must reach guanghubingshuo.com and public package registries"
|
||||||
|
```
|
||||||
|
|
||||||
|
工作流会自动安装:
|
||||||
|
|
||||||
|
```text
|
||||||
|
nsis
|
||||||
|
lld
|
||||||
|
llvm
|
||||||
|
clang
|
||||||
|
cargo-xwin
|
||||||
|
Node.js 22
|
||||||
|
pnpm 10
|
||||||
|
Rust x86_64-pc-windows-msvc target
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4 · 服务器补齐步骤
|
||||||
|
|
||||||
|
在运行 Gitea 的服务器或独立构建机上安装并启动 `act_runner`。
|
||||||
|
|
||||||
|
示例流程:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 安装 runner 二进制
|
||||||
|
# 按服务器系统选择 Gitea act_runner 官方 release。
|
||||||
|
|
||||||
|
# 2. 注册 runner
|
||||||
|
act_runner register \
|
||||||
|
--instance https://guanghubingshuo.com/code \
|
||||||
|
--token <GITEA_ACTIONS_RUNNER_REGISTRATION_TOKEN> \
|
||||||
|
--name hololake-linux-runner-01 \
|
||||||
|
--labels ubuntu-latest:host
|
||||||
|
|
||||||
|
# 3. 启动 runner
|
||||||
|
act_runner daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
生产建议用 systemd 托管:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Gitea Actions Runner
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/act_runner daemon --config /etc/act_runner/config.yaml
|
||||||
|
WorkingDirectory=/var/lib/act_runner
|
||||||
|
Restart=always
|
||||||
|
User=act-runner
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5 · 注册 token 放哪里
|
||||||
|
|
||||||
|
不要把 runner 注册 token 写入仓库。
|
||||||
|
|
||||||
|
token 应从 Gitea 管理界面获取:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Repository / Organization / Site Admin
|
||||||
|
→ Actions
|
||||||
|
→ Runners
|
||||||
|
→ Create or registration token
|
||||||
|
```
|
||||||
|
|
||||||
|
token 只在服务器注册 runner 时使用。
|
||||||
|
|
||||||
|
## 6 · 验证方式
|
||||||
|
|
||||||
|
runner 启动后,进入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://guanghubingshuo.com/code/bingshuo/hololake-platform/actions
|
||||||
|
```
|
||||||
|
|
||||||
|
确认 `build-windows-manual.yml` 左侧不再显示:
|
||||||
|
|
||||||
|
```text
|
||||||
|
No matching online runner with label: ubuntu-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
然后重新运行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Build Windows Internal Installer
|
||||||
|
```
|
||||||
|
|
||||||
|
成功后 artifact 名称应为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Guanghu-Windows-x64-internal
|
||||||
|
```
|
||||||
|
|
||||||
|
其中包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
*-setup.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7 · 后续正式发布链
|
||||||
|
|
||||||
|
内测 runner 跑通后,再补正式链:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. Windows Authenticode 代码签名证书
|
||||||
|
2. Tauri updater signing key
|
||||||
|
3. 正式 release workflow
|
||||||
|
4. 安装包 smoke test
|
||||||
|
5. 自有更新源 latest.json
|
||||||
|
```
|
||||||
|
|
||||||
|
正式发布前不要把该内测包描述为“已签名正式版”。
|
||||||
@ -3,7 +3,7 @@ registry:
|
|||||||
name: 光湖研发模块注册表
|
name: 光湖研发模块注册表
|
||||||
status: active
|
status: active
|
||||||
source_of_truth: true
|
source_of_truth: true
|
||||||
updated_at: "2026-07-13T22:30:00+08:00"
|
updated_at: "2026-07-17T10:30:00+08:00"
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
- 模块编号永久唯一,废弃后不可复用。
|
- 模块编号永久唯一,废弃后不可复用。
|
||||||
@ -41,9 +41,9 @@ modules:
|
|||||||
broadcast: research/broadcast/HLP-BRD-0003-ICE-GL-INFINITY-ZERO-CORE-ACTIVATION.md
|
broadcast: research/broadcast/HLP-BRD-0003-ICE-GL-INFINITY-ZERO-CORE-ACTIVATION.md
|
||||||
code_home: apps/tolaria/modules/02-knowledge-rendering/
|
code_home: apps/tolaria/modules/02-knowledge-rendering/
|
||||||
summary: 轻量知识页、路径卡、媒体和图表的可读渲染;重型全局皮肤不作为当前方向。
|
summary: 轻量知识页、路径卡、媒体和图表的可读渲染;重型全局皮肤不作为当前方向。
|
||||||
evidence: apps/tolaria/patches/0001-feat-add-Guanghu-ice-lake-visual-layer.patch
|
evidence: apps/tolaria/patches/0007-feat-complete-portable-page-block-set.patch
|
||||||
latest_progress: research/progress/HLP-DEV-0003-ICE-GL-INFINITY-TOLARIA-LIGHTWEIGHT-UI-AND-DRAG-FIX.md
|
latest_progress: research/progress/HLP-DEV-0004-HOLOLAKE-PAGE-BLOCKS-AND-THEME-RECOVERY.md
|
||||||
updated_at: "2026-07-15T17:00:00+08:00"
|
updated_at: "2026-07-17T10:30:00+08:00"
|
||||||
- id: HLP-MOD-0003
|
- id: HLP-MOD-0003
|
||||||
name: 仓库路径与知识索引
|
name: 仓库路径与知识索引
|
||||||
product: Tolaria / 光湖 App
|
product: Tolaria / 光湖 App
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
# HLP-DEV-0004 · HoloLake Era 页面块与主题恢复
|
||||||
|
|
||||||
|
> 日期:2026-07-17
|
||||||
|
>
|
||||||
|
> 关联模块:`HLP-MOD-0002`
|
||||||
|
>
|
||||||
|
> 状态:已验证
|
||||||
|
|
||||||
|
## 为什么做
|
||||||
|
|
||||||
|
桌面同时存在三个同 Bundle ID、同图标的 App 副本,macOS LaunchServices
|
||||||
|
会将它们视为同一应用。上一轮所谓“Notion 类页面组件”实际只完成了
|
||||||
|
四种 callout;首页默认隐藏,浅色主题也没有同步应用的基础亮暗模式。
|
||||||
|
|
||||||
|
## 实际改动
|
||||||
|
|
||||||
|
1. 恢复并完善可携带 Markdown callout 块:info / success / warning / danger。
|
||||||
|
2. 增加 annotation / relationship / path 三种语义块,支持中英文斜杠命令搜索。
|
||||||
|
3. 重新开放 BlockNote 原生 toggle heading / toggle list 入口。
|
||||||
|
4. 保留并复用现有代码、引用、Mermaid、表格、白板和媒体块。
|
||||||
|
5. HoloLake Era 世界首页改为默认可见,首页直接显示页面组件已就绪。
|
||||||
|
6. 湖光浅色与湖心深色现在会切换实际 `data-theme` 亮/暗模式。
|
||||||
|
|
||||||
|
## 代码与补丁路径
|
||||||
|
|
||||||
|
- 可编译开发基线:`bingshuo/guanghu`(REPO-004,只作零件/上游基线)。
|
||||||
|
- 产品事实与付款路径:`bingshuo/hololake-platform` 的独立补丁。
|
||||||
|
- `apps/tolaria/patches/0005-Add-portable-callout-page-blocks.patch`
|
||||||
|
- `apps/tolaria/patches/0006-fix-make-Guanghu-home-and-themes-visible.patch`
|
||||||
|
- `apps/tolaria/patches/0007-feat-complete-portable-page-block-set.patch`
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- Vitest:5 个定向文件,24 项全部通过。
|
||||||
|
- ESLint:本次修改文件通过。
|
||||||
|
- TypeScript:`tsc --noEmit` 通过。
|
||||||
|
- Vite:production build 通过。
|
||||||
|
- Tauri:Apple Silicon release App 与 DMG 构建通过。
|
||||||
|
- 原生视觉验收:默认世界首页、光湖 Logo、页面组件入口、独立 0.1.3 版本号可见。
|
||||||
|
- 内测 Bundle ID:`com.guanghu.desktop.review.20260717`,不与旧 `com.guanghu.desktop` 副本串版。
|
||||||
|
- DMG SHA-256 本地已核验,仓库仅记缩略值:`29bda0c9…40fda73`。
|
||||||
|
- 在服务器最新 `bingshuo/guanghu@514ab19` 新克隆上,按 `0003 → 0004 → 0005 → 0006 → 0007` 应用成功。
|
||||||
|
- CodeScene / Codacy:当前本机没有 MCP、CLI 或 API 环境,未能执行,不伪造通过结果。
|
||||||
|
|
||||||
|
## 未完成项
|
||||||
|
|
||||||
|
1. 用户确认 0.1.3 Review 后,将桌面三个旧副本移入归档或废纸篓。
|
||||||
|
2. 安装 CodeScene / Codacy 验证环境后补跑两项质量门。
|
||||||
|
3. Windows 自动打包仍需 Gitea Actions runner。
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
# HLP-DEV-0005 · Notion 阅读层与页面关系恢复
|
||||||
|
|
||||||
|
> 日期:2026-07-17
|
||||||
|
>
|
||||||
|
> 关联模块:`HLP-MOD-0002`
|
||||||
|
>
|
||||||
|
> 状态:0.1.4 Review 已完成原生验收
|
||||||
|
|
||||||
|
## 问题
|
||||||
|
|
||||||
|
Tolaria 软件壳本身正常,但知识页出现两项回退:
|
||||||
|
|
||||||
|
1. 标题、提示、正文、代码和表格挤在一起,普通引用只剩一根细线;
|
||||||
|
2. Notion 导出的 `[页面](相对路径.md)` 没有接到 Tolaria 内部导航,普通单击没有反应。
|
||||||
|
|
||||||
|
原生 `[[wikilink]]` 实现并未消失。真正缺失的是普通 Markdown 页面链接到
|
||||||
|
Tolaria 页面路由的兼容层;当前页面还把部分参考路径放在代码块里,代码文本本来也不可点击。
|
||||||
|
|
||||||
|
## 恢复内容
|
||||||
|
|
||||||
|
- 相对 `.md` / `.markdown` 链接普通单击后直接打开目标笔记;
|
||||||
|
- 解码 Notion 导出文件名里的 URL 编码,并去除查询串、锚点和扩展名后交给现有页面解析器;
|
||||||
|
- 不改变外部网址与附件的安全交互;
|
||||||
|
- 普通引用升级为蓝色提示面板,增加背景、边框、圆角和内边距;
|
||||||
|
- 增大正文行距、段落间距和阅读列宽;
|
||||||
|
- 代码块、表格、callout 与行内代码恢复独立视觉层次;
|
||||||
|
- `NEXT-TODO-20260717` 的参考路径已改为真实 Markdown 页面链接。
|
||||||
|
|
||||||
|
## 补丁与构建
|
||||||
|
|
||||||
|
- `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`
|
||||||
|
- `apps/tolaria/patches/0011-fix-resolve-links-inside-mounted-workspaces.patch`
|
||||||
|
- 桌面验收包:`HoloLake Era 0.1.4 Review`
|
||||||
|
- Bundle ID:`com.guanghu.desktop.review.20260717.page`
|
||||||
|
- DMG SHA-256:`ee5e4db8…35ee988`(完整值见验收包同目录 `.sha256` 文件)
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 回归测试先失败后修复:相对 Markdown 页面链接普通单击跳转;
|
||||||
|
- 原生点击发现重复 `README.md` 会误跳后,再补回归测试:路径按来源页面目录归一化并跳到唯一正确页面;
|
||||||
|
- 挂载多工作区复测后,补齐来源页面所属工作区路径,避免拿当前主仓目录解析挂载页面链接;
|
||||||
|
- 链接与编辑器定向 Vitest:51 项通过;
|
||||||
|
- ESLint、TypeScript 与 Vite production build 通过;
|
||||||
|
- Tauri Apple Silicon App 与 DMG 构建通过;
|
||||||
|
- 原生视觉验收:元信息提示面板、段落节奏、代码块层次可见;
|
||||||
|
- 原生导航验收:从 `NEXT-TODO-20260717` 普通单击,准确进入挂载平台仓的 `HLP-MOD-0002 · 视觉与知识渲染`;
|
||||||
|
- CodeScene / Codacy:本机仍无 MCP、CLI 或 API 环境,未伪造结果。
|
||||||
|
|
||||||
|
全量 Vitest 运行了 4822 项,其中 4809 项通过、13 项失败;2 项是主题快照预期,
|
||||||
|
已随补丁更新并通过。其余失败来自 5 个既有测试文件,和本次链接、CSS 改动无直接路径,
|
||||||
|
后续应在正式依赖安装环境中单独复跑,不把它们误记为本次功能通过。
|
||||||
Loading…
x
Reference in New Issue
Block a user