对齐 Wolai 编辑器搜索与文档画布
@@ -208,32 +208,118 @@ RED / GREEN:
|
|||||||
|
|
||||||
| ID | 状态 | 任务 | 验收要点 |
|
| ID | 状态 | 任务 | 验收要点 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| C1 | TODO | 顶栏高度与按钮密度 | 顶栏约 40px;按钮轻量 hover;右侧 icon/text 序列不重 |
|
| C1 | PARITY | 顶栏高度与按钮密度 | `task135` 覆盖顶栏约 40px、右侧按钮组无搜索入口、icon 不再渲染成黑色方块;`task119` 复核 topbarHeight=40 |
|
||||||
| C2 | TODO | breadcrumb | 图标、分隔符、弱色文字、截断方式对齐;切页后与页面树同步 |
|
| C2 | PARITY | breadcrumb | `task135` 覆盖 breadcrumb 当前标题绑定、弱色分隔符改为 Wolai chevron;真实父路径数据仍依赖后续 Page Aggregate / tree projection |
|
||||||
| C3 | TODO | owner/published 按钮分型 | owner 与 published 的按钮组合分开;不把 published 文案硬塞 owner 态 |
|
| C3 | PARITY | owner/published 按钮分型 | `task135` 覆盖 owner 顶栏绿色 `全网公开`,右侧按钮组从本地 AI/搜索收敛为 Wolai owner 态常见按钮序列 |
|
||||||
| C4 | TODO | 顶栏搜索入口 | 点击打开全局搜索 modal,URL 不变,焦点进入输入框 |
|
| C4 | PARITY | 顶栏搜索入口基线纠偏 | 2026-04-30 Wolai owner 当前 viewport 顶栏无搜索入口;本地已移除顶栏搜索按钮,搜索统一走侧栏入口 / `Ctrl+P` |
|
||||||
| C5 | TODO | 侧栏搜索入口 | 打开同一个全局搜索 modal 或明确的树过滤入口;不得误跳 `/search` |
|
| C5 | PARITY | 侧栏搜索入口 | `task135` 覆盖侧栏左上搜索打开同一个全局搜索 modal,URL 不变;modal 打开时再次点击侧栏搜索会关闭 |
|
||||||
| C6 | TODO | 搜索 modal 默认状态 | `仅匹配标题`、`页面内搜索` 为 switch 且默认开启;不是普通 pill button |
|
| C6 | PARITY | 搜索 modal 默认状态 | `task135` 按最新 Wolai 实测修正:空 query 先显示最近浏览,不直接显示筛选;输入后 switch 出现,`仅匹配标题` / `页面内搜索` 为 switch 且默认关闭 |
|
||||||
| C7 | TODO | 搜索 modal 关闭路径 | Esc、遮罩、再次 `Ctrl+P`、入口 toggle 按 Wolai 实测对齐 |
|
| C7 | PARITY | 搜索 modal 关闭路径 | `task135` 覆盖 Esc、主区域遮罩、再次 `Ctrl+P`、侧栏入口 toggle;URL 均不变 |
|
||||||
| C8 | TODO | 搜索结果行 | 匹配数量、命中高亮、快捷键提示、结果 icon、所在页面信息对齐 |
|
| C8 | PARITY | 搜索结果行 | `task135` 覆盖后台 API 结果、匹配数量、红色命中高亮、快捷键提示、结果 icon、结果 owner 标记 |
|
||||||
| C9 | TODO | 搜索空态/加载态 | 输入无结果和加载时的视觉与 DOM 状态可被 smoke 捕获 |
|
| C9 | PARITY | 搜索空态/加载态 | `task135` 覆盖空 query 最近浏览、输入后 loading、无结果 `没有搜索结果`,DOM 可断言 `data-search-loading` / `data-search-empty` |
|
||||||
| C10 | TODO | 搜索入口/快捷键矩阵 | 分别验证 Wolai 左侧搜索、顶栏/侧栏入口、`Ctrl+P`、`Ctrl+K`;每个入口记录 URL、打开/关闭路径和阻塞 |
|
| C10 | PARITY | 搜索入口/快捷键矩阵 | `task135` 和 subagent baseline 确认:侧栏搜索 / `Ctrl+P` 是搜索入口;`Ctrl+K` 按当前 Wolai 实测不打开搜索 |
|
||||||
| C11 | TODO | 搜索结果 fixture | 固定本地 `Hermes` 或等价种子数据,避免用随机工作区数据对比 Wolai 结果态 |
|
| C11 | PARITY | 搜索结果 fixture | `task135` 固定本地 `Rust` / `Hermes` fallback fixture,并断言 modal 调用 `/api/search/documents` 而不是 DOM 前端过滤 |
|
||||||
|
|
||||||
|
|
||||||
|
### 6.1 task135 Phase C 执行记录
|
||||||
|
|
||||||
|
2026-04-30 已完成 Phase C:顶栏、breadcrumb、owner/published、搜索入口与搜索 modal。执行口径按 `wolai-aline` skill:subagent 先取 Wolai / 本地基线,主线程复核截图后补 RED smoke;实现后本地 smoke 通过,并已派 subagent 做 post-fix 复测。
|
||||||
|
|
||||||
|
RED:
|
||||||
|
|
||||||
|
- 新增 `scripts/task135-wolai-aline-phase-c-smoke.js`,初始失败于顶栏高度 `44px`、公开态文案 `Public`、顶栏多搜索入口、搜索 modal 未调用 `/api/search/documents`。
|
||||||
|
- subagent 基线纠偏:当前 Wolai `Ctrl+K` 不打开搜索;空 query 不直接显示筛选控件;输入后 `仅匹配标题` / `页面内搜索` switch 本次实测为关闭态。
|
||||||
|
|
||||||
|
GREEN:
|
||||||
|
|
||||||
|
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`:顶栏公开态改为 `全网公开`,移除顶栏搜索入口,右侧按钮组对齐 owner 态;侧栏搜索入口改为 toggle;搜索 modal 输入改为 `type=search`,空 query 显示最近浏览,输入后调用 `/api/search/documents`,结果区标记 `data-search-results-owner="rust-kernel"`。
|
||||||
|
- `rust/crates/mnote-web/src/ssr/styles.rs`:顶栏高度收敛到 40px;搜索遮罩支持侧栏入口穿透 toggle,同时保留主区域遮罩关闭;补齐右侧按钮与结果图标 mask,避免黑色方块;搜索高亮统一红色弱底。
|
||||||
|
- `rust/crates/mnote-web/src/routes/search.rs`:fallback 搜索数据补 `Hermes` / `技能知识图谱开发` fixture,避免本地结果态依赖随机工作区数据。
|
||||||
|
- `scripts/task128-rust-web-wolai-search-modal-smoke.js`:同步当前 Wolai 基线,改为侧栏 / `Ctrl+P` 入口和 switch 默认关闭。
|
||||||
|
|
||||||
|
验证命令:
|
||||||
|
|
||||||
|
- `cargo fmt -p mnote-web --check`
|
||||||
|
- `cargo check -p mnote-web`
|
||||||
|
- `cargo build -p mnote-web --bin mnote-web`
|
||||||
|
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task135-wolai-aline-phase-c-smoke.js`
|
||||||
|
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task128-rust-web-wolai-search-modal-smoke.js`
|
||||||
|
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task125-rust-web-search-server-first-smoke.js`
|
||||||
|
- `node scripts/task119-rust-web-wolai-visual-regression-smoke.js`
|
||||||
|
|
||||||
|
证据路径:
|
||||||
|
|
||||||
|
- Phase C subagent 基线矩阵:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task135-phase-c-baseline/comparison-matrix.md`
|
||||||
|
- Wolai Hermes 搜索基线:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task135-phase-c-baseline/wolai-search-hermes.png`
|
||||||
|
- 本地 Phase C 最终截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task135-phase-c/local-phase-c-search.png`;post-fix subagent 本地复核:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task135-phase-c-postfix/local-10-query-hermes.png`
|
||||||
|
- 本地搜索 modal smoke 截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/local-search-modal/local-3000-search-modal.png`
|
||||||
|
|
||||||
|
主线程复核结论:
|
||||||
|
|
||||||
|
- 搜索不再只是 UI 壳:modal 输入会调用 `/api/search/documents`,请求体包含 query 和 switch filter,结果区有 `data-search-results-owner="rust-kernel"`。
|
||||||
|
- 当前 Wolai 基线推翻了旧 checklist 的两个假设:`Ctrl+K` 不是搜索入口;`仅匹配标题` / `页面内搜索` 在本次输入态实测为关闭态。
|
||||||
|
- 本地 `Hermes` fixture 只能证明结果态结构、后台链路和高亮;不代表本地已经有 Wolai 真实空间数据。
|
||||||
|
- 顶栏/breadcrumb 的父路径层级仍受本地 Page Aggregate / tree projection 数据限制;本阶段只完成控件、入口、状态和同步锚点的对标。
|
||||||
|
- post-fix subagent 复测矩阵:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task135-phase-c-postfix/comparison-matrix.md`;复核确认本地右上角不再有黑色方块,侧栏搜索 toggle、`Ctrl+P` toggle、`Ctrl+K` 不打开、空 query 最近浏览、后台搜索 API、结果 icon/高亮均通过。
|
||||||
|
|
||||||
## 7. Phase D:文档画布与阅读态
|
## 7. Phase D:文档画布与阅读态
|
||||||
|
|
||||||
| ID | 状态 | 任务 | 验收要点 |
|
| ID | 状态 | 任务 | 验收要点 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| D1 | TODO | 主内容列宽 | 普通文档列宽约 760px;大屏不贴左;移动端不溢出 |
|
| D1 | PARITY | 主内容列宽 | `task136` 覆盖普通文档主列 `760px`,大屏居中不贴左;移动端沿既有 media query 保持不溢出 |
|
||||||
| D2 | TODO | 标题位置与样式 | Hermes 标题 y 位置、`34px / 44px / 600`、颜色接近 Wolai |
|
| D2 | PARITY | 标题位置与样式 | `task136` 按 Wolai 基线收敛到 y≈108、`34px / 44px / 600`、颜色接近 Wolai |
|
||||||
| D3 | TODO | 默认隐藏 meta/debug 行 | 普通文档默认不显示不像 Wolai 的工作区 meta、横线、debug outline |
|
| D3 | PARITY | 默认隐藏 meta/debug 行 | `task136` 断言普通文档默认不显示 workspace/已同步 meta、大号页面 icon、debug 文案和横线 |
|
||||||
| D4 | TODO | 正文段落 | `16px / 24px`、段间距、弱色文本与 Wolai 对齐 |
|
| D4 | PARITY | 正文段落 | `task136` 覆盖正文 `16px / 24px`,标题到正文间距按 Wolai 基线加大 |
|
||||||
| D5 | TODO | 任务块 | checkbox 尺寸、边框、文本 baseline、完成态接近 Wolai |
|
| D5 | PARITY | 任务块 | `task136` 固定 Tiptap fixture 并断言渲染后存在 `input[type=checkbox]`,尺寸 16px;补齐 kernel `todo` 类型到 Tiptap taskList 的转换 |
|
||||||
| D6 | TODO | 引用/列表/标题块 | 阅读态块样式不跳出 Wolai 密度;截图覆盖至少三类块 |
|
| D6 | PARITY | 引用/列表/标题块 | `task136` 覆盖 heading、blockquote、bulletList;补齐 `bullet_list_item` / `numbered_list_item` 映射避免降级段落 |
|
||||||
| D7 | TODO | 右下角浮动入口 | AI / 帮助入口减重,尺寸和位置接近 Wolai,不遮挡正文 |
|
| D7 | PARITY | 右下角浮动入口 | `task136` 覆盖 AI / 帮助入口 40px、AI 在上、间距约 16px、底部留白约 44px,不遮挡正文 |
|
||||||
| D8 | TODO | 阅读态和编辑态切换 | 切换时标题、正文列宽、scroll 位置不明显跳动 |
|
| D8 | PARITY | 阅读态和编辑态切换 | `task136` 覆盖标题 focus、编辑器 focus 后标题 y/x 不跳动;subagent baseline 同样确认点击后布局稳定 |
|
||||||
| D9 | PARITY | 普通文档首屏入口语义 | `task130` 已覆盖 RED/GREEN,subagent 最终对标复核通过:根页不再停在“打开当前页面”入口,直接出现 document shell + editor island |
|
| D9 | PARITY | 普通文档首屏入口语义 | `task130` 已覆盖 RED/GREEN,subagent 最终对标复核通过:根页不再停在“打开当前页面”入口,直接出现 document shell + editor island |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 7.1 task136 Phase D 执行记录
|
||||||
|
|
||||||
|
2026-04-30 已完成 Phase D:文档画布与阅读态。执行口径按 `wolai-aline` skill:subagent 先取 Wolai / 本地只读基线,主线程复核截图后补 RED smoke;实现后本地 smoke 与相关回归通过,并已派 subagent 做 post-fix 复测。
|
||||||
|
|
||||||
|
RED:
|
||||||
|
|
||||||
|
- 新增 `scripts/task136-wolai-aline-phase-d-smoke.js`,初始失败于本地文档主列 `880px`,后续增强断言捕获标题 y 偏低、标题到正文间距偏小、右下浮动入口间距/底部位置旧基线。
|
||||||
|
- 基线截图确认 Wolai 标题为 y≈108、`34px / 44px / 600`;普通文档无 workspace/已同步 meta 行和大号页面 icon;右下入口为 40px,AI 在帮助上方,按钮间距约 16px,底部留白约 44px。
|
||||||
|
- smoke 还暴露后台链路差异:保存 Tiptap fixture 后,`/api/documents/content` 返回 `todo` / `bullet_list_item` 等 kernel 规范化类型,但浏览器 bootstrap 只识别旧类型,导致任务块和列表降级为普通段落。
|
||||||
|
|
||||||
|
GREEN:
|
||||||
|
|
||||||
|
- `rust/crates/mnote-web/src/ssr/styles.rs`:文档主列收敛到 760px;标题改为 `34px / 44px / 600` 且 y≈108;普通文档隐藏页面大 icon 与 meta/debug 行;正文行高 24px;补 task checkbox、blockquote、list 样式;右下浮动入口减重为 40px,AI 在上、帮助在下,间距 16px、底部留白 44px。
|
||||||
|
- `rust/crates/mnote-web/src/routes/web_shell.rs`:`legacyBlockToTiptap` 同时识别旧类型和 kernel 规范化类型:`todo`、`bullet_list_item`、`numbered_list_item`、`blockquote`。
|
||||||
|
- `rust/crates/mnote-web/src/routes/editor.rs`:runtime block 类型映射同步兼容 kernel 规范化类型,避免编辑器链路继续把任务/列表块当成 media 或段落。
|
||||||
|
- `scripts/task119-rust-web-wolai-visual-regression-smoke.js`:按本次 Wolai 基线更新右下浮动入口顺序、间距和底部留白断言,避免旧 Stitch 目标覆盖当前实测。
|
||||||
|
|
||||||
|
验证命令:
|
||||||
|
|
||||||
|
- `cargo fmt -p mnote-web --check`
|
||||||
|
- `cargo check -p mnote-web`
|
||||||
|
- `cargo build -p mnote-web --bin mnote-web`
|
||||||
|
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task136-wolai-aline-phase-d-smoke.js`
|
||||||
|
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task121-rust-web-editor-island-hydration-smoke.js`
|
||||||
|
- `node scripts/task119-rust-web-wolai-visual-regression-smoke.js`
|
||||||
|
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task128-rust-web-wolai-search-modal-smoke.js`
|
||||||
|
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task135-wolai-aline-phase-c-smoke.js`
|
||||||
|
|
||||||
|
证据路径:
|
||||||
|
|
||||||
|
- Phase D subagent 基线矩阵:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task136-phase-d-baseline2/comparison-matrix.md`
|
||||||
|
- Wolai Phase D 基线:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task136-phase-d-baseline2/wolai-01-first.png`
|
||||||
|
- 本地 Phase D 最终截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task136-phase-d/local-phase-d-document.png`
|
||||||
|
- Phase D subagent post-fix:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task136-phase-d-postfix/comparison-matrix.md`
|
||||||
|
|
||||||
|
主线程复核结论:
|
||||||
|
|
||||||
|
- 本地文档画布主列、标题字号/行高/字重、标题 y、meta/icon/debug 隐藏、正文行高、任务/引用/列表渲染、右下浮动入口尺寸/顺序/间距已被 smoke 固化。
|
||||||
|
- subagent post-fix 在根文档截图中复核标题 y=108、列宽 760、字号 34/字重 600、meta/icon/debug 不可见、浮动入口 40px/16px/44px、点击后不跳动。
|
||||||
|
- 当前本地 fixture smoke 能证明后台内容链路不再把任务块和列表降级为普通段落;post-fix 根文档缺少任务/引用/列表内容,因此这三类块的真实视觉细节仍以后续 Phase E editable-test 或专门 fixture 页面继续细化。
|
||||||
|
- 剩余差异:本地页面标题来自临时 fixture,文案与 Wolai Hermes 不同;Wolai 真实 per-page icon 数据仍未进入本地 projection,按 Phase B 结论不伪造。
|
||||||
|
|
||||||
## 8. Phase E:主编辑器块交互
|
## 8. Phase E:主编辑器块交互
|
||||||
|
|
||||||
| ID | 状态 | 任务 | 验收要点 |
|
| ID | 状态 | 任务 | 验收要点 |
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 287 KiB |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 107 KiB |
@@ -214,10 +214,10 @@ fn runtime_block_type_for_raw_type(raw_type: &str) -> (&'static str, bool) {
|
|||||||
match raw_type {
|
match raw_type {
|
||||||
"paragraph" => ("paragraph", true),
|
"paragraph" => ("paragraph", true),
|
||||||
"heading" => ("heading", true),
|
"heading" => ("heading", true),
|
||||||
"bulletListItem" => ("bullet_list_item", true),
|
"bulletListItem" | "bullet_list_item" => ("bullet_list_item", true),
|
||||||
"numberedListItem" => ("numbered_list_item", true),
|
"numberedListItem" | "numbered_list_item" => ("numbered_list_item", true),
|
||||||
"checkListItem" | "advancedTodo" => ("todo", true),
|
"checkListItem" | "advancedTodo" | "todo" => ("todo", true),
|
||||||
"quote" => ("quote", true),
|
"quote" | "blockquote" => ("quote", true),
|
||||||
"divider" => ("divider", true),
|
"divider" => ("divider", true),
|
||||||
"codeBlock" => ("code_block", true),
|
"codeBlock" => ("code_block", true),
|
||||||
"pageReference" => ("page_reference", false),
|
"pageReference" => ("page_reference", false),
|
||||||
@@ -237,6 +237,8 @@ fn raw_type_for_runtime_block_type(block_type: &str, fallback_raw_type: &str) ->
|
|||||||
"todo" => {
|
"todo" => {
|
||||||
if fallback_raw_type == "advancedTodo" {
|
if fallback_raw_type == "advancedTodo" {
|
||||||
"advancedTodo".into()
|
"advancedTodo".into()
|
||||||
|
} else if fallback_raw_type == "todo" {
|
||||||
|
"todo".into()
|
||||||
} else {
|
} else {
|
||||||
"checkListItem".into()
|
"checkListItem".into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,6 +266,22 @@ fn fallback_search_dataset(workspace_id: &str) -> Value {
|
|||||||
"rawText": "mnote-web search documents transport",
|
"rawText": "mnote-web search documents transport",
|
||||||
"createdAt": "2026-04-28T00:00:00Z",
|
"createdAt": "2026-04-28T00:00:00Z",
|
||||||
"updatedAt": "2026-04-28T00:00:00Z"
|
"updatedAt": "2026-04-28T00:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "doc_hermes",
|
||||||
|
"workspaceId": workspace_id,
|
||||||
|
"title": "Hermes",
|
||||||
|
"rawText": "Hermes 技能知识图谱开发 Wolai aline fixture",
|
||||||
|
"createdAt": "2026-04-30T00:00:00Z",
|
||||||
|
"updatedAt": "2026-04-30T00:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "doc_hermes_skill",
|
||||||
|
"workspaceId": workspace_id,
|
||||||
|
"title": "技能知识图谱开发",
|
||||||
|
"rawText": "Hermes 页面路径 个人 软件开发",
|
||||||
|
"createdAt": "2026-04-30T00:00:00Z",
|
||||||
|
"updatedAt": "2026-04-30T00:00:00Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mindmaps": [],
|
"mindmaps": [],
|
||||||
|
|||||||
@@ -315,16 +315,16 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
|||||||
const level = Number(block?.props?.level || block?.level || 1) || 1;
|
const level = Number(block?.props?.level || block?.level || 1) || 1;
|
||||||
return { type: 'heading', attrs: { level: Math.max(1, Math.min(6, level)) }, content };
|
return { type: 'heading', attrs: { level: Math.max(1, Math.min(6, level)) }, content };
|
||||||
}
|
}
|
||||||
if (type === 'bulletListItem') {
|
if (type === 'bulletListItem' || type === 'bullet_list_item') {
|
||||||
return { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content }] }] };
|
return { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content }] }] };
|
||||||
}
|
}
|
||||||
if (type === 'numberedListItem') {
|
if (type === 'numberedListItem' || type === 'numbered_list_item') {
|
||||||
return { type: 'orderedList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content }] }] };
|
return { type: 'orderedList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content }] }] };
|
||||||
}
|
}
|
||||||
if (type === 'checkListItem' || type === 'advancedTodo') {
|
if (type === 'checkListItem' || type === 'advancedTodo' || type === 'todo') {
|
||||||
return { type: 'taskList', content: [{ type: 'taskItem', attrs: { checked: Boolean(block?.props?.checked) }, content: [{ type: 'paragraph', content }] }] };
|
return { type: 'taskList', content: [{ type: 'taskItem', attrs: { checked: Boolean(block?.props?.checked) }, content: [{ type: 'paragraph', content }] }] };
|
||||||
}
|
}
|
||||||
if (type === 'quote') {
|
if (type === 'quote' || type === 'blockquote') {
|
||||||
return { type: 'blockquote', content: [{ type: 'paragraph', content }] };
|
return { type: 'blockquote', content: [{ type: 'paragraph', content }] };
|
||||||
}
|
}
|
||||||
if (type === 'codeBlock') {
|
if (type === 'codeBlock') {
|
||||||
|
|||||||
@@ -663,33 +663,34 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
'<div class="wolai-search-dialog">' +
|
'<div class="wolai-search-dialog">' +
|
||||||
'<div class="wolai-search-input-row">' +
|
'<div class="wolai-search-input-row">' +
|
||||||
'<span class="material-symbols-outlined wolai-search-input-icon" data-icon="search" aria-hidden="true"></span>' +
|
'<span class="material-symbols-outlined wolai-search-input-icon" data-icon="search" aria-hidden="true"></span>' +
|
||||||
'<input data-testid="wolai-search-input" class="wolai-search-input" type="text" autocomplete="off" placeholder="在当前工作区中搜索" />' +
|
'<input data-testid="wolai-search-input" class="wolai-search-input" type="search" autocomplete="off" placeholder="在当前工作区中搜索" />' +
|
||||||
'<button type="button" class="wolai-search-close" data-testid="wolai-search-close" aria-label="关闭搜索">×</button>' +
|
'<button type="button" class="wolai-search-close" data-testid="wolai-search-close" aria-label="关闭搜索">×</button>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class="wolai-search-options" aria-label="搜索选项">' +
|
'<div class="wolai-search-options" data-testid="wolai-search-options" aria-label="搜索选项" hidden>' +
|
||||||
'<div class="wolai-search-options-left">' +
|
'<div class="wolai-search-options-left">' +
|
||||||
'<span class="wolai-search-switch-control"><span>仅匹配标题</span><button type="button" class="wolai-search-switch is-on" data-search-switch="title" role="switch" aria-checked="true" aria-label="仅匹配标题"></button></span>' +
|
'<span class="wolai-search-switch-control"><span>仅匹配标题</span><button type="button" class="wolai-search-switch" data-search-switch="title" role="switch" aria-checked="false" aria-label="仅匹配标题"></button></span>' +
|
||||||
'<span class="wolai-search-switch-control"><span>精确匹配</span><button type="button" class="wolai-search-switch" data-search-switch="exact" role="switch" aria-checked="false" aria-label="精确匹配"></button></span>' +
|
'<span class="wolai-search-switch-control"><span>精确匹配</span><button type="button" class="wolai-search-switch" data-search-switch="exact" role="switch" aria-checked="false" aria-label="精确匹配"></button></span>' +
|
||||||
'<span class="wolai-search-sort-control"><span>按编辑时间</span><button type="button" class="wolai-search-sort-value" data-search-sort="updated" aria-label="按编辑时间范围">所有</button></span>' +
|
'<span class="wolai-search-sort-control"><span>按编辑时间</span><button type="button" class="wolai-search-sort-value" data-search-sort="updated" aria-label="按编辑时间范围">所有</button></span>' +
|
||||||
'<span class="wolai-search-sort-control"><span>按创建时间</span><button type="button" class="wolai-search-sort-value" data-search-sort="created" aria-label="按创建时间范围">所有</button></span>' +
|
'<span class="wolai-search-sort-control"><span>按创建时间</span><button type="button" class="wolai-search-sort-value" data-search-sort="created" aria-label="按创建时间范围">所有</button></span>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class="wolai-search-options-right">' +
|
'<div class="wolai-search-options-right">' +
|
||||||
'<span class="wolai-search-switch-control"><span>页面内搜索</span><button type="button" class="wolai-search-switch is-on" data-search-switch="page" role="switch" aria-checked="true" aria-label="页面内搜索"></button></span>' +
|
'<span class="wolai-search-switch-control"><span>页面内搜索</span><button type="button" class="wolai-search-switch" data-search-switch="page" role="switch" aria-checked="false" aria-label="页面内搜索"></button></span>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class="wolai-search-result-meta" data-testid="wolai-search-result-meta"></div>' +
|
'<div class="wolai-search-result-meta" data-testid="wolai-search-result-meta"></div>' +
|
||||||
'<div class="wolai-search-results" data-testid="wolai-search-results"></div>' +
|
'<div class="wolai-search-results" data-testid="wolai-search-results" data-search-results-owner="rust-kernel"></div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
||||||
var closeButton = overlay.querySelector('[data-testid="wolai-search-close"]');
|
var closeButton = overlay.querySelector('[data-testid="wolai-search-close"]');
|
||||||
if (input) input.addEventListener('input', renderSearchResults);
|
if (input) input.addEventListener('input', scheduleSearchResultsRender);
|
||||||
overlay.querySelectorAll('[data-search-switch]').forEach(function(button) {
|
overlay.querySelectorAll('[data-search-switch]').forEach(function(button) {
|
||||||
button.addEventListener('click', function() {
|
button.addEventListener('click', function() {
|
||||||
var isOn = button.getAttribute('aria-checked') !== 'true';
|
var isOn = button.getAttribute('aria-checked') !== 'true';
|
||||||
button.setAttribute('aria-checked', isOn ? 'true' : 'false');
|
button.setAttribute('aria-checked', isOn ? 'true' : 'false');
|
||||||
button.classList.toggle('is-on', isOn);
|
button.classList.toggle('is-on', isOn);
|
||||||
|
scheduleSearchResultsRender();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (closeButton) closeButton.addEventListener('click', closeSearchModal);
|
if (closeButton) closeButton.addEventListener('click', closeSearchModal);
|
||||||
@@ -699,26 +700,33 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
return overlay;
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var activeSearchRequestId = 0;
|
||||||
|
var searchRenderTimer = 0;
|
||||||
|
|
||||||
function searchText(value) {
|
function searchText(value) {
|
||||||
return String(value == null ? '' : value).replace(/\s+/g, ' ').trim();
|
return String(value == null ? '' : value).replace(/\s+/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectSearchCandidates() {
|
function currentWorkspaceName() {
|
||||||
var seen = Object.create(null);
|
var name = document.querySelector('.sidebar-workspace-name');
|
||||||
var candidates = [];
|
return searchText(name && name.textContent) || '当前工作区';
|
||||||
function add(title, source) {
|
}
|
||||||
title = searchText(title);
|
|
||||||
if (!title || seen[title]) return;
|
function currentDocumentId() {
|
||||||
seen[title] = true;
|
var shell = document.querySelector('.document-shell[data-document-id]');
|
||||||
candidates.push({ title: title, source: source || '当前工作区' });
|
var bodyId = document.body && document.body.getAttribute('data-document-id');
|
||||||
}
|
return searchText(shell && shell.getAttribute('data-document-id')) || searchText(bodyId);
|
||||||
document.querySelectorAll('.tree-link-title, .wolai-row-title, [data-page-title-current="true"], .document-title-input, .document-read-view h1, .document-shell h1').forEach(function(node) {
|
}
|
||||||
add(node.textContent, '页面');
|
|
||||||
});
|
function searchSwitchValue(overlay, name) {
|
||||||
document.querySelectorAll('.mnote-content h1, .mnote-content h2, .mnote-content h3, .mnote-content p, .mnote-content a').forEach(function(node) {
|
var button = overlay.querySelector('[data-search-switch="' + name + '"]');
|
||||||
add(node.textContent, '当前页面');
|
return button instanceof HTMLElement && button.getAttribute('aria-checked') === 'true';
|
||||||
});
|
}
|
||||||
return candidates.slice(0, 120);
|
|
||||||
|
function highlightedHtml(value) {
|
||||||
|
return escapeHtml(value)
|
||||||
|
.replace(/<mark>/g, '<mark>')
|
||||||
|
.replace(/<\/mark>/g, '</mark>');
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlightSearchTitle(title, query) {
|
function highlightSearchTitle(title, query) {
|
||||||
@@ -732,31 +740,82 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
escapeHtml(cleanTitle.slice(index + cleanQuery.length));
|
escapeHtml(cleanTitle.slice(index + cleanQuery.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSearchResults() {
|
function renderSearchRecentState(overlay) {
|
||||||
|
var options = overlay.querySelector('[data-testid="wolai-search-options"]');
|
||||||
|
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
||||||
|
var results = overlay.querySelector('[data-testid="wolai-search-results"]');
|
||||||
|
if (options instanceof HTMLElement) options.hidden = true;
|
||||||
|
if (meta) meta.innerHTML = '<span>最近浏览</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
||||||
|
if (results) {
|
||||||
|
results.innerHTML = '<div class="wolai-search-recent" data-testid="wolai-search-recent">暂无最近浏览</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleSearchResultsRender() {
|
||||||
|
window.clearTimeout(searchRenderTimer);
|
||||||
|
searchRenderTimer = window.setTimeout(function() { void renderSearchResults(); }, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderSearchResults() {
|
||||||
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
||||||
if (!(overlay instanceof HTMLElement)) return;
|
if (!(overlay instanceof HTMLElement)) return;
|
||||||
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
||||||
|
var options = overlay.querySelector('[data-testid="wolai-search-options"]');
|
||||||
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
||||||
var results = overlay.querySelector('[data-testid="wolai-search-results"]');
|
var results = overlay.querySelector('[data-testid="wolai-search-results"]');
|
||||||
if (!input || !meta || !results) return;
|
if (!input || !meta || !results) return;
|
||||||
var query = searchText(input.value);
|
var query = searchText(input.value);
|
||||||
var candidates = collectSearchCandidates();
|
if (!query) {
|
||||||
var filtered = query
|
activeSearchRequestId += 1;
|
||||||
? candidates.filter(function(item) { return item.title.toLowerCase().indexOf(query.toLowerCase()) >= 0; })
|
renderSearchRecentState(overlay);
|
||||||
: candidates.slice(0, 8);
|
|
||||||
filtered = filtered.slice(0, 100);
|
|
||||||
meta.innerHTML = '<span>共 ' + filtered.length + ' 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
|
||||||
if (!filtered.length) {
|
|
||||||
results.innerHTML = '<div class="wolai-search-empty">暂无匹配结果</div>';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
results.innerHTML = filtered.map(function(item) {
|
if (options instanceof HTMLElement) options.hidden = false;
|
||||||
return '<button type="button" class="wolai-search-result-row">' +
|
var requestId = ++activeSearchRequestId;
|
||||||
'<span class="material-symbols-outlined wolai-search-result-icon" data-icon="link" aria-hidden="true"></span>' +
|
meta.innerHTML = '<span>搜索中…</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
||||||
'<span class="wolai-search-result-main"><span class="wolai-search-result-title">' + highlightSearchTitle(item.title, query) + '</span>' +
|
results.innerHTML = '<div class="wolai-search-empty" data-search-loading="true">搜索中…</div>';
|
||||||
'<span class="wolai-search-result-snippet">' + escapeHtml(item.source) + ' · 当前工作区</span></span>' +
|
try {
|
||||||
'</button>';
|
var response = await fetch('/api/search/documents', {
|
||||||
}).join('');
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
workspaceId: resolveWorkspaceId(document.body),
|
||||||
|
documentId: currentDocumentId() || null,
|
||||||
|
query: query,
|
||||||
|
limit: 30,
|
||||||
|
filters: {
|
||||||
|
titleOnly: searchSwitchValue(overlay, 'title'),
|
||||||
|
exact: searchSwitchValue(overlay, 'exact'),
|
||||||
|
includeOcr: false,
|
||||||
|
onlyCurrentPage: searchSwitchValue(overlay, 'page'),
|
||||||
|
timeRange: 'any',
|
||||||
|
timeField: 'updated'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
var payload = await response.json();
|
||||||
|
if (requestId !== activeSearchRequestId) return;
|
||||||
|
var items = Array.isArray(payload.results) ? payload.results : [];
|
||||||
|
meta.innerHTML = '<span>共 ' + items.length + ' 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
||||||
|
if (!items.length) {
|
||||||
|
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">没有搜索结果</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
results.innerHTML = items.map(function(item) {
|
||||||
|
var title = searchText(item.title || item.name || item.documentTitle || '无标题');
|
||||||
|
var snippet = searchText(item.snippet || (Array.isArray(item.evidence) && item.evidence[0] && item.evidence[0].snippet) || '');
|
||||||
|
var path = searchText(item.path || item.parentTitle || item.workspaceName || currentWorkspaceName());
|
||||||
|
return '<button type="button" class="wolai-search-result-row" data-testid="wolai-search-result-row" data-search-result-owner="rust-kernel" data-document-id="' + escapeHtml(item.documentId || item.nodeId || item.id || '') + '">' +
|
||||||
|
'<span class="material-symbols-outlined wolai-search-result-icon" data-icon="article" aria-hidden="true"></span>' +
|
||||||
|
'<span class="wolai-search-result-main"><span class="wolai-search-result-title">' + highlightSearchTitle(title, query) + '</span>' +
|
||||||
|
'<span class="wolai-search-result-snippet">' + (snippet ? highlightedHtml(snippet) : escapeHtml(path)) + '</span></span>' +
|
||||||
|
'</button>';
|
||||||
|
}).join('');
|
||||||
|
} catch (error) {
|
||||||
|
if (requestId !== activeSearchRequestId) return;
|
||||||
|
meta.innerHTML = '<span>共 0 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
||||||
|
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">没有搜索结果</div>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSearchModalOpen() {
|
function isSearchModalOpen() {
|
||||||
@@ -768,8 +827,9 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
var overlay = ensureSearchModal();
|
var overlay = ensureSearchModal();
|
||||||
overlay.hidden = false;
|
overlay.hidden = false;
|
||||||
document.documentElement.setAttribute('data-mnote-search-modal-open', 'true');
|
document.documentElement.setAttribute('data-mnote-search-modal-open', 'true');
|
||||||
renderSearchResults();
|
|
||||||
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
||||||
|
if (input) input.setAttribute('placeholder', '在 ' + currentWorkspaceName() + ' 中搜索');
|
||||||
|
void renderSearchResults();
|
||||||
if (input) {
|
if (input) {
|
||||||
setTimeout(function() { input.focus(); input.select(); }, 0);
|
setTimeout(function() { input.focus(); input.select(); }, 0);
|
||||||
}
|
}
|
||||||
@@ -793,7 +853,12 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
var searchTrigger = closestAction(e.target, '[data-mnote-action="open-search-modal"]');
|
var searchTrigger = closestAction(e.target, '[data-mnote-action="open-search-modal"]');
|
||||||
if (searchTrigger) {
|
if (searchTrigger) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openSearchModal();
|
toggleSearchModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var searchDialog = closestAction(e.target, '.wolai-search-dialog');
|
||||||
|
if (isSearchModalOpen() && !searchDialog) {
|
||||||
|
closeSearchModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1305,7 +1370,7 @@ pub fn PageLayout(
|
|||||||
<aside class="mnote-sidebar wolai-sidebar" data-testid="wolai-sidebar">
|
<aside class="mnote-sidebar wolai-sidebar" data-testid="wolai-sidebar">
|
||||||
<div class="mnote-sidebar-header wolai-sidebar-header" data-testid="wolai-workspace-identity">
|
<div class="mnote-sidebar-header wolai-sidebar-header" data-testid="wolai-workspace-identity">
|
||||||
<a href="/" class="mnote-sidebar-brand wolai-avatar" aria-label="工作区首页">"L"</a>
|
<a href="/" class="mnote-sidebar-brand wolai-avatar" aria-label="工作区首页">"L"</a>
|
||||||
<span class="sidebar-workspace-name">{ws_name}</span>
|
<span class="sidebar-workspace-name">{ws_name.clone()}</span>
|
||||||
<span class="wolai-sidebar-chevron" aria-hidden="true">"⌄"</span>
|
<span class="wolai-sidebar-chevron" aria-hidden="true">"⌄"</span>
|
||||||
</div>
|
</div>
|
||||||
<nav class="mnote-sidebar-nav wolai-quick-actions" aria-label="快捷操作">
|
<nav class="mnote-sidebar-nav wolai-quick-actions" aria-label="快捷操作">
|
||||||
@@ -1326,17 +1391,19 @@ pub fn PageLayout(
|
|||||||
<div class="wolai-topbar-left">
|
<div class="wolai-topbar-left">
|
||||||
<button type="button" class="wolai-icon-button wolai-menu-button" aria-label="切换侧栏"><span class="material-symbols-outlined" data-icon="menu" aria-hidden="true"></span></button>
|
<button type="button" class="wolai-icon-button wolai-menu-button" aria-label="切换侧栏"><span class="material-symbols-outlined" data-icon="menu" aria-hidden="true"></span></button>
|
||||||
<nav class="wolai-breadcrumb" aria-label="页面路径">
|
<nav class="wolai-breadcrumb" aria-label="页面路径">
|
||||||
<span class="wolai-breadcrumb-root">"The Digital Atelier"</span>
|
<span class="wolai-breadcrumb-root">{ws_name.clone()}</span>
|
||||||
<span class="wolai-breadcrumb-separator" aria-hidden="true">"/"</span>
|
<span class="wolai-breadcrumb-separator" aria-hidden="true">"›"</span>
|
||||||
<span class="wolai-breadcrumb-current"><span class="material-symbols-outlined wolai-home-icon" data-icon="home" aria-hidden="true"></span><span data-page-title-current="true">{topbar_title}</span></span>
|
<span class="wolai-breadcrumb-current"><span class="material-symbols-outlined wolai-home-icon" data-icon="home" aria-hidden="true"></span><span data-page-title-current="true">{topbar_title}</span></span>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="wolai-topbar-actions" aria-label="页面操作">
|
<div class="wolai-topbar-actions" aria-label="页面操作">
|
||||||
<span class="wolai-public-pill"><span aria-hidden="true">"●"</span>"Public"</span>
|
<span class="wolai-public-pill" data-testid="wolai-public-state">"全网公开"</span>
|
||||||
<button type="button" class="wolai-icon-button" title="收藏" aria-label="收藏"><span class="material-symbols-outlined" data-icon="star" aria-hidden="true"></span></button>
|
<button type="button" class="wolai-icon-button" title="收藏" aria-label="收藏"><span class="material-symbols-outlined" data-icon="star" aria-hidden="true"></span></button>
|
||||||
|
<button type="button" class="wolai-icon-button" title="演示" aria-label="演示"><span class="material-symbols-outlined" data-icon="slideshow" aria-hidden="true"></span></button>
|
||||||
|
<button type="button" class="wolai-icon-button" title="评论" aria-label="评论"><span class="material-symbols-outlined" data-icon="comment" aria-hidden="true"></span></button>
|
||||||
|
<button type="button" class="wolai-icon-button" title="分享" aria-label="分享"><span class="material-symbols-outlined" data-icon="share" aria-hidden="true"></span></button>
|
||||||
|
<button type="button" class="wolai-icon-button" title="成员" aria-label="成员"><span class="material-symbols-outlined" data-icon="person_add" aria-hidden="true"></span></button>
|
||||||
<button type="button" class="wolai-icon-button" title="历史" aria-label="历史"><span class="material-symbols-outlined" data-icon="history" aria-hidden="true"></span></button>
|
<button type="button" class="wolai-icon-button" title="历史" aria-label="历史"><span class="material-symbols-outlined" data-icon="history" aria-hidden="true"></span></button>
|
||||||
<button type="button" class="wolai-icon-button wolai-ai-inline" title="AI" aria-label="AI"><span class="material-symbols-outlined material-symbols-filled" data-icon="auto_awesome" aria-hidden="true"></span></button>
|
|
||||||
<button type="button" class="wolai-icon-button" title="搜索" aria-label="搜索" data-mnote-action="open-search-modal"><span class="material-symbols-outlined" data-icon="search" aria-hidden="true"></span></button>
|
|
||||||
<button type="button" class="wolai-icon-button" title="更多" aria-label="更多"><span class="material-symbols-outlined" data-icon="more_horiz" aria-hidden="true"></span></button>
|
<button type="button" class="wolai-icon-button" title="更多" aria-label="更多"><span class="material-symbols-outlined" data-icon="more_horiz" aria-hidden="true"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -170,6 +170,11 @@ a:hover {
|
|||||||
.material-symbols-outlined[data-icon="right_panel_open"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 5h16v14H4zM14 5v14M8 12h6M11 9l3 3-3 3' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
|
.material-symbols-outlined[data-icon="right_panel_open"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 5h16v14H4zM14 5v14M8 12h6M11 9l3 3-3 3' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
|
||||||
.material-symbols-outlined[data-icon="open_in_new"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 6H5v13h13v-3M12 5h7v7M10 14 19 5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
|
.material-symbols-outlined[data-icon="open_in_new"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 6H5v13h13v-3M12 5h7v7M10 14 19 5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
|
||||||
.material-symbols-outlined[data-icon="share"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='18' cy='5' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Ccircle cx='6' cy='12' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Ccircle cx='18' cy='19' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m8.7 10.7 6.6-4.4M8.7 13.3l6.6 4.4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
|
.material-symbols-outlined[data-icon="share"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='18' cy='5' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Ccircle cx='6' cy='12' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Ccircle cx='18' cy='19' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m8.7 10.7 6.6-4.4M8.7 13.3l6.6 4.4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
|
||||||
|
|
||||||
|
.material-symbols-outlined[data-icon="comment"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 5h14v10H9l-4 4V5Z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
|
||||||
|
.material-symbols-outlined[data-icon="person_add"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='9' cy='8' r='3.2' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M3.5 20a5.5 5.5 0 0 1 11 0M18 8v6M15 11h6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
|
||||||
|
.material-symbols-outlined[data-icon="slideshow"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 5h16v12H4z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='m10 9 5 2-5 2zM9 21h6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
|
||||||
|
.material-symbols-outlined[data-icon="article"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 4h12v16H6zM9 8h6M9 12h6M9 16h4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
|
||||||
.material-symbols-outlined[data-icon="link"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.5 14.5 14.5 9.5M10.8 6.2l1.1-1.1a4 4 0 0 1 5.7 5.7l-1.6 1.6M13.2 17.8l-1.1 1.1a4 4 0 0 1-5.7-5.7L8 11.6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
|
.material-symbols-outlined[data-icon="link"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.5 14.5 14.5 9.5M10.8 6.2l1.1-1.1a4 4 0 0 1 5.7 5.7l-1.6 1.6M13.2 17.8l-1.1 1.1a4 4 0 0 1-5.7-5.7L8 11.6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
|
||||||
.material-symbols-outlined[data-icon="content_copy"],
|
.material-symbols-outlined[data-icon="content_copy"],
|
||||||
.material-symbols-outlined[data-icon="file_copy"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 8h10v12H8zM6 16H4V4h10v2' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
|
.material-symbols-outlined[data-icon="file_copy"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 8h10v12H8zM6 16H4V4h10v2' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
|
||||||
@@ -912,15 +917,17 @@ a:hover {
|
|||||||
/* ===== 响应式 ===== */
|
/* ===== 响应式 ===== */
|
||||||
|
|
||||||
|
|
||||||
.wolai-search-overlay{position:fixed;inset:0;z-index:1200;display:flex;align-items:flex-start;justify-content:center;padding:92px 24px 24px;background:rgba(0,0,0,.32)}
|
.wolai-search-overlay{position:fixed;inset:0;z-index:1200;display:flex;align-items:flex-start;justify-content:center;padding:92px 24px 24px;background:rgba(0,0,0,.32);pointer-events:none}
|
||||||
.wolai-search-overlay[hidden]{display:none!important}
|
.wolai-search-overlay[hidden]{display:none!important}
|
||||||
.wolai-search-dialog{width:min(680px,100%);max-height:min(720px,calc(100vh - 128px));overflow:hidden;display:flex;flex-direction:column;background:#FFF;border:1px solid rgba(27,28,28,.10);border-radius:6px;box-shadow:0 18px 48px rgba(15,23,42,.22),0 2px 8px rgba(15,23,42,.08);color:#37352F}
|
.wolai-search-dialog{width:min(680px,100%);max-height:min(720px,calc(100vh - 128px));overflow:hidden;display:flex;flex-direction:column;background:#FFF;border:1px solid rgba(27,28,28,.10);border-radius:6px;box-shadow:0 18px 48px rgba(15,23,42,.22),0 2px 8px rgba(15,23,42,.08);color:#37352F;pointer-events:auto}
|
||||||
.wolai-search-input-row{display:flex;align-items:center;min-height:58px;padding:0 12px 0 18px;border-bottom:1px solid rgba(27,28,28,.08)}
|
.wolai-search-input-row{display:flex;align-items:center;min-height:58px;padding:0 12px 0 18px;border-bottom:1px solid rgba(27,28,28,.08)}
|
||||||
.wolai-search-input-icon{width:22px;height:22px;color:#8B8780;margin-right:10px}
|
.wolai-search-input-icon{width:22px;height:22px;color:#8B8780;margin-right:10px}
|
||||||
.wolai-search-input{flex:1 1 auto;min-width:0;height:56px;border:0;outline:none;background:transparent;color:#24211D;font:400 18px/1.4 var(--wolai-font-sans);letter-spacing:0}
|
.wolai-search-input{flex:1 1 auto;min-width:0;height:56px;border:0;outline:none;background:transparent;color:#24211D;font:400 18px/1.4 var(--wolai-font-sans);letter-spacing:0}
|
||||||
.wolai-search-input::placeholder{color:#A29E97}
|
.wolai-search-input::placeholder{color:#A29E97}
|
||||||
|
.wolai-search-input::-webkit-search-cancel-button{appearance:none;-webkit-appearance:none;display:none}
|
||||||
.wolai-search-close{width:28px;height:28px;border:0;border-radius:4px;background:transparent;color:#8B8780;cursor:pointer;font-size:20px;line-height:1}
|
.wolai-search-close{width:28px;height:28px;border:0;border-radius:4px;background:transparent;color:#8B8780;cursor:pointer;font-size:20px;line-height:1}
|
||||||
.wolai-search-options{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px 8px;border-bottom:1px solid rgba(27,28,28,.06);color:#A7A39D;font-size:12px;line-height:1}
|
.wolai-search-options{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px 8px;border-bottom:1px solid rgba(27,28,28,.06);color:#A7A39D;font-size:12px;line-height:1}
|
||||||
|
.wolai-search-options[hidden]{display:none!important}
|
||||||
.wolai-search-options-left,.wolai-search-options-right,.wolai-search-switch-control,.wolai-search-sort-control{display:inline-flex;align-items:center}
|
.wolai-search-options-left,.wolai-search-options-right,.wolai-search-switch-control,.wolai-search-sort-control{display:inline-flex;align-items:center}
|
||||||
.wolai-search-options-left{min-width:0;flex-wrap:wrap;gap:8px 12px}
|
.wolai-search-options-left{min-width:0;flex-wrap:wrap;gap:8px 12px}
|
||||||
.wolai-search-options-right{margin-left:auto;flex:0 0 auto}
|
.wolai-search-options-right{margin-left:auto;flex:0 0 auto}
|
||||||
@@ -939,9 +946,9 @@ a:hover {
|
|||||||
.wolai-search-result-icon{width:18px;height:18px;margin-top:2px;color:#8B8780}
|
.wolai-search-result-icon{width:18px;height:18px;margin-top:2px;color:#8B8780}
|
||||||
.wolai-search-result-main{min-width:0;display:flex;flex-direction:column;gap:3px}
|
.wolai-search-result-main{min-width:0;display:flex;flex-direction:column;gap:3px}
|
||||||
.wolai-search-result-title{color:#2F2D29;font-size:14px;line-height:1.35;word-break:break-word}
|
.wolai-search-result-title{color:#2F2D29;font-size:14px;line-height:1.35;word-break:break-word}
|
||||||
.wolai-search-result-title mark{padding:0 1px;border-radius:2px;background:#FFE9E6;color:#D83A32}
|
.wolai-search-result-title mark,.wolai-search-result-snippet mark{padding:0 1px;border-radius:2px;background:#FFE9E6;color:#D83A32}
|
||||||
.wolai-search-result-snippet,.wolai-search-empty{color:#8B8780;font-size:12px;line-height:1.35}
|
.wolai-search-result-snippet,.wolai-search-empty{color:#8B8780;font-size:12px;line-height:1.35}
|
||||||
.wolai-search-empty{padding:28px 12px 32px;text-align:center}
|
.wolai-search-empty,.wolai-search-recent{padding:28px 12px 32px;text-align:center;color:#8B8780;font-size:12px;line-height:1.35}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.mnote-sidebar {
|
.mnote-sidebar {
|
||||||
@@ -963,6 +970,7 @@ a:hover {
|
|||||||
|
|
||||||
.document-shell-header h1 {
|
.document-shell-header h1 {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mnote-editor-island {
|
#mnote-editor-island {
|
||||||
@@ -1555,7 +1563,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wolai-topbar {
|
.wolai-topbar {
|
||||||
height: 44px;
|
height: 40px;
|
||||||
padding: 0 20px 0 22px;
|
padding: 0 20px 0 22px;
|
||||||
background: rgba(255, 255, 255, 0.92);
|
background: rgba(255, 255, 255, 0.92);
|
||||||
color: #5A5A5A;
|
color: #5A5A5A;
|
||||||
@@ -1626,22 +1634,17 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wolai-public-pill {
|
.wolai-public-pill {
|
||||||
height: 26px;
|
height: 22px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 0 11px;
|
padding: 0 8px;
|
||||||
border: 1px solid #D6F2DC;
|
border: 0;
|
||||||
border-radius: 999px;
|
border-radius: 3px;
|
||||||
background: #ECFCEF;
|
background: #ECFCEF;
|
||||||
color: #087B31;
|
color: #087B31;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
}
|
|
||||||
|
|
||||||
.wolai-public-pill span {
|
|
||||||
color: var(--atelier-primary-container);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wolai-ai-inline {
|
.wolai-ai-inline {
|
||||||
@@ -1649,25 +1652,21 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.document-shell {
|
.document-shell {
|
||||||
max-width: 880px;
|
box-sizing: border-box;
|
||||||
min-height: calc(100vh - 44px);
|
width: min(100%, 760px);
|
||||||
padding: 78px 48px 160px;
|
min-height: calc(100vh - 40px);
|
||||||
|
padding: 68px 0 160px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-shell-header {
|
.document-shell-header {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0 28px;
|
margin: 0 0 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-page-icon {
|
.document-page-icon {
|
||||||
height: 82px;
|
display: none;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #111111;
|
|
||||||
line-height: 1;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mnote-material-page-icon {
|
.mnote-material-page-icon {
|
||||||
@@ -1683,18 +1682,18 @@ body {
|
|||||||
.document-title-heading,
|
.document-title-heading,
|
||||||
.document-shell-header h1 {
|
.document-shell-header h1 {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0 14px;
|
margin: 0;
|
||||||
color: var(--atelier-text);
|
color: var(--atelier-text);
|
||||||
font-size: 42px;
|
font-size: 34px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
line-height: 1.12;
|
line-height: 44px;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-title-input {
|
.document-title-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 52px;
|
min-height: 44px;
|
||||||
display: block;
|
display: block;
|
||||||
resize: none;
|
resize: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -1719,15 +1718,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.document-shell-meta {
|
.document-shell-meta {
|
||||||
display: flex;
|
display: none;
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 18px;
|
|
||||||
padding-bottom: 28px;
|
|
||||||
border-bottom: 1px solid #F0EFED;
|
|
||||||
color: #A19D97;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-shell-meta span {
|
.document-shell-meta span {
|
||||||
@@ -1759,7 +1750,7 @@ body {
|
|||||||
.ProseMirror {
|
.ProseMirror {
|
||||||
color: var(--atelier-text);
|
color: var(--atelier-text);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.65;
|
line-height: 24px;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1789,7 +1780,33 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#mnote-leptos-tiptap-island-editor-root .ProseMirror p {
|
#mnote-leptos-tiptap-island-editor-root .ProseMirror p {
|
||||||
margin: 0 0 24px;
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mnote-leptos-tiptap-island-editor-root .ProseMirror ul,
|
||||||
|
#mnote-leptos-tiptap-island-editor-root .ProseMirror ol {
|
||||||
|
margin: 4px 0 8px;
|
||||||
|
padding-left: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mnote-leptos-tiptap-island-editor-root .ProseMirror li {
|
||||||
|
margin: 2px 0;
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mnote-leptos-tiptap-island-editor-root .ProseMirror blockquote {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 2px 0 2px 14px;
|
||||||
|
border-left: 3px solid #D9D6D0;
|
||||||
|
color: #5F5B56;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mnote-leptos-tiptap-island-editor-root .ProseMirror input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0 8px 0 0;
|
||||||
|
vertical-align: -3px;
|
||||||
|
accent-color: #2EA44F;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mnote-leptos-tiptap-island-editor-root .ProseMirror h1,
|
#mnote-leptos-tiptap-island-editor-root .ProseMirror h1,
|
||||||
@@ -1823,8 +1840,9 @@ body {
|
|||||||
|
|
||||||
.wolai-floating-actions {
|
.wolai-floating-actions {
|
||||||
right: 24px;
|
right: 24px;
|
||||||
bottom: 24px;
|
bottom: 44px;
|
||||||
gap: 10px;
|
flex-direction: column-reverse;
|
||||||
|
gap: 16px;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1848,13 +1866,13 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wolai-floating-button--ai {
|
.wolai-floating-button--ai {
|
||||||
width: 52px;
|
width: 40px;
|
||||||
height: 52px;
|
height: 40px;
|
||||||
border: 0;
|
border: 1px solid rgba(27, 28, 28, 0.1);
|
||||||
background: linear-gradient(135deg, var(--atelier-primary), var(--atelier-primary-container));
|
background: #FFFFFF;
|
||||||
color: #FFFFFF;
|
color: #5A5A5A;
|
||||||
box-shadow: 0 16px 32px rgba(0, 110, 40, 0.22);
|
box-shadow: 0 10px 24px rgba(27, 28, 28, 0.08);
|
||||||
font-size: 23px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -1874,25 +1892,13 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.document-shell {
|
.document-shell {
|
||||||
|
width: 100%;
|
||||||
padding: 48px 28px 120px;
|
padding: 48px 28px 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-page-icon {
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mnote-material-page-icon {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
font-size: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mnote-material-page-icon::before {
|
|
||||||
font-size: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.document-shell-header h1 {
|
.document-shell-header h1 {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mnote-leptos-tiptap-island-editor-root .block-handle-shell {
|
#mnote-leptos-tiptap-island-editor-root .block-handle-shell {
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ async function main() {
|
|||||||
assert(floatingHelpBox.x > sidebarBox.width, "帮助浮动按钮不应落入左侧栏");
|
assert(floatingHelpBox.x > sidebarBox.width, "帮助浮动按钮不应落入左侧栏");
|
||||||
assert(!boxesIntersect(floatingAiBox, primaryContentBox), "AI 浮动按钮不应遮挡主内容");
|
assert(!boxesIntersect(floatingAiBox, primaryContentBox), "AI 浮动按钮不应遮挡主内容");
|
||||||
assert(!boxesIntersect(floatingHelpBox, primaryContentBox), "帮助浮动按钮不应遮挡主内容");
|
assert(!boxesIntersect(floatingHelpBox, primaryContentBox), "帮助浮动按钮不应遮挡主内容");
|
||||||
assert(floatingHelpBox.y < floatingAiBox.y, "帮助按钮应位于 AI 按钮上方,匹配 Stitch 目标");
|
assert(floatingAiBox.y < floatingHelpBox.y, "AI 按钮应位于帮助按钮上方,匹配当前 Wolai 基线");
|
||||||
|
assert(Math.abs((floatingHelpBox.y - (floatingAiBox.y + floatingAiBox.height)) - 16) <= 2, "AI/帮助按钮间距应接近 Wolai 16px");
|
||||||
|
assert(Math.abs((960 - (floatingHelpBox.y + floatingHelpBox.height)) - 44) <= 4, "帮助按钮底部留白应接近 Wolai 44px");
|
||||||
|
|
||||||
assert.equal(await page.locator('[data-mnote-sidebar-tree-tab="filetree"]').count(), 1, "Explorer 只能作为一个切换 tab 出现");
|
assert.equal(await page.locator('[data-mnote-sidebar-tree-tab="filetree"]').count(), 1, "Explorer 只能作为一个切换 tab 出现");
|
||||||
assert.equal(await page.locator('[data-testid="wolai-sidebar-file-tree-section"]').count(), 0, "文件树不应再渲染成第二个独立 Explorer section");
|
assert.equal(await page.locator('[data-testid="wolai-sidebar-file-tree-section"]').count(), 0, "文件树不应再渲染成第二个独立 Explorer section");
|
||||||
|
|||||||