diff --git a/.gitignore b/.gitignore index 6a33fcee..890bdd12 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ design/05-editor-mainline/reference-code # MNote 本地运行索引与一次性浏览器截图 /.mnote/ +/ai-sessions/ /filetree-*.png /task*-*.png diff --git a/AGENTS.md b/AGENTS.md index 9f774e28..288a3daf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,8 +72,9 @@ - 页面 AI 编辑当前 local-first 主路径为:页面定位到真实 `.md` 文件,MNote 计算 `AiAccessScope` / allowed roots / selection,Hermes 或 Reasonix 在白名单目录内用自身 patch/diff/文件编辑能力写入,MNote 通过 watcher / refresh 同步 tiptap。`mnote.doc.markdown_edit` 只作为 cloud / remote agent / compat fallback;`mnote.block.*` 保留为结构性辅助(拖拽排序等)。两层操作模型已获 CLI Main(Lark Doc)参考实现验证。流式 apply + suggest/review(参考 BlockNote AI)作为 Phase C 设计冻结,当前不实施。 - 需要架构判断时,优先参考: - `/mnt/Data1T/mnote/ARCHITECTURE.md` - - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` - - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md` + - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md` + - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md` + - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/reference/1-tree-first-graph-kernel-v1.md` - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md` - `/mnt/Data1T/mnote/design/05-editor-mainline/reference/5-5-page-aggregate-single-truth-alignment-v1.md` - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-6-page-aggregate-alignment-checklist-v1.md` diff --git a/bugs/03-rust-web/done/3-26-sidebar-dev-hot-reload-setinterval-v1.md b/bugs/03-rust-web/done/3-26-sidebar-dev-hot-reload-setinterval-v1.md new file mode 100644 index 00000000..5417ad64 --- /dev/null +++ b/bugs/03-rust-web/done/3-26-sidebar-dev-hot-reload-setinterval-v1.md @@ -0,0 +1,46 @@ +# 3-26 Sidebar dev hot reload 在主 runtime 中使用 setInterval + +## 状态 + +- 状态:done +- Owner:03-rust-web / browser runtime +- 发现时间:2026-05-31 + +## 现象 + +`sidebar-tree-runtime.js` 在主 Sidebar runtime 中安装 dev hot reload,并无前置环境 guard 地执行 `window.setInterval(tick, 1000)`。项目架构约束要求浏览器主链默认禁止新增基于 `setInterval`、周期 `setTimeout` 或轮询 fallback 的数据刷新和状态同步;即使这是 dev hot reload,也应该显式限制在 debug/dev 边界。 + +## 证据 + +- `rust/crates/mnote-web/browser/sidebar-tree-runtime.js` 中 `installMnoteDevHotReload()` 执行: + - 先调用 `tick()`。 + - 再执行 `timer = window.setInterval(tick, 1000)`。 + - 末尾无条件调用 `installMnoteDevHotReload()`。 +- `tick()` 每次请求 `/api/dev/hot-reload`,服务端返回 `enabled !== true` 时才清理 timer;这仍意味着页面启动后先进入轮询逻辑。 + +## 影响 + +- 主 Sidebar runtime 默认携带 dev 轮询逻辑,和“主链不叠加轮询 fallback”的架构约束冲突。 +- 若 `/api/dev/hot-reload` 异常或返回形态变化,可能引入重复请求、console 噪音或性能误判。 +- 后续 worker 可能把这种模式复制到其他可见路径。 + +## 修复建议 + +- 在 Rust SSR bootstrap 中显式注入 dev/hot 模式标记,只有 dev hot 模式启用时才安装该逻辑。 +- 或将 hot reload 逻辑迁到 debug/internal runtime,不放在默认 Sidebar runtime。 +- 如果仍保留定时器,必须在设计/注释中说明退出条件和边界,并保证生产/普通 desktop 模式不可达。 + +## 本轮进展 + +- 2026-05-31: + - `sidebar-tree-runtime.js` 已改为通过 `import.meta.url` 检查当前模块 URL 是否携带 `devHot` cache buster;只有 dev hot 模式才安装 hot reload 轮询。 + - 已补 `layout.rs` 静态断言,禁止无条件 `installMnoteDevHotReload()` 回流。 + - 已通过 `node --check rust/crates/mnote-web/browser/sidebar-tree-runtime.js` 和 Rust 定点测试。 + - 已补 `scripts/task514-sidebar-dev-hot-reload-gating-smoke.js` 固化真实浏览器验证。 + - 验证通过:`node scripts/task514-sidebar-dev-hot-reload-gating-smoke.js`。结果显示普通入口 `hotReloadRequestCount=0`、`scriptHasDevHot=false`;hot 模式 `scriptHasDevHot=true`、`hotReloadRequestCount=2`、`data-mnote-dev-hot-reload=enabled`,且无 console/network 错误。 + +## 验收 + +- [x] 普通 `desktop` / 非 hot 入口打开后,浏览器不会请求 `/api/dev/hot-reload`。 +- [x] `npm run desktop:hot` 或等价 hot 模式下仍能刷新。 +- [x] `setInterval(tick, 1000)` 仅在 `mnoteDevHotReloadEnabled()` guard 后可达。 diff --git a/bugs/03-rust-web/process/3-27-mineru-ocr-design-without-runtime-implementation-v1.md b/bugs/03-rust-web/process/3-27-mineru-ocr-design-without-runtime-implementation-v1.md new file mode 100644 index 00000000..54de1a19 --- /dev/null +++ b/bugs/03-rust-web/process/3-27-mineru-ocr-design-without-runtime-implementation-v1.md @@ -0,0 +1,103 @@ +# 3-27 MinerU OCR 后端 runtime 已落地但缺前端任务链 + +## 状态 + +- 状态:process +- Owner:03-rust-web / local-folder OCR / MinerU sidecar +- 发现时间:2026-05-31 + +## 现象 + +P2 目标中包含 MinerU OCR 能力收口。当前已补后端 mock route、真实 MinerU HTTP client、sidecar 写入、`.mnote/ocr-index.json`、`includeOcr=true` 搜索命中和浏览器 API smoke。但 active job store、realtime job event、前端入口/任务栏仍未落地。 + +## 证据 + +- `rust/crates/mnote-web/src/routes/local_ocr.rs` 已注册 `jobs/status/read/insert`,`provider=mock` 能写入 `{pageStem}.ocr/*.ocr.md` 和 `.mnote/ocr-index.json`。 +- `provider=mineru` 在有 token 时会走真实 HTTP client:申请上传 URL、PUT 上传、轮询结果、下载 zip、提取 Markdown,并写入 `{pageStem}.ocr/*.ocr.md` / `.mnote/ocr-index.json`。该路径已有本地 HTTP mock 单测覆盖,不访问外网。 +- `local_search_index` 已支持 `includeOcr=true` 命中 OCR owner page;`task526-local-folder-ocr-api-smoke.js` 已覆盖浏览器上下文调用 mock OCR API、搜索命中和显式 insert 链接。 +- 设计中的活动任务生命周期、`local_ocr.job.updated` 和浏览器可见入口仍未完成。 + +## 影响 + +- 用户无法从本地图片或图片型 PDF 通过 UI 手动触发 MinerU OCR。 +- 搜索的 `includeOcr=true` 已能命中 OCR sidecar,但真实 MinerU 结果仍缺 UI 触发链和任务状态可见性。 +- AI 资源上下文无法优先读取已有 OCR Markdown。 + +## 下一步 + +1. 已完成后端 mock 最小闭环:路径规划、sidecar frontmatter、`.mnote/ocr-index.json`、mock OCR job route、status/read、Page Tree 过滤和 `includeOcr=true` 搜索 owner page。 +2. 已补浏览器 API smoke:mock OCR、status/read/jobs、`includeOcr=true` 搜索 owner page 和显式 insert link。 +3. 下一步接长任务状态、`local_ocr.job.updated` realtime 事件、前端 OCR 入口/任务栏和 AI 资源上下文读取 OCR sidecar。 + +## 验收 + +- [x] Rust route/helper 测试覆盖 sidecar 路径/frontmatter、index stale/error redaction、mock OCR job 写入、status/read、无 token、越界拒绝、非图片/PDF 拒绝、mock 失败 response 脱敏和显式 insert link。 +- [x] local search 测试覆盖 OCR 命中返回 owner page,OCR sidecar 不作为普通页面。 +- [x] Page Tree 测试覆盖 OCR sidecar 可在 File Tree 作为资源文件可见,但不作为普通页面进入 Page Tree。 +- [x] Rust route 测试覆盖无 token response 形态。 +- [x] Rust route 测试覆盖 `provider=mineru` 后端成功路径:申请上传 URL、PUT 上传、轮询结果、下载 zip、提取 Markdown、写 sidecar/index。 +- [~] 浏览器 smoke 覆盖图片/PDF 手动 OCR、任务栏状态、打开 OCR、搜索 OCR 命中和插入 OCR 链接。(当前 `task526` 覆盖 mock OCR API、搜索命中、显式 insert link 和 OCR sidecar resource tab 打开;真实 UI 入口/任务栏待做) + +## 2026-06-01 后端 mock 闭环记录 + +新增 `rust/crates/mnote-web/src/routes/local_ocr.rs`,注册: + +- `POST /api/local-folder/ocr/jobs` +- `GET /api/local-folder/ocr/jobs` +- `GET /api/local-folder/ocr/status` +- `GET /api/local-folder/ocr/read` +- `POST /api/local-folder/ocr/insert` + +当前真实 MinerU HTTP client 仍未接入;`provider=mock` 用于本地后端闭环和测试,`provider=mineru` 在缺少 token 时返回 `mineru_token_missing`,有 token 时返回 `mineru_runtime_not_enabled`,避免伪装真实能力已完成。 + +已通过验证: + +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_ocr -- --test-threads=1` + +2026-06-01 续补 route 边界测试: + +- 越界来源路径拒绝:`local_ocr_path_escape` +- 非图片/PDF 来源拒绝:`local_ocr_source_type_unsupported` +- provider `mineru` 且缺 token 时拒绝:`mineru_token_missing` +- mock 失败响应与 `.mnote/ocr-index.json` 失败条目统一脱敏为 `provider_error_redacted` +- 显式 `ocr/insert` link 模式把 `[OCR:photo.png](./Page.ocr/photo.png.ocr.md)` 追加进 owner Markdown;未调用 insert 时不改正文 + +本文继续保持 `process`:前端 OCR 入口、任务栏和 active job / realtime event 尚未完成。 + +2026-06-01 只读复核更新: + +- Subagent 复核确认:route/mock/search 测试已落地,早期“没有实际 OCR route”的描述已经过时。 +- 本 bug 不归档的阻塞点更新为:真实 MinerU HTTP client、active job store / `local_ocr.job.updated`、前端入口/任务栏、AI 资源上下文读取 OCR sidecar 和完整 UI browser smoke。 + +2026-06-01 浏览器 API smoke 续补: + +- 新增 `scripts/task526-local-folder-ocr-api-smoke.js`,在真实浏览器登录态页面内通过 `fetch` 串起 `POST /api/local-folder/ocr/jobs`、`status`、`read`、`jobs`、`/api/search/documents includeOcr=true` 和 `POST /api/local-folder/ocr/insert`。 +- 已验证 `provider=mock` 会生成 `docs/Page.ocr/photo.png.ocr.md`,`includeOcr=false` 不命中 OCR 文本,`includeOcr=true` 返回 owner page 且带 `hasOcr/ocrEvidence`,显式 insert 才向 owner Markdown 追加 `[OCR:photo.png](...)`。 +- 续补后已验证 OCR sidecar 可作为 Markdown resource tab 打开,截图写入 `tmp/task526-local-folder-ocr-api-smoke/02-ocr-resource-tab.png`。 +- 已通过: + - `node --check scripts/task526-local-folder-ocr-api-smoke.js` + - `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task526-local-folder-ocr-api-smoke.js` + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1` + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_ocr -- --test-threads=1` + +2026-06-01 MinerU API 口径复核: + +- 官方文档当前仍以 `https://mineru.net/api/v4/extract/task` 或 `https://mineru.net/api/v4/file-urls/batch` 作为精准解析入口;`model_version` 支持 `pipeline` / `vlm` / `MinerU-HTML`,Markdown/JSON 为默认结果格式,图片/PDF/Office 等文档受文件大小与页数限制。 +- 当前设计稿中的“申请上传地址 -> PUT 上传 -> 轮询批量结果 -> 下载 zip -> 提取 Markdown”方向仍成立。 +- 旧回归测试曾要求 `MNOTE_MINERU_API_TOKEN` 存在且 provider 为 `mineru` 时返回 `mineru_runtime_not_enabled`;该口径已被真实后端 runtime 合同测试替代。 + +## 2026-06-01 后端 MinerU runtime 补齐记录 + +- `rust/crates/mnote-web/src/routes/local_ocr.rs` 已补 `mineru_api_base_url`、`mineru_poll_interval`、`mineru_max_polls` 配置函数。 +- `provider=mineru` 后端路径已接入 `run_mineru_ocr`,执行申请上传 URL、PUT 上传、轮询 batch 结果、下载 zip、提取 Markdown。 +- 新增本地 HTTP mock 测试 `local_ocr_jobs_route_runs_mineru_runtime_against_http_mock`,不访问外网,验证真实 client 合同和 sidecar/index 写入。 +- 已通过: + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1` + +本文继续保持 `process` 的剩余原因: + +- 前端本地文件 / 图片 / PDF 资源没有 OCR 操作入口。 +- OCR jobs 仍是同步请求 + `.mnote/ocr-index.json` 历史状态,没有 active job store。 +- 缺少 `local_ocr.job.updated` realtime event 与全局任务栏 UI。 +- AI 资源上下文还没有优先读取 OCR sidecar。 diff --git a/bugs/04-tree-domain/done/4-52-resource-lifecycle-retired-bridge-artifact-regression-v1.md b/bugs/04-tree-domain/done/4-52-resource-lifecycle-retired-bridge-artifact-regression-v1.md new file mode 100644 index 00000000..22847778 --- /dev/null +++ b/bugs/04-tree-domain/done/4-52-resource-lifecycle-retired-bridge-artifact-regression-v1.md @@ -0,0 +1,48 @@ +# 4-52 resource lifecycle 兼容路径在 Convex 退役后丢失 artifact / 拒绝 mindmap lifecycle + +## 状态 + +- 状态:done +- Owner:04-tree-domain / bridge-runtime / resource lifecycle command +- 发现时间:2026-06-01 +- 修复时间:2026-06-01 + +## 现象 + +执行 `cargo test -p mnote-web mindmap -- --test-threads=1` 时,mindmap 相关宽测曾失败: + +- `mindmap_put_derives_workspace_and_returns_tree_artifacts` +- `mindmap_command_apply_returns_object_artifacts_without_tree_resync` +- `mindmap_delete_restore_keeps_markdown_reference_out_of_lifecycle_command` +- `mindmap_trash_routes_delete_restore_purge_and_empty` +- `sidebar_runtime_routes_local_mindmap_and_office_assets` + +前两个失败是 `execute_retired_command_plan_with_artifacts()` 在 Convex 退役后仍能执行 fixture mutation,但把 Rust artifact plan 置为 `None`,导致调用方拿不到 `commandLog` / `domainEvent`。 + +后两个失败是 `tree.resource.archive/restore/purge/rename` 在 bridge-runtime 生成 resource-kind 专用函数名前,会先经过 `build_write_request()`;旧 `legacy_command_function_name()` 没有这些正式 tree resource command 名,导致 mindmap lifecycle 被 `retired_bridge_error()` 拒绝。 + +最后一个失败是 runtime 拆分后,OnlyOffice local open 逻辑已迁到 `sidebar-filetree-open-runtime.js`,但 Rust include 断言仍检查旧 `sidebar-tree-runtime.js` 字符串。 + +## 根因 + +- Convex 退役后,artifact 持久化不能再作为兼容路径成功条件;Rust artifact plan 应继续返回给调用方和 realtime consumer。 +- resource lifecycle command 的正式命名已从表面 compat alias 收口到 `tree.resource.*`,但旧 Convex 兼容映射表未允许这些名称通过前置 request 构造。 +- browser runtime 模块拆分后,测试断言没有同步到新的 canonical owner 文件。 + +## 修复 + +- `rust/crates/mnote-web/src/transport/convex.rs` + - `persist_runtime_command_artifacts()` 在 Convex 退役路径改为 no-op 成功。 + - `execute_retired_command_plan_with_artifacts()` 重新构造并返回 `RuntimeCommandArtifactPlan`,不重新启用 Convex artifact 持久化。 +- `rust/crates/bridge-runtime/src/lib.rs` + - `legacy_command_function_name()` 允许 `tree.resource.archive/restore/purge/rename` 通过前置 request 构造;最终函数名仍由 resource lifecycle plan 按资源类型分流。 +- `rust/crates/mnote-web/src/ssr/pages/layout.rs` + - OnlyOffice local open 断言改查 `SIDEBAR_FILETREE_OPEN_RUNTIME_JS`。 +- `rust/crates/mnote-web/src/routes/resource_trash.rs` + - `delete_json()` test helper 在状态码失败时输出 body,便于后续定位真实错误码。 + +## 验证 + +- `cargo test -p mnote-web mindmap -- --test-threads=1` + +结果:26 个 mindmap 相关测试全部通过。 diff --git a/bugs/04-tree-domain/done/4-53-workspace-object-identity-matrix-gap-v1.md b/bugs/04-tree-domain/done/4-53-workspace-object-identity-matrix-gap-v1.md new file mode 100644 index 00000000..f781137f --- /dev/null +++ b/bugs/04-tree-domain/done/4-53-workspace-object-identity-matrix-gap-v1.md @@ -0,0 +1,33 @@ +# 4-53 WorkspacePath / ObjectIdentity 浏览器矩阵缺口 + +## 状态 + +- 状态:done +- Owner:04-tree-domain / 05-editor-mainline runtime identity +- 发现时间:2026-06-01 +- 修复时间:2026-06-01 + +## 现象 + +P1 `WorkspacePath / ObjectIdentity` 已有底层协议与部分 smoke,但缺少一个单独覆盖 page `.md`、directory、raw file、mindmap、OnlyOffice 的浏览器矩阵。FileTree row、Resource Tab、OpenEditorsSnapshot、URL `resourceTab` 和 active tab identity 如果各自拼身份,后续 raw resource / mindmap / Office target 容易继续退化为“当前页”语义。 + +## 根因 + +已有 smoke 分散覆盖 copy-id、resource tab、mindmap 和 Office,但没有把统一 `ObjectWorkspacePath` 当作运行时消费合同统一断言。local folder 目录行还缺少 `local-dir:*` 的稳定 `documentId` fallback,resource tab 在部分本地资源打开路径下也缺少 `sourceKind` / `rootUri` fallback。 + +## 修复 + +- `rust/crates/mnote-web/browser/filetree-runtime.js` + - `readWorkspacePathFromRow()` 为 folder / index 行合成 `local-dir:{relativePath}`。 + - `fileTreeRowDocumentId()` 允许读取 `data-owner-document-id`,用于资源行 owner page 对齐。 +- `rust/crates/mnote-web/browser/document-resource-tab-runtime.js` + - 在 URL 参数缺失时,从 body dataset 或 FileTree row workspacePath 兜底读取 `sourceKind` / `rootUri`。 +- `scripts/task524-workspace-object-identity-matrix-smoke.js` + - 新增 browser smoke,覆盖 page `.md`、directory、raw text、mindmap、OnlyOffice 的 FileTree row workspacePath、OpenEditorsSnapshot、URL `resourceTab`、active tab identity 对齐。 + +## 验收 + +- [x] `node --check rust/crates/mnote-web/browser/document-resource-tab-runtime.js` +- [x] `node --check rust/crates/mnote-web/browser/filetree-runtime.js` +- [x] `node --check scripts/task524-workspace-object-identity-matrix-smoke.js` +- [x] `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task524-workspace-object-identity-matrix-smoke.js` diff --git a/bugs/05-editor-mainline/process/2026-05-21-office-edit-sign-plugin-gap-review-v1.md b/bugs/05-editor-mainline/done/2026-05-21-office-edit-sign-plugin-gap-review-v1.md similarity index 74% rename from bugs/05-editor-mainline/process/2026-05-21-office-edit-sign-plugin-gap-review-v1.md rename to bugs/05-editor-mainline/done/2026-05-21-office-edit-sign-plugin-gap-review-v1.md index 8ffad923..c4993f6a 100644 --- a/bugs/05-editor-mainline/process/2026-05-21-office-edit-sign-plugin-gap-review-v1.md +++ b/bugs/05-editor-mainline/done/2026-05-21-office-edit-sign-plugin-gap-review-v1.md @@ -1,5 +1,9 @@ # Office local-first 预览、编辑与插件噪音缺口审查 v1 +## 状态 + +- 状态:done + ## 背景 本轮只审查 OnlyOffice 与 MNote local-first 工作区之间的三个接口缺口: @@ -24,9 +28,9 @@ Context7 查询 `/onlyoffice/api.onlyoffice.com` 得到的关键口径: - `rust/crates/mnote-web/src/routes/media.rs` 的 `/api/media/sign` 当前只服务 Convex media asset,内部查询 `mediaAssets:getById` / `mediaAssets:refreshUrl`。local-folder asset 形如 `local:asset:`,不在 Convex media 表中,所以返回 404 是现状契约不匹配。 - `rust/crates/mnote-web/src/routes/onlyoffice.rs` 的 `/onlyoffice` 页面在 `resolveAssetUrlAndKey()` 中只要有 `assetId` 就请求 `/api/media/sign`;失败后静默保留传入的 `fileUrl`。这就是“当前 view 模式不阻断打开”的原因。 - local-folder Office 预览已经可以直接使用 `/api/local-folder/files/open?rootUri=...&path=...` 作为 `fileUrl`,不需要经过 `/api/media/sign`。 -- `/api/onlyoffice/callback` 当前仍代理 legacy Next writeback。对 local-folder 的 `status 2/6 -> 下载 body.url -> 原文件覆盖写回 -> watcher 同步` 没有完整闭环。 -- `layout.rs` 中正文附件菜单已有 `new-window`,但没有“使用编辑模式打开”。部分路径当前会默认生成 `mode=edit`,这与“默认只读、显式编辑”的产品口径不一致。 -- annotation 插件 404 / pageerror 暂未证明会影响文档渲染;当前更像是 OnlyOffice 静态插件包或自定义插件配置缺失导致的 console 噪音。 +- `/api/onlyoffice/callback` 已由 `5-36` 补齐 local-folder `status 2/6 -> 下载 body.url -> 原文件覆盖写回` 的后端契约;legacy Next 代理仍保留为 cloud/compat 边界。 +- `layout.rs` / browser runtime 已提供“使用编辑模式打开”入口,默认打开保持 `mode=view`,显式 edit 保留 guard。 +- annotation/custom assistant 插件噪音已由 `5-37` / `5-40` 分类:插件噪音不等于主文档失败,真实 `errorCode=-18`、WebSocket 失败或 iframe 空白不能被噪音掩盖。 ## 根因判断 @@ -46,7 +50,7 @@ P0 目标: - 菜单提供“使用编辑模式打开”入口。 - 编辑入口必须带 guard:清楚标记为实验能力,或在 local-folder writeback 未闭环时阻止/提示。 -P1 目标才是实现 local-folder callback 写回。 +P1 目标是实现 local-folder callback 写回;该项已由 `5-36` 完成后端契约。 ### 3. annotation 插件 404 / pageerror @@ -70,7 +74,7 @@ P0 目标: - P0:若保存闭环未完成,编辑入口必须有 guard / 实验标记。 - `5-36-onlyoffice-local-edit-save-callback-contract-v1.md` - - P1:设计或实现 local-folder callback 写回。 + - P1:local-folder callback 写回。 - P1:覆盖 status 2/6、下载 URL rewrite、路径权限、冲突保护。 - `5-37-onlyoffice-annotation-plugin-noise-policy-v1.md` @@ -99,9 +103,19 @@ P0 目标: - 菜单提供“使用编辑模式打开”,进入前有实验 guard。 - 已存在 Office resource tab 从 view 切 edit 会刷新同一 iframe URL,不再只激活旧 tab。 - annotation/custom assistant 插件 404 已归类为 non-critical noise,不作为主文档打开失败。 -- P1 仍保留: - - local-folder Office edit/save callback 写回闭环未实现,编辑模式仍不能承诺保存到原文件。 - - `onRequestEditRights` 事件重新初始化 edit URL 未实现。 +- P1 已拆分并收口: + - `5-36`:local-folder Office edit/save callback 写回后端契约已完成,覆盖 status 2/6、非写状态、未认证、路径越界。 + - `5-35`:`onRequestEditRights` 事件会重新初始化为 `mode=edit` URL,不只 reload 当前 view。 + - 本 umbrella review 仅作为缺口审查和拆分记录归档;后续真实 Office 编辑保存端到端浏览器验收若发现新问题,应另建具体 bug。 - 浏览器证据: - Reasonix:`tmp/reasonix-office-view-edit-plugin-2026-05-21/result.json` 与截图。 - Codex:`tmp/codex-office-view-edit-plugin-2026-05-21/result.json`、`01-office-view-mode.png`、`02-office-edit-mode.png`。 +- 2026-06-01 验证: + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_page -- --test-threads=1` + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_callback -- --test-threads=1` + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_local_callback -- --test-threads=1` + - `cargo test --manifest-path rust/Cargo.toml -p adapter-onlyoffice callback_preparation -- --test-threads=1` + - `node --check scripts/task463-onlyoffice-resolver-smoke.js` + - `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task463-onlyoffice-resolver-smoke.js` + - `node --check scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` + - `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` diff --git a/bugs/05-editor-mainline/process/5-34-local-folder-media-sign-office-url-contract-v1.md b/bugs/05-editor-mainline/done/5-34-local-folder-media-sign-office-url-contract-v1.md similarity index 100% rename from bugs/05-editor-mainline/process/5-34-local-folder-media-sign-office-url-contract-v1.md rename to bugs/05-editor-mainline/done/5-34-local-folder-media-sign-office-url-contract-v1.md diff --git a/bugs/05-editor-mainline/process/5-35-office-edit-mode-menu-and-guard-v1.md b/bugs/05-editor-mainline/done/5-35-office-edit-mode-menu-and-guard-v1.md similarity index 69% rename from bugs/05-editor-mainline/process/5-35-office-edit-mode-menu-and-guard-v1.md rename to bugs/05-editor-mainline/done/5-35-office-edit-mode-menu-and-guard-v1.md index ead5d57e..6b75aff5 100644 --- a/bugs/05-editor-mainline/process/5-35-office-edit-mode-menu-and-guard-v1.md +++ b/bugs/05-editor-mainline/done/5-35-office-edit-mode-menu-and-guard-v1.md @@ -1,5 +1,9 @@ # 5-35 Office 编辑模式菜单与保护 +## 状态 + +- 状态:done + ## 目标 Office 文件默认只读打开;在正文附件三点菜单和文件树资源右键菜单中增加“使用编辑模式打开”入口。保存闭环未完成前,编辑入口必须有 guard 或明确实验标记。 @@ -28,16 +32,30 @@ ONLYOFFICE `mode=edit` 只是编辑器初始化模式。若 MNote 没有完成 c - [x] 编辑模式入口明确使用 `mode=edit` 打开到主 resource tab 或新窗口,行为与现有打开目标一致。 - [x] 在 local-folder save callback 未闭环时,编辑入口有 guard:可提示“编辑保存仍在实验中”,或通过 data/status 标记便于测试识别。 - [x] 已存在 Office resource tab 从 `mode=view` 切到显式 `mode=edit` 时,刷新同一 tab 的 iframe URL,而不是只激活旧 tab。 -- [ ] 若启用 `onRequestEditRights`,必须重新初始化为 edit URL,不只 reload 当前 view。(P1,未实现) +- [x] 若启用 `onRequestEditRights`,必须重新初始化为 edit URL,不只 reload 当前 view。 ## 验收 -- `cargo test -p mnote-web sidebar_tree_js -- --test-threads=1` -- `cargo test -p mnote-web onlyoffice -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_page -- --test-threads=1` - 浏览器截图:默认打开是只读;菜单中存在编辑入口;点击编辑入口后的页面 URL / debug state 包含 `mode=edit` 或 guard 提示。 ## 本轮执行记录 +- 2026-06-01:Codex 补齐 `onRequestEditRights` P1。 + - `/onlyoffice` 页面新增 `editModeLocationHref()`,触发 `onRequestEditRights` 时将当前 URL 的 `mode` 设置为 `edit` 并 `window.location.replace(editHref)`,避免只 reload 当前 view。 + - 新增 `onlyoffice_page_reinitializes_edit_url_on_request_edit_rights` 契约测试,断言事件处理、debug 标记和 edit URL replacement 存在。 + - 复测 `task463` 时发现并修复普通 `openTarget="new-window"` 被 `forceNewWindow || forceEditMode` 误判成 `mode=edit` 的状态泄漏;现在只有 `forceEditMode` 才会进入 edit,普通新窗口继续默认 view / `/office-preview`。 + - 验证通过: + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_page -- --test-threads=1` + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice -- --test-threads=1` + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --test-threads=1` + - `node --check scripts/task463-onlyoffice-resolver-smoke.js` + - `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task463-onlyoffice-resolver-smoke.js` + - `node --check scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` + - `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` + - 2026-05-21:Reasonix worker B 卡在计划阶段后由 Codex 终止;本条实现来自其它 Reasonix 结果与 Codex 复核修正。 - `buildOnlyOfficeOpenUrl` 默认 mode 从 `'edit'` 改为 `'view'`。 - `buildOnlyOfficeOpenPath` 默认 mode 从 `'edit'` 改为 `'view'`。 diff --git a/bugs/05-editor-mainline/process/5-36-onlyoffice-local-edit-save-callback-contract-v1.md b/bugs/05-editor-mainline/done/5-36-onlyoffice-local-edit-save-callback-contract-v1.md similarity index 53% rename from bugs/05-editor-mainline/process/5-36-onlyoffice-local-edit-save-callback-contract-v1.md rename to bugs/05-editor-mainline/done/5-36-onlyoffice-local-edit-save-callback-contract-v1.md index f22b7263..9c808213 100644 --- a/bugs/05-editor-mainline/process/5-36-onlyoffice-local-edit-save-callback-contract-v1.md +++ b/bugs/05-editor-mainline/done/5-36-onlyoffice-local-edit-save-callback-contract-v1.md @@ -1,12 +1,16 @@ # 5-36 OnlyOffice local-folder 编辑保存 callback 契约 +## 状态 + +- 状态:done + ## 目标 为 local-folder Office 编辑保存建立 callback 写回契约。P1 可以先落设计和测试骨架;若实现,必须覆盖 status 2/6 的下载写回和路径安全。 ## 原因 -ONLYOFFICE 官方保存链路要求 callback `status === 2` 或 `status === 6` 时,集成后端下载 `body.url` 并写回原文件。当前 `mnote-web` callback 仍代理 legacy Next,local-folder 写回未闭环。 +ONLYOFFICE 官方保存链路要求 callback `status === 2` 或 `status === 6` 时,集成后端下载 `body.url` 并写回原文件。此前 `mnote-web` callback 仍代理 legacy Next,local-folder 写回未闭环。本轮已补齐 local-folder callback 写回路径。 ## 允许修改 @@ -23,21 +27,38 @@ ONLYOFFICE 官方保存链路要求 callback `status === 2` 或 `status === 6` ## Checklist -- [ ] 明确 local asset callback 定位:从 `assetId` / `fileUrl` / session 推导 rootUri 与 path。 -- [ ] status 非 2/6 时返回 `{ "error": 0 }`,不写文件。 -- [ ] status 2/6 时下载 rewritten `body.url`。 -- [ ] 写回前校验目标路径属于当前 local root allowed roots。 -- [ ] 写回后触发 watcher / projection 刷新或说明现有 watcher 如何感知。 -- [ ] 若本轮不实现完整写回,必须在编辑入口保留 guard,不让用户以为保存已支持。 +- [x] 明确 local asset callback 定位:callback URL 携带 `rootUri` / `path` / `sessionId` / `token`,并用 bridge session 校验 asset match。 +- [x] status 非 2/6 时返回 `{ "error": 0 }`,不写文件。 +- [x] status 2/6 时下载 rewritten `body.url`。 +- [x] 写回前校验目标路径属于当前 local root allowed roots。 +- [x] 写回后写入原始本地文件;后续页面刷新由现有 local-folder watcher / projection refresh 感知。浏览器编辑保存端到端 smoke 另列后续验收,不作为本 bug 阻塞项。 +- [x] 编辑入口仍保留 guard,不让用户误解为完整协作编辑能力已产品化。 ## 验收 -- `cargo test -p mnote-web onlyoffice_callback -- --test-threads=1` -- `cargo test -p mnote-web -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_callback -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_local_callback -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p adapter-onlyoffice callback_preparation -- --test-threads=1` - 后续浏览器编辑保存验收需单独设计,不纳入 P0。 ## 本轮执行记录 +- 2026-06-01:Codex 复核当前实现并补齐 callback 写回回归测试。 + - `OnlyOfficeCallbackQuery` 已携带 `root_uri` / `path` / `session_id` / `token`。 + - `buildCallbackUrl()` 已把 local-folder Office asset 的 root/path/session/token 写入 callback query。 + - `local_folder_onlyoffice_callback()` 已校验 root/path、bridge session token、session asset match,并通过 `resolve_onlyoffice_local_file_path()` 阻断 root escape。 + - status 2/6 通过 `prepare_callback()` rewrite 后下载 `body.url` 并写回原始本地文件;非 2/6 返回成功且不写文件。 + - 新增/确认测试覆盖: + - `onlyoffice_local_callback_rejects_unauthenticated_local_write` + - `onlyoffice_local_callback_writes_status_two_body_to_original_file` + - `onlyoffice_local_callback_writes_status_six_body_to_original_file` + - `onlyoffice_local_callback_rejects_root_escape_path` + - `onlyoffice_local_callback_ignores_non_write_status` + - 验证通过: + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_callback -- --test-threads=1` + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_local_callback -- --test-threads=1` + - `cargo test --manifest-path rust/Cargo.toml -p adapter-onlyoffice callback_preparation -- --test-threads=1` + - 2026-05-21:Reasonix worker C 分析完成。 **结论:本轮不应实现完整写回。** diff --git a/bugs/05-editor-mainline/process/5-37-onlyoffice-annotation-plugin-noise-policy-v1.md b/bugs/05-editor-mainline/done/5-37-onlyoffice-annotation-plugin-noise-policy-v1.md similarity index 80% rename from bugs/05-editor-mainline/process/5-37-onlyoffice-annotation-plugin-noise-policy-v1.md rename to bugs/05-editor-mainline/done/5-37-onlyoffice-annotation-plugin-noise-policy-v1.md index 2913bfd9..180432cb 100644 --- a/bugs/05-editor-mainline/process/5-37-onlyoffice-annotation-plugin-noise-policy-v1.md +++ b/bugs/05-editor-mainline/done/5-37-onlyoffice-annotation-plugin-noise-policy-v1.md @@ -1,5 +1,7 @@ # 5-37 OnlyOffice annotation 插件噪音策略 +> 2026-06-01 复核:本文是 2026-05-21 旧口径。后续 `5-40-onlyoffice-bridge-plugin-noise-regression-v1` 引入了新的 OnlyOffice bridge/plugin 注入口径,已覆盖“当前代码无 autostart 注入”的结论。本文仅保留 annotation/custom assistant 内置插件噪音分类;主文档失败分类、`errorCode=-18`、iframe 空白和 bridge plugin 噪音治理以 `5-40` 的最终分类为准。`5-40` 已通过 `task518-onlyoffice-real-iframe-session-scope-smoke.js` 的 `errorClassification` 验收,本文不再作为活跃 process 缺陷。 + ## 目标 定位 OnlyOffice annotation / custom assistant 插件 404 或 pageerror 来源,并把它从主打开失败中剥离:能禁用则禁用,不能禁用则在测试和日志中明确为 non-critical。 @@ -25,7 +27,7 @@ ONLYOFFICE 插件通过 `editorConfig.plugins.autostart` 和 `pluginsData` 注 - [x] 找到 annotation/custom assistant 插件配置或请求来源。 - [x] 若 MNote 当前不依赖该插件,禁用无效 autostart / pluginsData。(确认当前代码已无 autostart 注入,无需额外禁用) - [x] 若属于 OnlyOffice 内置可选插件缺资源,记录为 non-critical,并更新浏览器测试过滤口径。 -- [ ] 补测试或文档,确保 `errorCode=-18` / WebSocket 失败仍被视为失败,不被插件噪音掩盖。(P1,后续补充) +- [x] 补测试或文档,确保 `errorCode=-18` / WebSocket 失败仍被视为失败,不被插件噪音掩盖。(由 `5-40` / `task518-onlyoffice-real-iframe-session-scope-smoke.js` 的 `errorClassification.mainDocument` 覆盖) ## 验收 diff --git a/bugs/05-editor-mainline/done/5-40-onlyoffice-bridge-plugin-noise-regression-v1.md b/bugs/05-editor-mainline/done/5-40-onlyoffice-bridge-plugin-noise-regression-v1.md new file mode 100644 index 00000000..38a911c2 --- /dev/null +++ b/bugs/05-editor-mainline/done/5-40-onlyoffice-bridge-plugin-noise-regression-v1.md @@ -0,0 +1,67 @@ +# 5-40 ONLYOFFICE bridge 插件注入后噪音口径需复测 + +## 状态 + +- 状态:done +- Owner:05-editor-mainline / OnlyOffice preview-edit / browser smoke +- 发现时间:2026-05-31 + +## 现象 + +旧缺陷 `5-37` 的结论是当前代码没有注入 `plugins.autostart` / `pluginsData`,因此 annotation/custom assistant 插件 404 可作为 OnlyOffice 内置非关键噪音处理。但近期 ONLYOFFICE live bridge 已在 `onlyoffice.rs` 中重新注入 MNote bridge 插件,这会让旧噪音策略失效,必须重新区分 MNote bridge 插件失败、OnlyOffice 内置插件噪音和真正主文档加载失败。 + +## 证据 + +- `bugs/05-editor-mainline/process/5-37-onlyoffice-annotation-plugin-noise-policy-v1.md` 记录:“当前代码已无 autostart 注入,无需额外禁用”。 +- `rust/crates/mnote-web/src/routes/onlyoffice.rs` 当前 `editorConfig.plugins` 注入: + - `autostart: [MNOTE_AGENT_PLUGIN_GUID]` + - `pluginsData: [bridgePluginConfigUrl.toString()]` + +## 影响 + +- clean browser 测试里 MNote bridge 插件加载失败可能被错误归类为“OnlyOffice 内置插件噪音”。 +- 相反,主 iframe 空白、`errorCode=-18`、DocumentServer WebSocket 失败也可能被泛化过滤掉。 +- Office live agent 能力上线后,插件加载失败将不再是纯噪音,而是 AI Office 工具不可用。 + +## 最小复现建议 + +1. clean browser 打开 docx/xlsx/pptx。 +2. 记录 console、pageerror、network 404/500、OnlyOffice iframe ready state。 +3. 分别验证: + - MNote bridge 插件 config/index 是否成功加载。 + - OnlyOffice 内置 annotation/custom assistant 缺资源是否仍为 non-critical。 + - 主 iframe 空白、`errorCode=-18`、WebSocket 失败仍判定为失败。 + +## 修复建议 + +- 更新 `5-37` 或新 smoke 的错误分类规则:MNote bridge 插件错误不等同于内置插件噪音。 +- 在 OnlyOffice smoke 中输出插件分类字段,例如 `mnoteBridgePluginOk`、`builtinPluginNoiseOnly`、`mainDocumentReady`。 +- 对 bridge 插件 config/index 增加 route 单测和 browser smoke 断言。 + +## 本轮进展 + +- 2026-06-01:新增并通过 `scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js`,在真实 Chromium 中 mock `Asc.plugin` 后直接加载 bridge plugin index,验证 session 注册、token 错误拒绝、`selection.get` / `document.insert_text` command 消费和 result 回传。 +- 该 smoke 证明 MNote bridge plugin index / HTTP loop 自身可用,但不覆盖真实 ONLYOFFICE iframe / DocumentServer 的 autostart、内置插件噪音、主文档 ready 或 `errorCode=-18` 分类,因此本 bug 仍保留在 `process/`。 +- 2026-06-01:新增并通过 `scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`,在真实 ONLYOFFICE iframe / DocumentServer 下验证 MNote bridge 插件可 autostart 注册并回收命令。该 smoke 记录到的 404 均为 `/api/onlyoffice/bridge/plugin/index/translations/langs.json` 与 `/api/onlyoffice/bridge/plugin/index/translations/zh-CN.json`,当前可归类为 bridge 插件翻译资源噪声;它没有阻断 session 注册、`selection.get` 或工具层授权 dry-run。 + +- 2026-06-01:增强并通过 `scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`,输出 `errorClassification`: + - `mnoteBridgePlugin.httpErrors=[]` + - `mnoteBridgePlugin.sessionsRegistered=true` + - `mnoteBridgePlugin.commandLoopOk=true` + - `bridgeTranslationNoise.count=6`,仅包含 `/api/onlyoffice/bridge/plugin/index/translations/langs.json` 和 `zh-CN.json` 404 + - `builtinPluginNoise.count=0` + - `mainDocument.ready=true` + - `mainDocument.failures=[]` + - `mainDocument.errorCodeMinus18=false` + 该分类把 MNote bridge 插件失败、bridge 翻译资源噪音、OnlyOffice 内置插件噪音和主文档失败分开;`errorCode=-18` / 主 iframe 空白不会被插件噪音吞掉。 + +## 验收 + +- [x] Clean browser smoke 能区分三类错误:MNote bridge 插件失败、OnlyOffice 内置插件噪音、主文档加载失败。 +- [x] `errorCode=-18` / 主 iframe 空白不会被插件噪音过滤吞掉。 +- [x] bridge 插件不可用时,Office 文档预览结论和 Office AI 工具可用性结论分开报告。 + +## 最终验证 + +- `node --check scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` +- `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` diff --git a/bugs/05-editor-mainline/done/5-41-page-aggregate-compat-slimming-gaps-v1.md b/bugs/05-editor-mainline/done/5-41-page-aggregate-compat-slimming-gaps-v1.md new file mode 100644 index 00000000..3fba31d9 --- /dev/null +++ b/bugs/05-editor-mainline/done/5-41-page-aggregate-compat-slimming-gaps-v1.md @@ -0,0 +1,57 @@ +# 5-41 Page Aggregate compat 瘦身剩余缺口 + +## 状态 + +- 状态:done +- Owner:05-editor-mainline / Page Aggregate / browser document conversion +- 发现时间:2026-06-01 + +## 现象 + +Page Aggregate 已经完成 local-first 主链和多个单一真源修复。2026-06-01 复核后,`body.content`、compat join 和浏览器 legacy content 降级被明确收口为 v1 兼容面:它们仍是 active runtime 的兼容读写字段,但不再作为 local-first 浏览器主事实源。 + +## 证据 + +- `rust/crates/core-protocol/src/page_aggregate.rs` 中 `PageBody.content` 仍是必填字段。 +- `rust/crates/mnote-web/src/routes/local_folder_source.rs` 的 local-first aggregate 仍会填充 `body.content`。 +- `rust/crates/bridge-runtime/src/lib.rs` 的 cloud/compat aggregate 路径仍支持 `CompatMetaContentJoin`。 +- `rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js` 已在 2026-06-01 调整为 `blockDocument/editorDocument` 优先,`local_markdown.content` 与 `compat.legacy_content` 只作为缺少 block document 时的降级路径;`scripts/task522-page-aggregate-compat-fallback-contract.js` 覆盖了这几个 source 分支。 +- `rust/crates/mnote-web/src/routes/documents.rs` 的保存兼容面仍同时携带 `editorDocument`、`content`、`tiptapDocument`。 +- `rust/crates/mnote-web/src/page_aggregate/builder.rs` 的默认 source 已在 2026-06-01 改为 `KernelProjection`,显式 `.source(PageAggregateSource::CompatMetaContentJoin)` 仍保留 compat 路径。 + +## 影响 + +- “Page Aggregate 单一真源”仍保留 v1 兼容数据面:前端和后端都需要继续维护 legacy content,但只作为镜像 / fallback。 +- local-first 浏览器转换层已优先消费 block document;后端协议和 compat 保存面继续保留 legacy content,保证 cloud/compat fallback 和旧保存面不被破坏。 +- 后续若要删除或改为可选 `body.content`,必须进入 `mnote.page_aggregate.v2` 或等价协议升级 checklist,不能在 v1 中直接删除。 + +## 下一步建议 + +- `PageBody.content` 在 `mnote.page_aggregate.v1` 中继续保持必填兼容字段,用于 legacy/cloud/compat fallback、旧保存面和降级回读。 +- `body.blockDocument` / `editorDocument` 是 local-first 浏览器主消费面;`body.content` 不再作为 active editor 初始化的优先事实源。 +- `CompatMetaContentJoin` 继续只作为显式 cloud/compat substrate 边界保留;`PageAggregateBuilder::new()` 默认 source 保持 `KernelProjection`。 +- `body.content` 改为可选或删除的退出条件:必须先有 `mnote.page_aggregate.v2` 协议或迁移 checklist,证明 cloud/compat 保存面、外部 agent、历史 fixture 和降级读取全部不再依赖该字段。 + +## 验收 + +- [x] local-first 浏览器转换优先消费 `body.blockDocument`,`pageBodyTiptapDocumentSource(...)` 在 `blockDocument` 存在时返回 `page_aggregate.block_document`。 +- [x] `body.content` 仅在缺少 `blockDocument` / `editorDocument` 时作为 local markdown legacy fallback。 +- [x] Builder 默认 source 不再是 `CompatMetaContentJoin`。 +- [x] `compat.legacy_content` 降级路径有明确 source 标记和测试覆盖。 +- [x] 设计稿明确 `body.content` 的长期状态:v1 保留为必填兼容镜像 / fallback;v2 或后续协议升级再评估可选或删除。 + +## 2026-06-01 验证 + +- `node --check rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js` +- `node --check scripts/task522-page-aggregate-compat-fallback-contract.js` +- `node scripts/task522-page-aggregate-compat-fallback-contract.js` +- `cargo test -p mnote-web document_shell_returns_page_aggregate_snapshot -- --test-threads=1` +- `cargo test -p mnote-web page_aggregate::builder::tests -- --test-threads=1` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task167-local-markdown-title-body-options-no-convex-smoke.js` + +## 2026-06-01 收口决策 + +- `PageBody.content`:v1 保留必填,语义降级为兼容镜像 / fallback,不作为 local-first active editor 主事实源。 +- `body.blockDocument` / `editorDocument`:local-first 浏览器转换优先消费的 canonical block view。 +- `compat.legacy_content`:仅当缺少 `blockDocument` / `editorDocument` 时可被浏览器转换层使用,并通过 `task522-page-aggregate-compat-fallback-contract.js` 固化 source 标记。 +- `CompatMetaContentJoin`:显式 compat substrate 边界,保留到 cloud/legacy 保存面完成协议升级,不再是 builder 默认 source。 diff --git a/bugs/07-ai/done/7-45-onlyoffice-live-bridge-session-isolation-v1.md b/bugs/07-ai/done/7-45-onlyoffice-live-bridge-session-isolation-v1.md new file mode 100644 index 00000000..d9d223a5 --- /dev/null +++ b/bugs/07-ai/done/7-45-onlyoffice-live-bridge-session-isolation-v1.md @@ -0,0 +1,69 @@ +# 7-45 ONLYOFFICE live bridge session 隔离风险 + +## 状态 + +- 状态:done +- Owner:07-ai / OnlyOffice live bridge / mnote-web +- 发现时间:2026-05-31 + +## 现象 + +ONLYOFFICE live bridge session 当前是进程内全局状态。工具调用未显式传 `onlyofficeSessionId` / `bridgeSessionId` 时,会 fallback 到最近活跃 session;同一文档的 bridge session id 又由 `docKey` 派生,重复打开同文档或多用户/多 tab 打开不同 Office 文档时,存在命令落到错误 Office tab 或 token 覆盖的风险。 + +## 证据 + +- `rust/crates/mnote-web/src/routes/onlyoffice.rs` 生成 `bridgeSessionId = "mnote-oo-" + fileState.docKey`。 +- `rust/crates/mnote-web/src/routes/onlyoffice_bridge.rs` 的 `register_session` 按 `session_id` 覆盖 `token`、`document_id`、`asset_id` 和 `last_seen_millis`。 +- `rust/crates/mnote-web/src/routes/onlyoffice_bridge.rs` 的 `current_session_info()` 直接取全局 `last_seen_millis` 最大值。 +- `rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rs` 的 `resolve_session_id()` 在未传 session id 时 fallback 到 `current_session_info()`。 + +## 影响 + +- Page AI 或 Hermes tool 可能在多 Office tab 场景写错目标文档。 +- 同一文档重复打开可能互相覆盖 bridge token,使旧 tab 的插件长轮询或结果回传失效。 +- 多用户共享同一 mnote-web 进程时,全局最近 session 可能跨用户泄漏目标选择。 + +## 最小复现建议 + +1. 打开两个不同 Office 文档,或同一文档两个 tab。 +2. 不传 `onlyofficeSessionId` 调用 `mnote.onlyoffice.session.current` 与一个写工具,例如 `mnote.onlyoffice.document.insert_text`。 +3. 观察返回 session 是否只由最近活跃 tab 决定。 +4. 同一文档双 tab 复测 token 覆盖后,旧 tab `commands/next` 或 `results` 是否返回 401 / 无结果。 + +## 修复建议 + +- bridge session id 加入 browser tab 级随机后缀,不只使用 `docKey`。 +- Page AI run payload 必须携带当前 resource tab 的 explicit `onlyofficeSessionId`;写工具禁止默认 fallback 写入。 +- `current_session` 只能作为只读诊断工具,不能作为写工具默认目标。 +- session state 至少按 actor/session/document/resource 维度过滤。 + +## 本轮进展 + +- 2026-05-31: + - `onlyoffice.rs` 已把 `bridgeSessionId` 从仅基于 `docKey` 改为 `docKey + bridgeSessionSalt`,避免同文档多 tab 共用同一个 bridge session id。 + - `onlyoffice_live.rs` 已禁止读/写 action fallback 到全局最近 session;读写工具必须显式传 `onlyofficeSessionId` / `bridgeSessionId`。 + - `onlyoffice.rs` 的本地 callback 已绑定 bridge `sessionId` + `token`,未认证本地写回返回 401 并保持原文件不变。 + - 已补 Rust 定点测试覆盖缺 explicit session 的读/写工具拒绝,以及未认证 local callback 拒绝。 + - 已补 `scripts/task515-onlyoffice-live-scope-http-smoke.js` 覆盖 HTTP 层缺 explicit session 返回 400。 + - 已补并通过 `scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js`,在真实浏览器中验证 A/B 两个 bridge session 的 token 校验、command queue 和 result 回收互不串台,错误 token 返回 401。 +- 2026-06-01: + - 已补并通过 `scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`,在真实 ONLYOFFICE iframe / DocumentServer 下打开同一 docx 两个 tab 和另一个 docx,验证同文档双 tab 共享 `docKey` 但使用不同 `bridgeSessionId`,三个插件 session 均能注册并各自回收 `selection.get` command。 + - `task518` 同时覆盖显式传 B session 但 scope=A 时返回 403,以及 scope=B 授权 dry-run 返回 200,证明真实 iframe session 能被工具层按显式 session 和 resource scope 约束。 + - Page AI open editors snapshot / target package 已开始保留 Office `onlyofficeSessionId` / `bridgeSessionId`,服务端 `agentTargetPackage` 与 `aiAccessScope.allowedResourceIds` 已能保留 Office asset、session 和 `resource:onlyoffice:{documentId}:{assetId}` 候选;新增 Rust 定点测试 `hermes_client_run_body_preserves_onlyoffice_target_scope` 覆盖该合同。 + - ONLYOFFICE iframe 在设置 `__MNOTE_ONLYOFFICE_DEBUG__` 后会向父窗口发送 `mnote:onlyoffice-bridge-ready`,resource tab runtime 收到后事件驱动刷新 open editors snapshot;Page AI 发送前也会拒绝没有 `onlyofficeSessionId` 的 Office target,避免 bridge 未就绪时发起 run。 + - `task518` 已扩展并通过非 dry-run 写入落点验证:通过 `mnote.onlyoffice.document.insert_text` 向 Office B 写入唯一 marker,再通过真实 iframe bridge `document.export` 导出 A/B 内容,断言 B 包含 marker 且 A 不包含;截图保存到 `tmp/task518-onlyoffice-real-iframe-session-scope-smoke/screenshots/office-a-after-write.png` 和 `office-b-after-write.png`。 + - 已补并通过 `scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js`:在真实文档页内打开 Office resource tab,等待 iframe bridge ready 后通过 Page AI target picker 选择 Office target,断言 `editorTarget` 与 `targetPackage` 均冻结真实 `onlyofficeSessionId` / `bridgeSessionId`,并断言无 `consoleErrors` / `networkFailures` / `httpErrors`。 + +当前 P0 安全阻断已覆盖“工具不能隐式读写全局最近 session”“callback 不能未认证写本地文件”“bridge session/token/queue 在浏览器 HTTP 层隔离”“真实 iframe 插件 session 显式授权边界”“Page AI run scope 不再被服务端压回当前页面 id”“Office bridge 未就绪时 Page AI 发送前拒绝”“真实 iframe 非 dry-run 写入不串台”和“真实 Page AI UI 从 Office iframe 目标取到 live `bridgeSessionId` 并冻结进 run payload”。 + +## 验收 + +- [x] Rust 单测覆盖页面生成同 docKey 双 session 随机 session id。 +- [x] Rust 单测覆盖读/写工具缺 explicit session id 时拒绝。 +- [x] Rust 单测覆盖本地 callback 缺 bridge session/token 时拒绝。 +- [x] Browser smoke 覆盖两个 bridge session 的 token、command queue 与 result 隔离。 +- [x] Browser smoke 覆盖两个真实 Office iframe tab 下插件注册、sessionId 隔离和 command 回收。 +- [x] Rust 单测覆盖 Page AI Office target package / aiAccessScope 保留 session 与 resource scope。 +- [x] 前端事件驱动刷新覆盖 ONLYOFFICE bridge ready 消息,发送前拒绝缺 session 的 Office target。 +- [x] Browser smoke 覆盖两个真实 Office iframe tab 下非 dry-run 写入目标不串台。 +- [x] Browser smoke 覆盖 Page AI target picker 从真实 Office iframe target 读取 live `onlyofficeSessionId` 并写入 run payload。 diff --git a/bugs/07-ai/done/7-46-onlyoffice-live-tool-resource-scope-bypass-v1.md b/bugs/07-ai/done/7-46-onlyoffice-live-tool-resource-scope-bypass-v1.md new file mode 100644 index 00000000..af2c0d6a --- /dev/null +++ b/bugs/07-ai/done/7-46-onlyoffice-live-tool-resource-scope-bypass-v1.md @@ -0,0 +1,66 @@ +# 7-46 ONLYOFFICE live tool 缺少 resource scope 绑定 + +## 状态 + +- 状态:done +- Owner:07-ai / Hermes tools / OnlyOffice live bridge +- 发现时间:2026-05-31 + +## 现象 + +ONLYOFFICE live 写工具当前主要检查 `idempotencyKey`、`dryRun`、`aiAccessScope.permissionLevel` 和 command context 写权限,但没有把 bridge session 的 `documentId` / `assetId` 与 `aiAccessScope.allowedResourceIds` 绑定。调用方如果显式传入另一个已注册 Office session,存在越过当前 target resource scope 写入非授权资源的风险。 + +## 证据 + +- `rust/crates/mnote-web/src/routes/hermes_tools.rs` 注册了多组 `mnote.onlyoffice.*` 读写工具。 +- `rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rs` 的 `run_write_action()` 调用 `ensure_write_authorized()` 后立即解析 session 并执行 bridge command。 +- `rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rs` 的 `resolve_session_id()` 只解析显式 session 或全局 current session,没有验证该 session 对应的 resource 是否在 `allowedResourceIds` 内。 +- 对照 `rust/crates/mnote-web/src/hermes_tools/resource.rs`,mindmap/resource 工具已有 `ensure_resource_scope_allowed()` 校验 `allowedResourceIds` / `objectIdentity`。 + +## 影响 + +- Page AI 当前 target 是资源 A 时,模型或恶意调用可传入资源 B 的 `bridgeSessionId`,尝试写入 B。 +- 写入审计会显示工具有写权限,但缺少“写的是哪个 resource、是否被 allowedResourceIds 授权”的闭环。 +- 多 tab session fallback 与本 bug 叠加时,错误写入更难被用户发现。 + +## 最小复现建议 + +1. 构造 `aiAccessScope.allowedResourceIds = [A]`。 +2. 注册两个 ONLYOFFICE bridge session:A 和 B。 +3. 调用 `mnote.onlyoffice.sheet.set_value` 或 `mnote.onlyoffice.document.insert_text`,显式传 B 的 `bridgeSessionId`。 +4. 期望:返回 403;当前风险:只要通用写权限通过就可能执行。 + +## 修复建议 + +- `BridgeSessionInfo` 暴露稳定 `documentId`、`assetId`、`objectIdentity`。 +- ONLYOFFICE live 工具增加与 resource 工具等价的 scope 校验。 +- 写工具返回 receipt 时包含 `resourceKind=office`、`documentId`、`assetId`、`onlyofficeSessionId` 与 permission decision。 +- dry-run 也必须执行 scope 校验,不能只返回 wouldWrite。 + +## 本轮进展 + +- 2026-05-31: + - `onlyoffice_live.rs` 已在读/写 action 执行前校验 explicit session 对应的 `sessionId` / `documentId` / `assetId` / `resource:office:{documentId}:{assetId}` 是否包含在 `aiAccessScope.allowedResourceIds`。 + - 缺失 `aiAccessScope` 或空 `allowedResourceIds` 现在返回 403,不再兼容放行;dry-run 同样执行该 scope 校验。 + - `manifest.rs` 已为 OnlyOffice live 工具声明 `aiAccessScope.allowedResourceIds` 和 `onlyofficeSessionId` / `bridgeSessionId` 的 `anyOf` 合同。 + - 已补 Rust 定点测试覆盖 `allowedResourceIds=[asset_a]` 时禁止写入 session `asset_b`,`allowedResourceIds=[asset_allowed]` 时允许生成 dry-run plan,缺 scope 时返回 403,以及 manifest 合同。 + - 已补并通过 `scripts/task515-onlyoffice-live-scope-http-smoke.js`:HTTP 层覆盖缺 explicit session 400、scope 不匹配 403、缺 scope 403、授权 scope dry-run 200。 +- 2026-06-01: + - resource scope candidate 已同时接受 `resource:office:{documentId}:{assetId}` 与 FileTree / resource 对象侧使用的 `resource:onlyoffice:{documentId}:{assetId}`,避免真实 Page AI target 使用 OnlyOffice object identity 时被误拒。 + - 已补并通过 `scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`:真实 ONLYOFFICE iframe / DocumentServer 下,显式传入 Office B 的 `bridgeSessionId` 但 `aiAccessScope.allowedResourceIds=[Office A]` 时返回 403;授权 B 的 `assetId` / `resource:onlyoffice:{documentId}:{assetId}` 时 dry-run 返回 200。 + - `document-resource-tab-runtime.js` / `sidebar-page-ai-runtime.js` / `hermes_client.rs` 已补最小 target scope 链路:Office resource target 可把 `onlyofficeSessionId` 写入 target package,服务端 sanitize 不再丢弃 `primaryTargetId`、`targets[]`、`assetId`、`onlyofficeSessionId`,本地 run instructions 的 `aiAccessScope.allowedResourceIds` 会包含 Office asset、session 和 `resource:onlyoffice:{documentId}:{assetId}`。新增 `hermes_client_run_body_preserves_onlyoffice_target_scope` 证明服务端不再把 Office target scope 降级为当前页面 id。 + - 本地 agent instructions 已明确要求从 `agentRunEnvelope.targetPackage.onlyofficeSessionId` 或对应 target 取值传给 `mnote.onlyoffice.*` 工具,不允许 fallback 到最近活跃 Office session。 + - `task518` 已扩展并通过授权 B session 的非 dry-run 写入验证:写入后 `document.export` 证明 B 包含唯一 marker,A 不包含,补齐真实 iframe 层写入落点证据。 + - 已补并通过 `scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js`:真实 Page AI UI 选择 Office resource target 后,run payload 中 `editorTarget.onlyofficeSessionId`、`targetPackage.onlyofficeSessionId` 和 `targetPackage.targets[0].onlyofficeSessionId` 均等于 iframe live bridge session;`allowedFiles` 只包含选中 Office 文件路径,且无 buffer-state 404 / console error。 + +当前 `task518` 已覆盖真实 iframe session + 工具层 resource scope + 非 dry-run 写入落点,`task523` 补齐真实 Page AI UI target picker 到 live Office session 的 run payload 绑定;组合后覆盖“UI 只授权选中 Office target,工具层拒绝未授权 session”的端到端安全边界。 + +## 验收 + +- [x] Rust 单测覆盖 allowedResourceIds 不包含 session resource 时写工具返回 403。 +- [x] Rust 单测覆盖 allowedResourceIds 包含 `assetId` 或 `objectIdentity` 时允许执行。 +- [x] Rust 单测覆盖缺失 / 空 resource scope 时拒绝。 +- [x] HTTP smoke 覆盖工具层 session/scope 边界。 +- [x] Browser smoke 覆盖真实 ONLYOFFICE iframe session 的工具层 A/B resource scope 边界。 +- [x] Rust 单测覆盖 Page AI Office target package 生成的 aiAccessScope 包含 Office asset/session/object identity。 +- [x] Browser smoke 覆盖真实 Page AI UI 选择 Office target 后只把该 Office 的 live session 与 relativePath 冻结进 run payload;工具层 A/B session 越权拒写由 `task518` 覆盖。 diff --git a/bugs/07-ai/done/7-47-local-first-ai-markdown-edit-mouth-drift-v1.md b/bugs/07-ai/done/7-47-local-first-ai-markdown-edit-mouth-drift-v1.md new file mode 100644 index 00000000..729f7ebc --- /dev/null +++ b/bugs/07-ai/done/7-47-local-first-ai-markdown-edit-mouth-drift-v1.md @@ -0,0 +1,43 @@ +# 7-47 local-first Page AI 写入入口口径漂移 + +## 状态 + +- 状态:done +- Owner:07-ai / Page AI / design governance +- 发现时间:2026-05-31 + +## 现象 + +`design/10-review/done/11-current-full-architecture-review-v1.md` 保留了“`mnote.doc.markdown_edit` 已是简单正文编辑主路径”的历史结论。但当前 AGENTS、ARCHITECTURE、CURRENT_ARCHITECTURE 与 `7-18` 都已经把 local-first 普通 Markdown 编辑主路径改为:定位真实 `.md` 文件,Hermes/Reasonix 在 allowed roots 内使用自身 patch/diff/文件编辑能力写入,再由 watcher / BufferStore / Page Aggregate 同步。旧 review 结论容易误导后续 worker 继续扩 `mnote.doc.markdown_edit`。 + +## 证据 + +- `design/10-review/done/11-current-full-architecture-review-v1.md` 写明 `mnote.doc.markdown_edit` 是简单正文编辑主路径。 +- `AGENTS.md` 与 `ARCHITECTURE.md` 当前口径明确:`mnote.doc.markdown_edit` 只作为 cloud / remote agent / compat fallback。 +- `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 明确要求 Agent Target Resolver、allowed roots/files、dirty guard 和 agent 原生 patch/diff 主路径。 + +## 影响 + +- 后续 Page AI 任务可能绕开 `7-18` 的 target package / allowed roots / dirty conflict 模型。 +- AI 写入审计、文件版本冲突和 watcher 同步难以统一。 +- `mnote.page.save` / `mnote.doc.markdown_edit` 可能被继续当成 local-first 精确编辑入口扩张。 + +## 修复建议 + +- 在 `11-current-full-architecture-review-v1.md` 补充“历史快照,local-first 主路径已被 `7-18` 覆盖”的说明,或在 `7-18` 中添加覆盖旧 review 的显式引用。 +- Page AI 新任务默认从 `7-18` 拆 checklist,不从 `11` 的旧 tool 主路径拆实现。 +- 对 Hermes tool guidance 增加断言:local-first 普通 Markdown 编辑优先文件引用和 patch/diff,工具写入只作为 fallback。 + +## 本轮进展 + +- 2026-05-31:已在 `design/10-review/done/11-current-full-architecture-review-v1.md` 顶部补充历史快照说明,明确当前 local-first 普通 Markdown 编辑主路径以 `AGENTS.md`、`CURRENT_ARCHITECTURE.md`、`ARCHITECTURE.md` 和 `7-18` 为准。 +- 2026-06-01:已补 Page AI run 的 `mnote.agent_target_package.v1` 输入和后端 sanitize / allowedFiles 派生,避免 local-first agent 只拿到目录级 `allowedRoots` 而缺少冻结目标文件。该进展只覆盖运行时输入合同,target chip / picker、真实 agent 写入回收和 dirty conflict 仍按 `7-18` 后续阶段推进。 + +- 2026-06-01:已检查 Hermes client runtime guidance。local-first 分支已经明确“普通 Markdown 编辑优先使用 agent 原生 patch/diff 写入真实文件”;remote/cloud/compat 分支中 `mnote_doc_markdown_edit` 的描述已补上“远端 / cloud / compat”限定,测试名同步改为 `hermes_client_run_guidance_prefers_markdown_edit_only_for_remote_compat_plain_body_edits`。 +- 2026-06-01:已给 `design/10-review/done/08`、`09`、`10` 补充历史快照说明,避免旧 review 结论继续覆盖 `7-18` 当前口径。 + +## 验收 + +- [x] `rg -n "markdown_edit.*主路径|简单正文编辑主路径|应优先调用 mnote_doc_markdown_edit|mnote_doc_markdown_edit for plain" design AGENTS.md ARCHITECTURE.md CURRENT_ARCHITECTURE.md rust/crates/mnote-web/src/routes/hermes_client.rs -g '*.md' -g '*.rs'` 的剩余命中均属于历史快照说明、已退役/降级口径、old 目录或 remote/cloud/compat 分支。 +- [x] Page AI task prompt / skill guidance 中明确区分 local-first 主路径与 cloud/remote/compat fallback。 +- [x] `cargo test -p mnote-web hermes_client_run_guidance_prefers_markdown_edit_only_for_remote_compat_plain_body_edits -- --test-threads=1` diff --git a/bugs/07-ai/done/7-48-mindmap-create-from-outline-envelope-test-embed-drift-v1.md b/bugs/07-ai/done/7-48-mindmap-create-from-outline-envelope-test-embed-drift-v1.md new file mode 100644 index 00000000..0f3c3449 --- /dev/null +++ b/bugs/07-ai/done/7-48-mindmap-create-from-outline-envelope-test-embed-drift-v1.md @@ -0,0 +1,29 @@ +# 7-48 Mindmap create_from_outline envelope 单测缺少 embed 边界 + +## 状态 + +- 状态:done +- Owner:07-ai / Hermes tools / mindmap resource +- 发现时间:2026-05-31 +- 修复时间:2026-05-31 + +## 现象 + +`cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib hermes_tools_mindmap -- --test-threads=1` 中 `hermes_tools_mindmap_create_from_outline_writes_default_envelope` 失败,返回: + +```text +mnote_resource_page_not_found: 找不到要绑定 mindmap 的本地 Markdown 页面 +``` + +## 根因 + +该用例只验证 `mnote.mindmap.create_from_outline` 生成默认 `.mindmap.json` envelope,但 payload 未显式设置 `embedIntoPage`。当前工具合同中 `embedIntoPage` 默认是 `true`,而测试 fixture 没有创建 `README.md`,导致资源文件写入后进入页面 embed 阶段并失败。 + +## 修复 + +- 在纯 envelope 写入用例里显式设置 `embedIntoPage: false`。 +- 保留另一个 `hermes_tools_mindmap_create_from_outline_can_embed_into_local_markdown_page` 用例继续覆盖显式 `embedIntoPage=true` 的页面绑定行为。 + +## 验证 + +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib hermes_tools_mindmap -- --test-threads=1` diff --git a/bugs/07-ai/done/7-50-onlyoffice-session-current-scope-leak-v1.md b/bugs/07-ai/done/7-50-onlyoffice-session-current-scope-leak-v1.md new file mode 100644 index 00000000..797ada1d --- /dev/null +++ b/bugs/07-ai/done/7-50-onlyoffice-session-current-scope-leak-v1.md @@ -0,0 +1,51 @@ +# 7-50 ONLYOFFICE session.current 诊断工具缺少 resource scope 过滤 + +## 状态 + +- 状态:done +- Owner:07-ai / OnlyOffice live bridge / Hermes tools +- 发现时间:2026-06-01 + +## 现象 + +`mnote.onlyoffice.session.current` 是诊断工具,不会执行读写动作;但它在未传 `onlyofficeSessionId` / `bridgeSessionId` 时仍会 fallback 到进程内最近活跃 OnlyOffice bridge session,并返回 `sessionId`、`documentId`、`assetId`、`fileType`、pending command/result 计数等元数据。 + +这与读写工具已经要求 explicit session + `aiAccessScope.allowedResourceIds` 的收口方向不完全一致。若 Page AI 当前 target=A,但进程内最近活跃 Office session 属于 resource=B,模型可能通过 `session.current` 看到 B 的 session 元数据。 + +## 证据 + +- `rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rs` 中 `session_current()` 允许缺 session id 时使用 `current_session_info()`。 +- `rust/crates/mnote-web/src/routes/onlyoffice_bridge.rs` 中 `current_session_info()` 返回全局最近活跃 session。 +- Reasonix 只读复核 `reasonix-2026-05-31T16-38-36-472Z-63f3a41c` 判断:这不是写绕过,但属于低级信息泄漏风险;建议 `session_current` 也加入 scope 校验或过滤返回字段。 + +## 影响 + +- 不会直接写入或读取文档正文,因此风险低于 `7-45` / `7-46`。 +- 可能暴露非当前 target resource 的 Office session 元数据,给后续工具调用或模型选择目标带来混淆。 +- 与 OnlyOffice live bridge 的最小权限口径不一致。 + +## 修复建议 + +- `session_current` 若传 explicit session id,应校验该 session 对应的 `sessionId` / `documentId` / `assetId` / `resource:office:{documentId}:{assetId}` 是否在 `aiAccessScope.allowedResourceIds` 中。 +- `session_current` 若未传 explicit session id,不应返回全局最近活跃 session;可以改为返回 `mnote_onlyoffice_session_explicit_required`,或只在 debug/admin 边界允许。 +- 如果保留诊断 fallback,至少过滤 `documentId` / `assetId` 等跨 resource 元数据,并在 manifest 标注只用于诊断。 + +## 本轮处理 + +- 2026-06-01:`mnote.onlyoffice.session.current` 已改为复用 `resolve_explicit_session_id()` 与 `ensure_onlyoffice_resource_scope_allowed()`。 +- 已删除 `onlyoffice_bridge::current_session_info()` 全局最近 session fallback,避免诊断工具继续返回非当前 target 的 session 元数据。 +- `manifest.rs` 中 `mnote.onlyoffice.session.current` 已复用 OnlyOffice live 工具 schema,要求 `aiAccessScope.allowedResourceIds`,并通过 `anyOf` 要求 `onlyofficeSessionId` 或 `bridgeSessionId`。 +- `scripts/task515-onlyoffice-live-scope-http-smoke.js` 已扩展覆盖 `session.current` 缺 explicit session 返回 400、scope 不匹配返回 403、授权 scope 返回 session 元数据。 + +## 验收 + +- [x] Rust 单测覆盖 target=A 的 `aiAccessScope.allowedResourceIds` 不能通过 `session.current` 获取 resource=B 的 session 元数据。 +- [x] 缺 explicit session id 的 `session.current` 不再返回全局最近 session。 +- [x] `task515` HTTP smoke 覆盖 `session.current` scope 边界。 + +验证: + +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib hermes_tools_onlyoffice_session_current -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib hermes_tools_manifest_describes_onlyoffice_live_scope -- --test-threads=1` +- `node --check scripts/task515-onlyoffice-live-scope-http-smoke.js` +- `node scripts/task515-onlyoffice-live-scope-http-smoke.js` diff --git a/bugs/07-ai/done/7-51-mindmap-apply-ops-native-patch-only-v1.md b/bugs/07-ai/done/7-51-mindmap-apply-ops-native-patch-only-v1.md new file mode 100644 index 00000000..2bceda05 --- /dev/null +++ b/bugs/07-ai/done/7-51-mindmap-apply-ops-native-patch-only-v1.md @@ -0,0 +1,57 @@ +# 7-51 mindmap apply_ops 结构化写入与可见性缺口 + +## 状态 + +- 状态:done,工具层最小结构化写入、刷新后 embedded mindmap id 保真、真实 mindmap resource tab Page AI target 已修复;更丰富 `apply_ops` 指令面扩展另拆 follow-up,不阻塞本 bug 归档 +- Owner:07-ai / Hermes tools / mindmap resource +- 发现时间:2026-06-01 + +## 现象 + +`mnote.mindmap.apply_ops` 的非 dry-run 路径曾不执行结构化写入,而是在完成资源路径和写权限校验后返回 `mnote_resource_native_patch_required`,提示 agent 使用原生 patch 编辑授权文件。这与 `7-42` 第二批目标“把 `apply_ops` 从提示 agent patch 文件收口到结构化写入”不一致。 + +2026-06-01 本轮已完成工具层最小修复:`dryRun=false` 可执行 `updateText` / `updateNode`、`insertChild` / `addChild`、`deleteNode`,写入后返回 `revision`、`changedFiles`、`markdownSummary`,并保留 `view`、未知顶层字段和未知 node 字段。当前缺口转为浏览器可见性和 Page AI resource target 集成。 + +## 证据 + +- `rust/crates/mnote-web/src/hermes_tools/resource.rs` 的 `mindmap_apply_ops()` 已实现最小结构化写入。 +- `rust/crates/mnote-web/src/routes/hermes_tools.rs` 已有 `hermes_tools_mindmap_apply_ops_writes_and_preserves_envelope_fields`,覆盖非 dry-run 写入、`view` 和未知字段保留。 +- `rust/crates/mnote-web/src/routes/hermes_tools.rs` 已有 `hermes_tools_mindmap_apply_ops_rejects_stale_revision`,覆盖 `expectedRevision` 不匹配拒写。 +- `rust/crates/mnote-web/src/routes/hermes_tools.rs` 已有 `hermes_tools_mindmap_apply_ops_shared_read_is_forbidden`,说明 shared/read-only scope 拒写已经有单测证据,不是当前主要缺口。 +- `task455`、`task524`、`task525` 已覆盖 embedded mindmap 刷新、FileTree / Resource Tab 可见性和真实 Page AI mindmap target/contextRefs。 + +## 影响 + +- Agent 已可通过结构化 tool 安全执行最小增删改 mindmap node,不再只能回退到文件 patch。 +- `view`、未知字段保留和 revision conflict 已由工具层单测覆盖。 +- Page AI mindmap 第二批的最小可用链路已覆盖:tool 结构化写入、embedded mindmap 刷新、FileTree / Resource Tab 可见性,以及真实 Page AI mindmap target/contextRefs。后续缺口转为真实编辑需求下的更丰富 `apply_ops` 指令面。 + +## 修复建议 + +- 按真实编辑需求继续扩展 `apply_ops`,例如 `moveNode`、`insertSiblingAfter`、`setHyperlink`、`setRefs`、`appendNote`、`patchView`。 + +## 验收 + +- [x] Rust 单测覆盖 `apply_ops` 非 dry-run 结构化写入。 +- [x] Rust 单测覆盖 `view` 和未知字段保留。 +- [x] Rust 单测覆盖 revision mismatch 返回 conflict。 +- [x] Rust 单测覆盖 shared/read-only scope 下写工具拒绝。 +- [x] Browser smoke 覆盖 mindmap 资源在 File Tree / Resource Tab 可见。 +- [x] Browser smoke 覆盖真实 mindmap resource tab 注入 Page AI `active_editor` contextRef / `targetPackage`。 +- [x] `task455-local-folder-mindmap-clean-smoke.js` 刷新后 `mindmapId` 保持新建资源文件名,不退化为 `"mindmap"`。 + +## 本轮验证 + +- `cargo test -p mnote-web hermes_tools_mindmap_apply_ops -- --test-threads=1` +- `cargo test -p mnote-web mindmap -- --test-threads=1` +- 2026-06-01 复跑失败:`MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task455-local-folder-mindmap-clean-smoke.js` + - 失败点:`刷新后 mindmapId 应保持不变` + - 证据:`tmp/task455-local-folder-mindmap-clean-smoke/result.json` + - 关键现象:刷新前 `mindmapId=mindmap-647356316`,刷新后 DOM `mindmapId=mindmap`;FileTree 仍有 `local-file:CleanPage/mindmap-647356316.json` 行。 +- 2026-06-01 修复后复跑通过:`MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task455-local-folder-mindmap-clean-smoke.js` + - 修复点:`blockDocument.blocks[].attrs` 保留 `mindmapId/sourcePath/rootNodeId`,浏览器 `document-tiptap-conversion-runtime.js` 消费 blockDocument 时读取这些 attrs。 + - 补充验证:`cargo test -p bridge-runtime page_aggregate_block_document_preserves_mindmap_projection_attrs -- --test-threads=1`,`cargo test -p mnote-web local_markdown_generated_mindmap_id_roundtrips_as_mindmap_block -- --test-threads=1`。 + - 回归验证:`MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task524-workspace-object-identity-matrix-smoke.js`。 +- 2026-06-01 新增并通过:`MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task525-page-ai-mindmap-resource-target-smoke.js` + - 覆盖真实 local-folder mindmap resource tab 打开后,Page AI run payload 中 `contextRefs.active_editor.resourceKind=mindmap`。 + - 覆盖 `targetPackage.primaryTargetId`、`targetPackage.currentFile`、`allowedFiles` 指向 `Page/map.mindmap.json`。 diff --git a/bugs/07-ai/done/7-52-page-ai-target-picker-resource-target-gap-v1.md b/bugs/07-ai/done/7-52-page-ai-target-picker-resource-target-gap-v1.md new file mode 100644 index 00000000..ec901945 --- /dev/null +++ b/bugs/07-ai/done/7-52-page-ai-target-picker-resource-target-gap-v1.md @@ -0,0 +1,52 @@ +# 7-52 Page AI target picker 与 resource target 仍未闭环 + +## 状态 + +- 状态:done +- Owner:07-ai / Page AI sidebar runtime / Agent Target Resolver +- 发现时间:2026-06-01 + +## 现象 + +Page AI 已经有 agent selector、contextRefs popover、`targetPackage` 和 `allowedFiles` 基础输入。2026-06-01 已补上 page target、mindmap resource target、OnlyOffice resource target 与 raw local resource target 的 composer chip / picker / payload 冻结闭环;mindmap / raw file `active_editor` contextRef 均已携带 `targetId` / `objectIdentity` / `resourceKind` / `assetId` / `relativePath`。跨 workspace 多 target 的产品化确认继续由 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 跟踪,不再由本文阻塞 target picker 收口。 + +## 证据 + +- `rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` 已有 `currentPageAiOpenEditorsSnapshot()`、`currentPageAiEditorTarget()`、target chip / picker 和 `pageAiBuildAgentTargetPackage()`。 +- `scripts/task502-page-ai-agent-selector-context-smoke.js` 已覆盖 page target button / popover / chip,并通过 mock `resourceEditors` 覆盖 mindmap target 选择,断言 run payload 的 `targetPackage.primaryTargetId`、`targets[]`、`resourceKind=mindmap`、`relativePath=maps/Task502.mindmap.json`、`policy.writeRequiresExplicitTarget=true`;同时断言 `active_editor` contextRef 携带 mindmap `targetId` / `objectIdentity` / `resourceKind` / `assetId` / `relativePath`。 +- 2026-06-01 验证命令:`MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task502-page-ai-agent-selector-context-smoke.js`,截图:`tmp/task502-page-ai-agent-selector-context-smoke/01-agent-selector-context.png`。 +- `scripts/task453-local-folder-page-ai-changed-files-smoke.js` 覆盖 target snapshot 冻结、跨 workspace 阻断、dirty buffer 阻断,但不是 target picker UI。 +- `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 仍把 target chip/picker、跨 workspace 多选确认、真实 agent 写入回收列为未完成。 +- OnlyOffice 工具层 session/scope 已有 `task515` - `task518` 覆盖;`task523-page-ai-onlyoffice-real-target-session-smoke.js` 已补真实 Page AI target 到 `onlyofficeSessionId` / `bridgeSessionId` 的 UI 绑定。 +- `mnote-mindmap` skill 已注册,mindmap target 的 `active_editor` contextRef 已携带资源身份;`task525-page-ai-mindmap-resource-target-smoke.js` 已补真实 mindmap resource tab target / contextRefs 复测。 +- `scripts/task520-page-ai-raw-resource-target-smoke.js` 已补真实 raw local resource tab:打开 `Page/notes.txt` 后 Page AI target chip 指向 `notes.txt`,run payload 冻结 `targetPackage.primaryTargetId=resource:file:{rootUri}:Page/notes.txt`,`allowedFiles=["Page/notes.txt"]`,并断言 `objectIdentity` 不再退化成 `[object Object]`。 + +## 影响 + +- Page AI UI 已能清楚告诉用户当前 page / mindmap / OnlyOffice target。 +- Page AI target picker 已覆盖 page / mindmap / OnlyOffice / raw file 四类当前 P1 目标,不再退化为“当前页”语义。 +- 真实 agent 写入回收、跨 workspace 多 target 和 mindmap resource tab 自动注入的更大产品化项继续由 `7-18` / `7-42` / `7-51` 跟踪。 + +## 下一步建议 + +- 若继续扩多目标选择,新增跨 workspace 多 target 确认 smoke,并归入 `7-18`。 +- 若继续扩 mindmap 多目标或更细粒度 contextRefs,另拆 `7-42` / `7-51` follow-up,不再由本文阻塞 target picker 收口。 + +## 验收 + +- [x] Page AI composer 可见 page target chip。 +- [x] target picker 可在当前 page 与 mindmap resource tab 间切换。 +- [x] run payload 冻结 page `targetPackage.targets[0]`。 +- [x] run payload 冻结 mindmap resource `targetPackage.targets[0]`。 +- [x] OnlyOffice target 不会把 A 页授权误用于 B session。 +- [x] mindmap resource tab 可作为 `resourceKind=mindmap` target。 +- [x] mindmap `active_editor` contextRef 携带 `targetId` / `objectIdentity` / `resourceKind` / `assetId`。 +- [x] raw file target 可从真实 resource tab 候选选择并冻结到 run payload。 +- [x] 真实 mindmap resource tab target / contextRefs 已由 `task525` 覆盖。 + +## 本轮验证 + +- `node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` +- `node --check scripts/task520-page-ai-raw-resource-target-smoke.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task520-page-ai-raw-resource-target-smoke.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task502-page-ai-agent-selector-context-smoke.js` diff --git a/bugs/07-ai/done/7-53-page-ai-runtime-split-threshold-triggered-v1.md b/bugs/07-ai/done/7-53-page-ai-runtime-split-threshold-triggered-v1.md new file mode 100644 index 00000000..ef77ab3f --- /dev/null +++ b/bugs/07-ai/done/7-53-page-ai-runtime-split-threshold-triggered-v1.md @@ -0,0 +1,134 @@ +# 7-53 Page AI runtime 已触发继续拆分阈值 + +## 状态 + +- 状态:done +- Owner:07-ai / Page AI sidebar runtime +- 发现时间:2026-06-01 + +## 现象 + +`rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` 曾约 3458 行,已经超过 `design/07-ai/process/7-38-page-ai-sidebar-runtime-owner-split-v1.md` 中设定的 2500 行继续拆分阈值。文件内仍混合 UI render、run orchestration 等多类职责。 + +同时,`sidebar-tree-runtime.js` 已不再初始化 `pageUiState.pageAi*` 状态,Page AI 默认值由 `sidebar-page-ai-runtime.js` 的 `ensurePageAiStateFacade()` 集中兜底;但 tree runtime 仍传入共享 `pageUiState` 对象并保留少量 Page AI 代理函数,后续子模块拆分仍未闭环。 + +## 证据 + +- `sidebar-page-ai-runtime.js` 曾约 3458 行;2026-06-01 render helper 续补后降至 2498 行。 +- `sidebar-page-ai-markdown-runtime.js` 已承接 Page AI Markdown / conversation 文本渲染 helper。 +- `sidebar-page-ai-profile-runtime.js` 已承接 provider/profile/history filter/usage helper。 +- `sidebar-page-ai-permission-runtime.js` 已承接 ACP permission message/dialog/resolve helper。 +- `sidebar-page-ai-session-runtime.js` 已承接 session storage、backend session list/detail/search/resume/delete 和 runtime event replay helper。 +- `sidebar-page-ai-skill-runtime.js` 已承接 skill source、skill preference、Hermes builtin 隐藏和 Reasonix memory preference helper。 +- `sidebar-page-ai-target-runtime.js` 已承接 OpenEditorsSnapshot target 派生、WorkspacePath、run target snapshot、contextRefs、agentTargetPackage 和 target writable guard helper。 +- `sidebar-page-ai-runtime.js` 包含 `function ensurePageAiStateFacade`,并集中初始化 `pageAiAcpRuntime: 'reasonix'` 等 Page AI 默认状态。 +- `sidebar-tree-runtime.js` 不再包含 `pageAiAcpRuntime: 'reasonix'` 等 `pageAi*:` 默认字段,只保留 `open-page-ai` 主壳入口和代理函数。 +- `design/07-ai/process/7-38-page-ai-sidebar-runtime-owner-split-v1.md` 第 3 节仍将以下项列为未完成: + - 若 `sidebar-page-ai-runtime.js` 超过 2500 行,再按 render / conversation / run orchestration 继续拆成子模块。 + +## 影响 + +- Page AI 行为继续占用 tree runtime 委托面,tree/filetree 和 AI session owner 边界仍混杂。 +- 后续新增 target picker、OnlyOffice target、mindmap contextRefs 时,容易继续堆入单一大文件。 +- 浏览器事件委托散落在 tree runtime 与 Page AI runtime 之间,增加回归风险。 + +## 下一步建议 + +- 若后续再次超过 2500 行,继续按 conversation / run orchestration 分子模块拆,降低 `sidebar-page-ai-runtime.js` 单文件职责。 +- 继续补 Page AI smoke,覆盖停止、关闭、history/session、permission、profile/skill 切换等 owner 迁移风险面;停止/关闭尾项已拆到 `bugs/07-ai/process/7-56-page-ai-runtime-stop-close-smoke-gap-v1.md`。 + +## 验收 + +- [x] `sidebar-tree-runtime.js` 不再包含 `[data-page-ai-action=...]` click/change 分发。 +- [x] `sidebar-page-ai-runtime.js` 导出并安装 `installPageAiDelegates()`。 +- [x] `pageUiState.pageAi*` 从通用 sidebar state 下沉或有明确兼容 getter/setter。 +- [x] Page AI smoke 覆盖打开 drawer、切换 tab、切换 agent和发送;停止/关闭完整断言已拆到 `bugs/07-ai/process/7-56-page-ai-runtime-stop-close-smoke-gap-v1.md`,不阻塞本文拆分阈值归档。 + +## 2026-06-01 第一刀验证 + +已完成第一刀:`sidebar-tree-runtime.js` 中 Page AI click/input/change/keydown 的大段 action 分发已迁到 `sidebar-page-ai-runtime.js` 的 `handlePageAiClick`、`handlePageAiKeyDown`、`handlePageAiInput`、`handlePageAiChange`,并由 `installPageAiDelegates()` 在 Page AI owner runtime 内安装事件监听。tree runtime 仅保留 `data-mnote-action="open-page-ai"` 主壳入口。 + +已通过验证: + +- `node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` +- `node --check rust/crates/mnote-web/browser/sidebar-tree-runtime.js` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_agent_target_picker_contract_is_visible_and_serialized -- --test-threads=1` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task490-runtime-surfaces-smoke.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task502-page-ai-agent-selector-context-smoke.js` + +2026-06-01 续补: + +- `sidebar-page-ai-runtime.js` 新增并导出 `installPageAiDelegates()`,内部一次性安装 Page AI click/keydown/input/change delegate。 +- `sidebar-tree-runtime.js` 不再调用 `sidebarPageAi.handlePageAi*`,只在自身监听注册后调用 `sidebarPageAi.installPageAiDelegates()`。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证 drawer、agent/context/target/skills 和发送 payload 链路仍可用。 + +2026-06-01 state facade 续补: + +- `sidebar-page-ai-runtime.js` 新增 `ensurePageAiStateFacade()`,集中初始化 Page AI 默认状态并导出该 facade。 +- `sidebar-tree-runtime.js` 的 `pageUiState` 初始对象删除 `pageAi*:` 默认字段,tree runtime 不再定义 Page AI 状态真相。 +- Rust include/assert 已更新为:Page AI runtime 包含 `ensurePageAiStateFacade` 与 `pageAiAcpRuntime: 'reasonix'`,tree runtime 不包含该默认字段。 + +2026-06-01 conversation helper 续补: + +- 新增 `sidebar-page-ai-markdown-runtime.js`,抽出 `textFromUnknown`、`renderPageAiMarkdown` 和 inline Markdown 渲染 helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiMarkdownRuntime()`,保留现有 assistant message 渲染行为。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-markdown-runtime.js` 静态 runtime asset,并更新 runtime asset mount 测试。 + +2026-06-01 profile helper 续补: + +- 新增 `sidebar-page-ai-profile-runtime.js`,抽出 provider/profile/chat-only profile/history filter/usage helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiProfileRuntime()` 并保留同名代理常量,降低调用点扰动。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-profile-runtime.js` 静态 runtime asset,并更新 runtime asset mount 测试。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证二级 import 后 drawer、agent/context/target/skills 和发送 payload 链路仍可用。 + +2026-06-01 permission helper 续补: + +- 新增 `sidebar-page-ai-permission-runtime.js`,抽出 ACP permission message、dialog show/hide 和 resolve-permission helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiPermissionRuntime()` 并保留同名代理常量,主 runtime 继续负责事件流持久化、会话同步和 conversation 渲染入口。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-permission-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证新增 permission 二级 import 后 sidebar/Page AI 仍可加载和发送。 + +2026-06-01 session helper 续补: + +- 新增 `sidebar-page-ai-session-runtime.js`,抽出 session storage、backend session list/detail/search/resume/delete、session message sync 和 backend runtime event replay helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiSessionRuntime()` 并保留同名代理常量,主 runtime 继续负责 Page AI UI render、run orchestration 和事件委托入口。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-session-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证新增 session 二级 import 后 Page AI drawer、agent/context/target/skills 和发送 payload 链路仍可用。 + +2026-06-01 skill helper 续补: + +- 新增 `sidebar-page-ai-skill-runtime.js`,抽出 skill source、skill preference、Hermes builtin 隐藏和 Reasonix memory preference helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiSkillRuntime()` 并保留同名代理常量,主 runtime 继续负责 skill 异步加载、target/run orchestration 和 UI render。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-skill-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言。 +- 已通过 `node --check` 覆盖 Page AI 主 runtime、skill/session/permission/profile/markdown helper 和 `sidebar-tree-runtime.js`。 +- 已通过 `cargo test --manifest-path rust/Cargo.toml -p mnote-web mnote_browser_runtime_assets_are_explicitly_mounted -- --test-threads=1`、`page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch`、`page_ai_agent_target_picker_contract_is_visible_and_serialized`。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证新增 skill 二级 import 后 Page AI drawer、agent/context/target/skills 和发送 payload 链路仍可用。 + +2026-06-01 target helper 续补: + +- 新增 `sidebar-page-ai-target-runtime.js`,抽出 OpenEditorsSnapshot target 派生、WorkspacePath、run target snapshot、contextRefs、agentTargetPackage 和 target writable guard helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiTargetRuntime()` 并保留同名代理常量,主 runtime 继续负责 target popover UI、事件分发和 run orchestration 顺序。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-target-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言;target picker 合同测试改为在 target helper 中断言 `primaryTargetId` / `targets` / `policy`。 +- 已通过 `node --check` 覆盖 Page AI 主 runtime、target/skill/session/permission/profile/markdown helper 和 `sidebar-tree-runtime.js`。 +- 已通过 `cargo test --manifest-path rust/Cargo.toml -p mnote-web mnote_browser_runtime_assets_are_explicitly_mounted -- --test-threads=1`、`page_ai_uses_backend_acp_session_runtime_store`、`page_ai_agent_target_picker_contract_is_visible_and_serialized`、`page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch`。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js`、`task502-page-ai-agent-selector-context-smoke.js`、`task520-page-ai-raw-resource-target-smoke.js`、`task525-page-ai-mindmap-resource-target-smoke.js`,验证新增 target 二级 import 后 Page AI drawer、agent/context/target/skills、raw resource target、mindmap target 和发送 payload 链路仍可用。 + +2026-06-01 render helper 续补: + +- 新增 `sidebar-page-ai-render-runtime.js`,抽出 target/context display helper、agent/profile/model label、skill filter、drawer shell、controls render、suggestions render、conversation render 和 response humanize helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiRenderRuntime()`,保留同名转发函数,主 runtime 继续负责 state facade、agent/run orchestration、session/permission/skill/target runtime 接线与事件委托。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-render-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言,避免浏览器 ES module import 404。 +- `sidebar-page-ai-runtime.js` 当前 2498 行,已低于 `7-38` 设定的 2500 行继续拆分阈值。 +- 已通过 `node --check` 覆盖 Page AI 主 runtime、render/target/skill/session/permission/profile/markdown helper 和 `sidebar-tree-runtime.js`。 +- 已通过 `cargo test --manifest-path rust/Cargo.toml -p mnote-web mnote_browser_runtime_assets_are_explicitly_mounted -- --test-threads=1`、`page_ai_uses_backend_acp_session_runtime_store`、`page_ai_agent_target_picker_contract_is_visible_and_serialized`、`page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch`。 + +最终验证: + +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task490-runtime-surfaces-smoke.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task502-page-ai-agent-selector-context-smoke.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task504-page-ai-history-agent-filter-smoke.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task520-page-ai-raw-resource-target-smoke.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task525-page-ai-mindmap-resource-target-smoke.js` + +本文已满足归档条件:`sidebar-page-ai-runtime.js` 当前 2498 行,低于 2500 行继续拆分阈值;render helper 二级 import、drawer controls、history/session filter、当前页 target、raw resource target、mindmap target 和发送 payload 链路均通过 smoke。 diff --git a/bugs/07-ai/done/7-54-page-ai-onlyoffice-target-buffer-state-404-v1.md b/bugs/07-ai/done/7-54-page-ai-onlyoffice-target-buffer-state-404-v1.md new file mode 100644 index 00000000..c9803668 --- /dev/null +++ b/bugs/07-ai/done/7-54-page-ai-onlyoffice-target-buffer-state-404-v1.md @@ -0,0 +1,33 @@ +# 7-54 Page AI Office target 误查 Markdown buffer-state + +## 状态 + +- 状态:done +- Owner:07-ai / Page AI target runtime / OnlyOffice resource target +- 发现时间:2026-06-01 +- 修复时间:2026-06-01 + +## 现象 + +真实 Page AI UI 选择 Office resource target 后,发送 run 前的 buffer guard 会按 Office 文件相对路径请求 `/api/documents/buffer-state`: + +```text +/api/documents/buffer-state?...&relativePath=Page/office-a.docx +``` + +该端点只服务 local Markdown 文档 buffer,Office target 会返回 404。虽然当前 404 没有阻断 run,但会污染 console / HTTP error 证据,并说明 Page AI 把 Office resource 当成 Markdown buffer 检查。 + +## 根因 + +`rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` 的 `fetchPageAiTargetBufferState()` 只判断 `sourceKind=local_folder`,没有区分 target `resourceKind`。Office target 已由 OnlyOffice bridge session、resource scope 和 tool 层权限保护,不应走 Markdown `BufferStore` 查询。 + +## 修复 + +- `fetchPageAiTargetBufferState()` 对 `office` / `only_office` / `onlyoffice` target 直接返回 `null`。 +- `scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js` 增加断言:真实 Page AI Office target run 过程中不得出现 `/api/documents/buffer-state` 404,并要求 `consoleErrors` / `networkFailures` / `httpErrors` 为空。 + +## 验收 + +- `node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` +- `node --check scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js` diff --git a/bugs/07-ai/done/7-55-page-ai-current-page-target-stale-workspace-v1.md b/bugs/07-ai/done/7-55-page-ai-current-page-target-stale-workspace-v1.md new file mode 100644 index 00000000..ec1cccf5 --- /dev/null +++ b/bugs/07-ai/done/7-55-page-ai-current-page-target-stale-workspace-v1.md @@ -0,0 +1,32 @@ +# 7-55 Page AI 当前页目标继承 stale workspacePath + +## 状态 + +- 状态:done +- Owner:07-ai / Page AI target runtime +- 发现时间:2026-06-01 +- 修复时间:2026-06-01 + +## 现象 + +`task504-page-ai-history-agent-filter-smoke.js` 在模拟 stale `OpenEditorsSnapshot` 后,用户偏好已将 `active_editor` contextRef 关闭,只发送当前页上下文;但 `sendPageAiMessage()` 仍强制把 `scopedContext.editorTarget` 覆盖为 `currentPageAiEditorTarget()`,导致当前页请求被旧 active editor 的 `workspaceId` 拦截,前端显示: + +`AI target 与当前 workspaceId 不一致,请重新选择当前工作区内的目标。` + +## 根因 + +- `pageAiScopedPageContext()` 已按 contextRefs 计算 scoped target,但发送链路又覆盖为 `currentPageAiEditorTarget()`。 +- `currentPageAiPageEditorTarget()` 从 page editor snapshot 读取 `workspacePath` 时,没有把当前 `workspaceId/sourceKind/rootUri/relativePath/documentId` 写回,stale snapshot 会把旧 workspace 信息带进当前页目标。 + +## 修复 + +- `sendPageAiMessage()` 改用 `currentPageAiScopedEditorTarget()`,遵守 contextRefs 对 active editor 的开关。 +- `currentPageAiPageEditorTarget()` 在复用 page editor snapshot 时显式覆盖当前 workspace path 字段,避免 stale snapshot 污染当前页 target。 + +## 验证 + +- `node --check rust/crates/mnote-web/browser/sidebar-page-ai-target-runtime.js` +- `node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task504-page-ai-history-agent-filter-smoke.js` + +验证结果:`task504` 通过,`capturedRuns[0].contextRefs` 只包含 `current_page`,`runTargetSnapshot.editorTarget.workspaceId` 为当前 `local-ws:mnote-e2e:task504`,未继续使用 stale workspaceId。 diff --git a/bugs/07-ai/done/7-56-page-ai-runtime-stop-close-smoke-gap-v1.md b/bugs/07-ai/done/7-56-page-ai-runtime-stop-close-smoke-gap-v1.md new file mode 100644 index 00000000..91f941fa --- /dev/null +++ b/bugs/07-ai/done/7-56-page-ai-runtime-stop-close-smoke-gap-v1.md @@ -0,0 +1,49 @@ +# 7-56 Page AI runtime stop / close smoke 缺口 + +## 状态 + +- 状态:done +- Owner:07-ai / Page AI runtime +- 发现时间:2026-06-01 + +## 现象 + +`7-53` 已完成 Page AI runtime 拆分并把 `sidebar-page-ai-runtime.js` 降到 2500 行阈值以下,但停止运行与关闭抽屉的浏览器断言仍不完整。 + +## 证据 + +- `bugs/07-ai/done/7-53-page-ai-runtime-split-threshold-triggered-v1.md` 已记录拆分完成,但验收中仍保留 stop/close smoke 的 `[~]` 项。 +- `scripts/task490-runtime-surfaces-smoke.js` 会打开并点击关闭 Page AI 抽屉,但缺少明确的 drawer hidden / closed state 断言。 +- 现有 smoke 未覆盖 `data-page-ai-action="stop-run"` 到 abort API / terminal event 的端到端行为。 + +## 影响 + +- Page AI runtime 拆分后,基础打开/发送/target picker 已有保护,但 stop/close 这类常用交互仍可能在后续拆分中回退。 +- `7-53` 虽然可作为拆分阈值 bug 归档,但停止/关闭行为应继续作为独立 P2 测试缺口跟踪。 + +## 下一步 + +1. 扩展现有 `task490` 或新增窄 smoke,断言关闭后 Page AI drawer 进入隐藏状态,且页面根状态同步清除。 +2. 增加 stop-run smoke:启动可控流式 run,点击停止,断言 abort API 被调用并收到 terminal/aborted 状态。 +3. 将验证命令补入 `scripts/TESTING_REFERENCE.md`。 + +## 验收 + +- [x] 浏览器 smoke 明确断言 Page AI drawer close 后不可见。 +- [x] 浏览器 smoke 覆盖 stop-run -> abort/terminal event。 +- [x] `node --check` 与对应 smoke 通过。 + +## 2026-06-01 修复记录 + +- `scripts/task490-runtime-surfaces-smoke.js` 已扩展 Page AI close 断言:关闭后 drawer 必须 `hidden=true`,浮动 AI 按钮 `data-state=closed` 且 `aria-expanded=false`。 +- `task490` 已新增可控 mock run:发送后等待 run 进入 `running`,点击 `data-page-ai-action="stop-run"`,断言 abort API 被调用一次、请求 reason 为 `page_ai_user_stop`,并等待 UI 进入 `aborted` 状态。 +- 修复 Page AI render helper 拆分漏注入:`sidebar-page-ai-render-runtime.js` 使用 `pageAiProviderLabel()` 渲染 provider card,但 `sidebar-page-ai-runtime.js` 未传入该 helper;已补 context 注入。 +- 已通过: + - `node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` + - `node --check rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js` + - `node --check scripts/task490-runtime-surfaces-smoke.js` + - `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task490-runtime-surfaces-smoke.js` + +截图: + +- `tmp/task490-runtime-surfaces-smoke/02b-page-ai-stopped.png` diff --git a/bugs/07-ai/done/7-57-onlyoffice-plugin-bridge-p2-productization-tail-v1.md b/bugs/07-ai/done/7-57-onlyoffice-plugin-bridge-p2-productization-tail-v1.md new file mode 100644 index 00000000..f5b7763f --- /dev/null +++ b/bugs/07-ai/done/7-57-onlyoffice-plugin-bridge-p2-productization-tail-v1.md @@ -0,0 +1,80 @@ +# 7-57 OnlyOffice Plugin Bridge P2 产品化尾项 + +## 状态 + +- 状态:done +- Owner:07-ai / ONLYOFFICE live bridge / Page AI target runtime +- 发现时间:2026-06-01 +- 归档时间:2026-06-01 + +## 现象 + +OnlyOffice live bridge 的 P0 安全阻断已完成:显式 session、resource scope、真实 iframe 多 session 隔离、非 dry-run 写入落点和 Page AI target picker 到 live session 的绑定均已有 smoke 证据。但 `7-43` 仍保留若干 P2 产品化尾项,缺少独立 bug 落点。 + +## 证据 + +- `design/07-ai/process/7-43-onlyoffice-plugin-bridge-design-v1.md` 仍保留 P2 follow-up:Office 主文档加载噪音治理、第三批 recipe 逐项实测。`session/current`、`session/close` 与 session state `docKey/pageOrigin` 已在 2026-06-01 续补完成。 +- 已有 smoke 证明安全主路径完成: + - `scripts/task515-onlyoffice-live-scope-http-smoke.js` + - `scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js` + - `scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js` + - `scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` + - `scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js` + +## 影响 + +- P0 安全问题已经不再阻塞;P2 recipe 扩展已收窄为 `7-43` 的矩阵化后续,不再作为本文 bug 阻塞项。 +- `7-43` 已区分已完成安全项和后续产品化项。 + +## 下一步 + +1. 记录并治理 Office 主文档加载噪音,例如 DocumentServer `errorCode=-18`、WebSocket / polling 失败等。(已完成) +2. 第三批 recipe 必须逐项实测后再暴露给 agent,不凭 API 名称直接开放。(已收窄为矩阵规则) + +## 验收 + +- [x] `session/current` 与 `session/close` API 已实现并有 Rust route 测试。 +- [x] session state 已补 `docKey` / `pageOrigin`,并由 plugin config / register session / list sessions smoke 证明透传。 +- [x] Office 加载噪音有明确分类、截图或日志证据,并不误判为打开成功。 +- [x] 每个已暴露 recipe 保留 Rust unit、browser direct smoke 和 tool API smoke 要求;未暴露第三批 recipe 已明确不对 agent 宣称可用。 +- [x] 插件执行层已补 editorType guard,`document.*` / `sheet.*` / `presentation.*` 不能跨 Word / Excel / PPT session 串用。 +- [x] `design/07-ai/process/7-43...` 中 P0 已完成项与 P2 尾项拆分清晰。 + +## 2026-06-01 session lifecycle 续补记录 + +- `rust/crates/mnote-web/src/routes/onlyoffice_bridge.rs` 已新增 `current_session` / `close_session`,并把 `docKey` / `pageOrigin` 纳入 `BridgeSessionState`、`BridgeSessionPayload`、`BridgeSessionInfo` 和 plugin index state。 +- `/onlyoffice` 页面会把当前 `fileState.docKey` 与 `location.origin` 传给 bridge plugin config;mock plugin direct smoke 已验证 plugin 注册后 session 中存在 `docKey/pageOrigin`。 +- `scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js` 已扩展 `session/current`、`session/close` 和关闭后 session 不再可取 command 的断言。 +- 已通过: + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1` + - `node scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js` + - `MNOTE_UI_BASE_URL=http://127.0.0.1:3302 node scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js` + +## 2026-06-01 噪音分类与主文档失败断言 + +- `scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` 已新增 `classifyOnlyOfficeSignals`,把 bridge plugin translation 404、ONLYOFFICE 内置插件噪音和主文档加载失败分开记录。 +- smoke 会断言 MNote bridge session 注册与 command loop 可用,同时要求 `mainDocument.ready=true`。 +- `errorCode=-18` 不再被当作可忽略噪音;一旦在 console 或 editor error log 中出现,`task518` 会失败并把它归为主文档失败。 +- `design/07-ai/process/7-43-onlyoffice-plugin-bridge-design-v1.md` 头部口径已声明 P0 session/scope 安全阻断完成,本文和 `7-43` 只继续跟踪 P2 recipe 扩展与产品化尾项。 + +本文继续保持 `process` 的剩余原因: + +- 已归档。第三批 recipe(Word 图片/修订/content controls/表格行列样式、Excel 筛选/工作表删除移动、PPT 图片/重排/主题布局/shape 样式位置、PDF/forms)仍需逐项实测后再开放给 agent,但这些是后续 `7-43` 矩阵项,不再作为本文 bug 阻塞。 + +## 2026-06-01 归档记录 + +Recipe 产品化矩阵: + +| 类别 | 当前状态 | 归档口径 | +| --- | --- | --- | +| Word 基础读写 / 表格 / 评论 | exposed-with-tests | 已暴露,继续要求 Rust unit、browser direct smoke、tool API smoke。 | +| Excel sheets / range / format / dimensions / sort / chart | exposed-with-tests | 已暴露,Excel sort/chart 已纳入当前 capabilities。 | +| PPT slide texts / shapes / replace / delete / table / clear / shape | exposed-with-tests | 已暴露,PPT table/clear/shape 已纳入当前 capabilities。 | +| Word 图片 / 修订 / content controls / 表格增删行列和样式 | not-exposed | 仅作为候选,不向 agent 宣称可用。 | +| Excel 筛选 / 工作表删除移动 | not-exposed | 仅作为候选,不向 agent 宣称可用。 | +| PPT 图片 / slide 重排 / 主题布局 / shape 样式位置 | not-exposed | 仅作为候选,不向 agent 宣称可用。 | +| PDF / forms 字段读取与填写 | blocked-by-api | 需先确认 ONLYOFFICE 社区版 Plugin API 可行性。 | + +验证: + +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1` diff --git a/bugs/07-ai/done/7-58-mindmap-p2-apply-ops-and-ui-smoke-gap-v1.md b/bugs/07-ai/done/7-58-mindmap-p2-apply-ops-and-ui-smoke-gap-v1.md new file mode 100644 index 00000000..7c1ac1a1 --- /dev/null +++ b/bugs/07-ai/done/7-58-mindmap-p2-apply-ops-and-ui-smoke-gap-v1.md @@ -0,0 +1,57 @@ +# 7-58 Mindmap P2 apply_ops 与真实 UI smoke 缺口 + +## 状态 + +- 状态:done +- Owner:07-ai / mindmap resource skill / Page AI target runtime +- 发现时间:2026-06-01 + +## 现象 + +Mindmap skill 和资源 target 已有最小闭环:`fetch`、`create_from_outline`、`apply_ops` 基础工具、`mnote-mindmap` skill 注册、`task503` 和 `task525` smoke 已覆盖 API/target 绑定。但 P2 第二批仍缺 apply_ops 操作白名单文档化、长文本节点策略和真实 UI 交互 smoke。 + +## 证据 + +- `rust/crates/mnote-web/src/hermes_tools/resource.rs` 已有 mindmap `fetch/apply_ops/create_from_outline` 入口。 +- `scripts/task503-mindmap-skill-capability-smoke.js` 覆盖 skill、create_from_outline、fetch、embed。 +- `scripts/task525-page-ai-mindmap-resource-target-smoke.js` 覆盖 mindmap resource tab / Page AI targetPackage。 +- 当前缺口未在独立 bugs/process 中跟踪;历史相关 bug `7-48/7-51/7-52` 已作为各自窄问题归档。 + +## 影响 + +- agent 可以调用 mindmap 工具,但不清楚 `apply_ops` 的稳定操作白名单和限制。 +- 长文本节点可能破坏 mindmap 渲染或 agent 输出可读性。 +- 现有 smoke 偏 API/target 合同,未覆盖用户真实 UI 交互,例如节点编辑、拖拽、导出等。 + +## 下一步 + +1. 已文档化 `apply_ops` 支持的操作白名单、payload schema、dry-run/revision 规则和失败形态。 +2. 已为关键 ops 增加 Rust 单测,覆盖成功、别名、delete、dry-run、revision mismatch、越权/只读拒绝、unsupported op、invalid ops payload、禁止删除 root。 +3. 已在 skill prompt 中明确长文本节点压缩策略:短语化、避免长段落,拆成 child nodes。 +4. 已用真实 mindmap UI smoke 覆盖打开、插入、渲染、样式抽屉默认状态、slash 菜单层级和 resize 基础交互。 + +## 验收 + +- [x] `apply_ops` 白名单与限制写入 design / skill。 +- [x] `hermes_tools_mindmap_apply_ops_*` targeted tests 覆盖核心操作。 +- [x] 长文本节点策略有测试或明确降级说明。 +- [x] 至少一条真实 mindmap UI browser smoke 通过并登记到 `scripts/TESTING_REFERENCE.md`。 + +## 2026-06-01 修复记录 + +- `skills/mnote-mindmap/SKILL.md` 已新增 `Current apply_ops contract`,明确支持 `updateText/updateNode`、`insertChild/addChild`、`deleteNode`,记录 `nodeId/id`、`text/title`、`parentId/nodeId` 等 payload schema、未知 op 拒绝、禁止删除 root、长文本短语化/拆子节点、`dryRun` 与 revision checks。 +- `rust/crates/mnote-web/src/routes/hermes_tools.rs` 已补齐 apply_ops targeted tests: + - `hermes_tools_mindmap_apply_ops_accepts_common_aliases` + - `hermes_tools_mindmap_apply_ops_deletes_child_node` + - `hermes_tools_mindmap_apply_ops_dry_run_returns_diff_without_writing` + - `hermes_tools_mindmap_apply_ops_rejects_invalid_ops_payload` + - `hermes_tools_mindmap_apply_ops_rejects_root_delete` + - `hermes_tools_mindmap_apply_ops_rejects_unsupported_op` +- 已通过: + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_mindmap_apply_ops -- --test-threads=1` + - `node --check scripts/task455-local-folder-mindmap-clean-smoke.js` + - `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3303 node scripts/task455-local-folder-mindmap-clean-smoke.js` + +真实 UI smoke 结果: + +- `tmp/task455-local-folder-mindmap-clean-smoke/result.json` diff --git a/bugs/07-ai/process/7-49-chatonly-openclaw-provider-unit-test-gap-v1.md b/bugs/07-ai/process/7-49-chatonly-openclaw-provider-unit-test-gap-v1.md new file mode 100644 index 00000000..0848fe3b --- /dev/null +++ b/bugs/07-ai/process/7-49-chatonly-openclaw-provider-unit-test-gap-v1.md @@ -0,0 +1,98 @@ +# 7-49 ChatOnly OpenClaw provider 单测缺口 + +## 状态 + +- 状态:process +- Owner:07-ai / OpenClaw provider integration +- 发现时间:2026-06-01 + +## 现象 + +`7-44` 的 MNote 侧 ChatOnly provider conversation binding 已完成并通过真实浏览器 smoke。外部 OpenClaw 仓库已经补齐 Doubao provider 的 conversation id 抽取与 delete helper 单测,但 DeepSeek / Gemini provider 级 delete helper 与单测仍没有可靠实现落点,不能在 MNote 仓库内用集成 smoke 替代。 + +## 已有 MNote 侧证据 + +- `cargo test --manifest-path rust/Cargo.toml -p control-plane external_conversation -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib provider_conversation -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib chatonly_doubao_session_delete_calls_provider_and_marks_remote_deleted -- --test-threads=1` +- `node scripts/task512-chatonly-doubao-sync-smoke.js` +- `node scripts/task513-chatonly-provider-sync-smoke.js deepseek` +- `node scripts/task513-chatonly-provider-sync-smoke.js gemini` + +## 缺口 + +- OpenClaw Doubao provider 的 `conversation_id` 捕获、`deleteConversation` / `/im/conversation/batch_del_user_conv` 已有 provider 仓库内单测。 +- DeepSeek / Gemini provider delete helper 仍缺 provider 仓库内实现与单测;如果没有可靠远端删除 API,不应伪造 `remote_deleted`。 + +## 2026-06-01 外部仓库复核 + +- 本机存在 `/home/lix/openclaw-zero-token-latest`,但该仓库已有未提交改动: + - `src/zero-token/providers/doubao-web-client-browser.ts` + - `src/zero-token/providers/gemini-web-client-browser.ts` + - `src/zero-token/streams/doubao-web-stream.ts` + - `src/zero-token/providers/web-session-binding.test.ts` +- 只读核对显示,现有 `web-session-binding.test.ts` 覆盖 Doubao `conversation_id` SSE 提取和 Gemini URL / stale candidate,但未覆盖 `deleteConversation`、`/im/conversation/batch_del_user_conv`、`remote_deleted` / `remote_delete_failed`。 +- 尝试运行 `pnpm exec vitest run src/zero-token/providers/web-session-binding.test.ts` 与 `pnpm exec vitest run --config vitest.unit.config.ts src/zero-token/providers/web-session-binding.test.ts` 均因缺少 `@vitest/browser-playwright` 启动失败;`scripts/vitest.zero-token-web.config.ts` 当前 include 不包含 provider test。 + +结论:本缺口仍是 OpenClaw 外部仓库未完成项,不应在 MNote 仓库内标记 done。 + +## 下一步 + +在 OpenClaw provider 源码仓库中继续补 DeepSeek / Gemini provider 删除能力,覆盖: + +- 删除成功返回 `remote_deleted`。 +- 删除失败返回 `remote_delete_failed` 且脱敏错误。 + +## 2026-06-01 外部仓库二次复核 + +只读复核 `/home/lix/openclaw-zero-token-latest` 后确认本文仍保持 `process`,不能在 MNote 仓库内归档: + +- 外部仓库已有未提交/未跟踪改动,包含 `src/zero-token/providers/doubao-web-client-browser.ts`、`src/zero-token/providers/gemini-web-client-browser.ts`、`src/zero-token/streams/doubao-web-stream.ts`、`src/zero-token/providers/web-session-binding.test.ts`,本轮不得覆盖或清理。 +- Doubao stream/client 代码已有 conversation id 捕获和复用路径,但 provider 单测仍只覆盖 SSE 提取、Gemini URL normalize 和 stale candidate;未覆盖 Doubao 复用、`deleteConversation` 成功/失败。 +- `rg deleteConversation` 在 provider/streams 下未发现 DeepSeek/Gemini delete helper 可见测试。 +- 默认 `pnpm exec vitest run src/zero-token/providers/web-session-binding.test.ts --reporter=dot` 仍会因根 `vitest.config.ts` 导入但本机缺少 `@vitest/browser-playwright` 失败;`scripts/vitest.zero-token-web.config.ts` 又未 include provider test,强行指定 provider 文件会 `No test files found`。 + +结论:MNote 侧 ChatOnly/Doubao binding smoke 可作为 MNote 集成证据,但 `7-49` 的 owner 是 OpenClaw provider 仓库单测缺口,仍需在外部仓库补测试配置和 provider 单测后才能移动到 `done`。 + +## 2026-06-01 外部仓库三次推进 + +在 `/home/lix/openclaw-zero-token-latest` 已补并验证 Doubao provider 级测试: + +- `src/zero-token/providers/doubao-web-client-browser.ts` + - 新增 `ProviderConversationDeleteResult`、`DoubaoConversationDeleteRequest`、`buildDoubaoConversationDeleteRequest`、`deleteDoubaoConversationWithFetch` 和 `DoubaoWebClientBrowser.deleteConversation`。 + - 删除请求固定走 `/im/conversation/batch_del_user_conv`,失败响应会脱敏 `sessionid` / `ttwid`。 +- `src/zero-token/providers/web-session-binding.test.ts` + - 覆盖 Doubao `conversation_id` / `conversationId` / `sessionId` / object `event_data` 抽取。 + - 覆盖 `conversation_id:"0"`、malformed SSE、`[DONE]` 跳过。 + - 覆盖 batch delete request、`remote_deleted` 成功、`remote_delete_failed` 脱敏失败。 +- `scripts/vitest.zero-token-web.config.ts` + - 已 include provider test,并补 `openclaw/plugin-sdk/*` alias,避免根 `vitest` 多项目配置缺 `@vitest/browser-playwright` 阻断 provider 单测。 + +已通过: + +- `pnpm exec vitest run --config scripts/vitest.zero-token-web.config.ts src/zero-token/providers/web-session-binding.test.ts --reporter=dot` +- `pnpm exec vitest run --config scripts/vitest.zero-token-web.config.ts --reporter=dot` + +Reasonix 只读复核(`reasonix-2026-06-01T00-32-52-539Z-c41a37a3`)结论与主线程复核一致: + +- Doubao 是当前唯一有 provider 级 `deleteConversation` / `remote_deleted` / `remote_delete_failed` helper 与单测的 web provider。 +- DeepSeek / Gemini 只有 stream 层 `sessionMap` / `conversationMap` 复用,没有 provider 级删除能力。 +- Qwen、GLM、Kimi、Grok、Claude、ChatGPT、XiaoMiMo、Perplexity 等其他 web provider 也未见 provider 级 delete helper;这些不是 `7-44` Doubao 绑定的阻断项,但应作为后续 OpenClaw provider cleanup 矩阵继续跟踪。 +- `pnpm exec vitest run --config scripts/vitest.zero-token-web.config.ts --reporter=dot` 在外部仓库通过,3 files / 14 tests passed。 + +本文继续保持 `process` 的剩余原因: + +- DeepSeek provider 当前没有 `deleteConversation` / delete helper;不能从 MNote 侧 `task513` 推断 provider 仓库已有可靠远端删除实现。 +- Gemini provider 当前只有 DOM 对话打开和 conversation URL normalize / stale candidate 测试;没有 provider 级远端删除 helper。 +- 后续需要先核实 DeepSeek / Gemini 真实远端删除 API 或明确降级语义,再补 provider 单测;不能为了归档而把本地删除伪装成 `remote_deleted`。 + +## 2026-06-01 subagent 复核确认 + +只读 subagent 复核 `/home/lix/openclaw-zero-token-latest` 后确认本文继续保持 `process`: + +- Doubao provider delete/helper/test 与 Vitest 专用配置已补齐。 +- `rg deleteConversation|remote_deleted|remote_delete_failed|batch_del_user_conv src/zero-token/providers src/zero-token/streams` 仍只命中 Doubao 和 `web-session-binding.test.ts`,未命中 DeepSeek / Gemini。 +- DeepSeek 当前只有 `createChatSession` / `chatCompletions` 和 stream `sessionMap` 复用。 +- Gemini 当前只有 URL normalize、DOM 打开/复用对话和 `conversation_id: page.url()`,没有 provider 级远端删除 helper。 + +因此 MNote 侧 `7-44` / `task512` / `task513` 只能证明 MNote provider conversation binding,不足以替代外部 provider 仓库的 DeepSeek / Gemini 删除单测。 diff --git a/bugs/10-review/done/10-18-design-process-status-and-stale-entry-v1.md b/bugs/10-review/done/10-18-design-process-status-and-stale-entry-v1.md new file mode 100644 index 00000000..773923e1 --- /dev/null +++ b/bugs/10-review/done/10-18-design-process-status-and-stale-entry-v1.md @@ -0,0 +1,45 @@ +# 10-18 design process 状态与入口漂移 + +## 状态 + +- 状态:done +- Owner:10-review / design governance +- 发现时间:2026-05-31 + +## 现象 + +当前 design 主入口和若干 `process/` 文档状态已经与真实口径不一致,容易误导后续 worker 重复执行已完成任务,或从已迁移到 `old/` 的历史文档读取优先级。 + +## 证据 + +- `design/README.md` 曾把 `design/01-05-current-priority-overview.md` 写成当前优先级入口,但实际文件已在 `design/old/01-05-current-priority-overview.md`。 +- `design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md` 曾在上位依据中引用同一个已迁移文件,并仍引用已归档的 `3-3` / `3-18` 作为 active process 入口。 +- `design/03-rust-web/process/3-24-global-content-type-page-width-settings-v1.md` checklist 全部完成,曾仍位于 `process/`;2026-05-31 已迁入 `design/03-rust-web/done/`。 +- `design/05-editor-mainline/process/5-31-page-settings-sqlite-preference-convergence-v1.md` 已记录 Phase A/B/C/D 最小闭环完成,但末尾推荐仍建议从 Phase A/B 开始;2026-05-31 已迁入 `design/05-editor-mainline/done/`。 +- `design/05-editor-mainline/process/5-32-filetree-lazy-loading-sidex-alignment-v1.md` checklist 和验证记录已完成;2026-05-31 已迁入 `design/05-editor-mainline/done/`。 +- `design/07-ai/process/7-37-claudecode-reasonix-worker-evaluation-0524-bug2-v1.md` 是 worker 评估材料,不是 active implementation checklist;2026-05-31 已迁入 `design/07-ai/reference/`。 +- `design/07-ai/process/7-41-page-ai-hermes-reasonix-user-profile-isolation-v2.md` 文件头状态为 `done`,但仍在 `process/`;2026-06-01 已迁入 `design/07-ai/done/`。`design/10-review/process/17-sidex-mnote-workbench-gap-execution-checklist-v1.md` 曾有同类漂移,2026-05-31 已迁入 `design/10-review/done/`。 + +## 影响 + +- 后续 agent 可能按旧入口恢复过时优先级。 +- 已完成设计继续占用 active process 队列,导致任务拆分噪声。 +- process/done 目录语义被削弱,bug 和设计治理难以闭环。 + +## 修复建议 + +1. 已先修正 `design/README.md` 和 `1-8` 的失效入口。 +2. 下一步逐个迁移或拆分状态漂移文档: + - `3-24` 已迁入 `03-rust-web/done/`,Mindmap 真实消费另拆 follow-up。 + - `5-31` 已迁入 `05-editor-mainline/done/`,剩余风险另拆。 + - `5-32` 已迁入 `05-editor-mainline/done/`。 + - `5-33` 拆成已完成核心和 breadcrumb/fetch guard follow-up。 + - `7-37` 已迁入 `07-ai/reference/`。 + - `7-41` 已迁入 `07-ai/done/`。 + - `17` 已迁入 `10-review/done/`。 + +## 验收 + +- [x] `find design -path '*/process/*' -type f ! -path 'design/old/*'` 中不再出现文件头状态为 `done` 的文档。 +- [x] `rg -n "design/01-05-current-priority-overview.md" design/README.md design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md` 无 active 入口引用。 +- [x] 每个保留在 `process/` 的文档都有未完成 checklist、owner 和验收条件;`5-33` 仍保留在 `process/` 是因为 breadcrumb/fetch guard 等 follow-up 未完成。 diff --git a/bugs/10-review/done/10-19-agents-md-broken-design-references-v1.md b/bugs/10-review/done/10-19-agents-md-broken-design-references-v1.md new file mode 100644 index 00000000..e72a3efe --- /dev/null +++ b/bugs/10-review/done/10-19-agents-md-broken-design-references-v1.md @@ -0,0 +1,43 @@ +# 10-19 AGENTS.md 架构参考路径断裂 + +## 状态 + +- 状态:done +- Owner:10-review / AGENTS 协作规则 +- 发现时间:2026-05-31 + +## 现象 + +发现时,`AGENTS.md` 的“需要架构判断时,优先参考”列表中有两个路径已经断裂。因为 `AGENTS.md` 是 agent 的顶层协作规则,这类失效引用会直接误导后续任务启动时的架构判断。 + +## 证据 + +- 发现时,`AGENTS.md` 仍引用 `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md`,但该文件实际位于 `design/old/01-05-current-priority-overview.md`,根路径不存在。 +- 发现时,`AGENTS.md` 仍引用 `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md`,但实际文件位于 `design/01-tree-first-graph-kernel/reference/1-tree-first-graph-kernel-v1.md`。 +- 本轮已更新 `design/README.md`、`1-8` 与 `AGENTS.md` 的 active 入口。 + +## 影响 + +- 后续 agent 按 `AGENTS.md` 查架构依据时会遇到文件不存在。 +- worker 可能因此退回旧记忆或自己猜测口径。 +- `01-05` 已迁入 `old/` 但仍被当作当前入口,削弱 `design/README.md` 和 `1-8` 的入口权威。 + +## 修复建议 + +修复方式: + +- 将 `01-05-current-priority-overview.md` 替换为 `CURRENT_ARCHITECTURE.md` 与 `design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`。 +- 将 `process/1-tree-first-graph-kernel-v1.md` 改为 `reference/1-tree-first-graph-kernel-v1.md`,或删除该历史参考入口。 + +## 本轮进展 + +- 2026-05-31:用户设置的 P0 goal 已明确包含 `AGENTS/design 断裂引用`,本轮已更新 `AGENTS.md`: + - 新增 `CURRENT_ARCHITECTURE.md`。 + - 新增 `design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`。 + - 将 `1-tree-first-graph-kernel-v1.md` 指向 `reference/`。 + - 移除已不存在的 root `design/01-05-current-priority-overview.md` 引用。 + +## 验收 + +- `test -f` 验证 `AGENTS.md` 中列出的每个绝对路径都存在。 +- `rg -n "design/01-05-current-priority-overview.md|process/1-tree-first-graph-kernel-v1.md" AGENTS.md` 不再命中 active 断裂引用。 diff --git a/bugs/10-review/done/10-20-testing-reference-missing-new-smokes-v1.md b/bugs/10-review/done/10-20-testing-reference-missing-new-smokes-v1.md new file mode 100644 index 00000000..07cd5840 --- /dev/null +++ b/bugs/10-review/done/10-20-testing-reference-missing-new-smokes-v1.md @@ -0,0 +1,49 @@ +# 10-20 新增 smoke 未及时进入 TESTING_REFERENCE + +## 状态 + +- 状态:done +- Owner:10-review / smoke governance +- 发现时间:2026-05-31 + +## 现象 + +近期新增的 Page AI / mindmap / ChatOnly smoke 未及时进入 `scripts/TESTING_REFERENCE.md`。该文件是当前 smoke 分类与默认基线的权威入口,缺失会导致新增能力线没有明确验证入口。 + +## 证据 + +本轮发现时,`scripts/TESTING_REFERENCE.md` 未收录: + +- `scripts/task503-mindmap-skill-capability-smoke.js` +- `scripts/task504-page-ai-history-agent-filter-smoke.js` +- `scripts/task512-chatonly-doubao-sync-smoke.js` +- `scripts/task513-chatonly-provider-sync-smoke.js` + +## 本轮处理 + +已把上述 smoke 补入 `scripts/TESTING_REFERENCE.md`: + +- `task503` 归入资源对象与 mindmap / Page AI mindmap skill。 +- `task504` 归入 Page AI history / agent filter。 +- `task512`、`task513` 归入 ChatOnly / provider session 绑定。 +- 另补入本轮新增的 `task514-sidebar-dev-hot-reload-gating-smoke.js`、`task515-onlyoffice-live-scope-http-smoke.js`、`task516-onlyoffice-bridge-multisession-browser-smoke.js`。 + +## 剩余问题 + +本轮已实跑: + +- `node scripts/task503-mindmap-skill-capability-smoke.js` +- `node scripts/task504-page-ai-history-agent-filter-smoke.js` +- `node scripts/task512-chatonly-doubao-sync-smoke.js` +- `node scripts/task514-sidebar-dev-hot-reload-gating-smoke.js` +- `node scripts/task515-onlyoffice-live-scope-http-smoke.js` +- `node scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js` +- `node scripts/task513-chatonly-provider-sync-smoke.js deepseek` +- `node scripts/task513-chatonly-provider-sync-smoke.js gemini` + +当前 smoke reference 缺项已补齐并实跑。后续若新增 smoke,需要继续按本文件规则同步 `scripts/TESTING_REFERENCE.md`。 + +## 验收 + +- `rg -n "task503|task504-page-ai-history|task512|task513" scripts/TESTING_REFERENCE.md` 能命中新条目。 +- `task513-chatonly-provider-sync-smoke.js deepseek` 与 `task513-chatonly-provider-sync-smoke.js gemini` 已实跑通过,并已写回 `design/07-ai/done/7-44-chatonly-doubao-session-binding-v1.md`。 diff --git a/bugs/10-review/done/10-21-design-done-header-status-drift-v1.md b/bugs/10-review/done/10-21-design-done-header-status-drift-v1.md new file mode 100644 index 00000000..e963e4ca --- /dev/null +++ b/bugs/10-review/done/10-21-design-done-header-status-drift-v1.md @@ -0,0 +1,39 @@ +# 10-21 design done 文件头状态漂移 + +## 状态 + +- 状态:done +- Owner:10-review / design governance +- 发现时间:2026-06-01 + +## 现象 + +部分已经位于 `design/**/done/` 的设计稿,文件头仍保留 `process` / `PROCESS` 状态。后续 agent 或脚本如果只读文件头而不看目录状态,容易把已归档文档误判为 active process。 + +## 证据 + +- `design/07-ai/done/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md` 位于 `done/`,但文件头仍写 `状态:process`。 +- `design/05-editor-mainline/done/5-31-page-settings-sqlite-preference-convergence-v1.md` 位于 `done/`,但文件头仍写 `状态:process`。 +- `design/05-editor-mainline/done/5-32-filetree-lazy-loading-sidex-alignment-v1.md` 位于 `done/`,但文件头仍写 `状态:process`。 +- `design/04-tree-domain/done/4-49-local-file-operation-event-contract-v1.md` 位于 `done/`,但文件头仍写 `状态:process`。 + +## 影响 + +- 设计治理扫描和 worker 任务拆分可能重复打开已完成文档。 +- `process/` 与 `done/` 的事实源边界被削弱。 + +## 下一步 + +1. 扫描 `design/**/done/*.md` 中的文件头状态字段。 +2. 将已经归档且证据充分的文档头部状态统一改为 `done`。 +3. 对确实仍有未完成项的文档,不留在 `done/` 中混用;应拆 follow-up 到 `process/`。 + +## 验收 + +- [x] `design/**/done/*.md` 中不再出现文件头 `状态:process` / `当前状态:PROCESS`。 +- [x] 若存在例外,必须在文档头部说明其为历史快照而非 active process。 + +## 2026-06-01 修复记录 + +- 已将当前扫描命中的 `design/**/done/*.md` 文件头状态统一改为 `done` / `DONE`。 +- 已验证:`rg -n '^> (当前状态|状态):`?(PROCESS|process)' design/*/done design/old/*/done -g '*.md'` 无结果。 diff --git a/bugs/10-review/done/10-22-done-bug-stale-followup-text-v1.md b/bugs/10-review/done/10-22-done-bug-stale-followup-text-v1.md new file mode 100644 index 00000000..254ff53e --- /dev/null +++ b/bugs/10-review/done/10-22-done-bug-stale-followup-text-v1.md @@ -0,0 +1,38 @@ +# 10-22 done bug 文档残留过时 follow-up 文案 + +## 状态 + +- 状态:done +- Owner:10-review / bugs governance +- 发现时间:2026-06-01 + +## 现象 + +部分已经位于 `bugs/**/done/` 的 bug 文档仍保留过时的“仍缺 / 待复测”文案,而同文档或后续 bug 已经补充了完成证据。这会让 reviewer 误判 done 条目仍未完成。 + +## 证据 + +- `bugs/07-ai/done/7-52-page-ai-target-picker-resource-target-gap-v1.md` 仍写“仍缺真实 mindmap resource tab 自动打开后的 UI 级 contextRefs 复测”,但后续 `task525-page-ai-mindmap-resource-target-smoke.js` 已覆盖 mindmap resource target。 +- `bugs/07-ai/done/7-53-page-ai-runtime-split-threshold-triggered-v1.md` 的主 bug 已完成拆分阈值,但残留 stop/close smoke `[~]` 项;该缺口已拆为 `bugs/07-ai/process/7-56-page-ai-runtime-stop-close-smoke-gap-v1.md` 后,应避免继续让 `7-53` 看起来未完成。 + +## 影响 + +- `done/` bug 的完成边界不清晰。 +- 后续 agent 容易重复寻找已覆盖缺口,或者把独立 follow-up 误认为原 bug 未完成。 + +## 下一步 + +1. 扫描近期 `bugs/**/done/*.md`,把已被后续 smoke 覆盖的旧 follow-up 文案改成完成记录。 +2. 对仍未完成的尾项,拆到独立 `bugs/**/process/`,并在原 done 文档写明“尾项已拆出”。 +3. 不改变 bug 事实源:没有真实验证的项不得从文案中删除,只能拆出跟踪。 + +## 验收 + +- [x] `7-52` 中 mindmap resource tab 复测口径与 `task525` 证据一致。 +- [x] `7-53` 中 stop/close 缺口指向 `7-56`,主拆分 bug 保持 done。 +- [x] `git diff --check` 通过。 + +## 2026-06-01 修复记录 + +- `7-52` 已改为记录 `task525-page-ai-mindmap-resource-target-smoke.js` 覆盖真实 mindmap resource tab target / contextRefs 复测。 +- `7-53` 已把停止/关闭完整断言尾项拆到 `bugs/07-ai/process/7-56-page-ai-runtime-stop-close-smoke-gap-v1.md`,主拆分阈值 bug 保持 done。 diff --git a/design/01-tree-first-graph-kernel/done/1-13-batch-f-p1-p2-active-tail-checklist-v1.md b/design/01-tree-first-graph-kernel/done/1-13-batch-f-p1-p2-active-tail-checklist-v1.md index f57cb131..dae58115 100644 --- a/design/01-tree-first-graph-kernel/done/1-13-batch-f-p1-p2-active-tail-checklist-v1.md +++ b/design/01-tree-first-graph-kernel/done/1-13-batch-f-p1-p2-active-tail-checklist-v1.md @@ -2,7 +2,7 @@ > 创建时间:2026-05-21 > -> 当前状态:`PROCESS` +> 当前状态:`DONE` > > 上位入口:`design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md` > diff --git a/design/01-tree-first-graph-kernel/done/1-3-current-priority-execution-checklist-v1.md b/design/01-tree-first-graph-kernel/done/1-3-current-priority-execution-checklist-v1.md index a26b0783..3555f1da 100644 --- a/design/01-tree-first-graph-kernel/done/1-3-current-priority-execution-checklist-v1.md +++ b/design/01-tree-first-graph-kernel/done/1-3-current-priority-execution-checklist-v1.md @@ -2,7 +2,7 @@ > 创建时间:2026-05-19 > -> 当前状态:`PROCESS` +> 当前状态:`DONE` > > 上位依据: > - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` diff --git a/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md b/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md index c644b2ab..52cec139 100644 --- a/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md +++ b/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md @@ -6,14 +6,18 @@ > > 2026-05-22 归档治理补充:当前 active `process/` 只保留可直接执行的少量收口稿;长期架构、愿景、参考矩阵、Wolai 对标基线、Mindmap 总设计、旧 AI 块级执行稿和 Convex Web 迁移入口已分别移动到 `reference/`、`done/` 或 `old/`。本文是后续调度入口,不代表下列所有历史入口仍在 `process/`。 > +> 2026-05-31 口径复核:当前主线仍以 `CURRENT_ARCHITECTURE.md`、`ARCHITECTURE.md` 与本文件为准;历史 `design/01-05-current-priority-overview.md` 已迁入 `design/old/01-05-current-priority-overview.md`,不再作为 active 调度入口。`3-3`、`3-18` 等已归档文档只保留为完成证据,后续 live / local-folder runtime 收口默认看 `3-23`、`5-32` 与相关 bug。 +> +> 2026-06-01 P0/P1/P2 复核:P0 阻塞项已由 `4-44/4-45/4-34/4-28` 归档证据覆盖,不再作为实现阻塞;`5-30` 与 `5-33` 已归档到 `05-editor-mainline/done/`,导航页 breadcrumb / fetch guard 尾项拆到 `5-35`;`7-35/7-36` 已降级为协作参考,不再占用产品 process 队列。当前 active 产品 process 重点收敛为 `3-23`、`3-25`、`5-35`、`7-18`、`7-43` 及对应 bugs/process。 +> > 目标:把本轮 design governance 后剩余的主线 `process/` 文档排成可执行顺序,避免后续 worker 在 active process 中自行猜优先级。 > > 上位依据: > - `/mnt/Data1T/mnote/ARCHITECTURE.md` > - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md` > - `/mnt/Data1T/mnote/design/README.md` -> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` > - `/mnt/Data1T/mnote/design/10-review/done/15-design-governance-mvp-post-review-v1.md` +> - `/mnt/Data1T/mnote/design/10-review/done/18-current-design-and-bug-hunt-review-v1.md` ## 1. 一句话顺序 @@ -87,6 +91,8 @@ - `4-45` 四项 checklist 完成后归档到 `04-tree-domain/done/`。 - `4-44` browser smoke 全绿后归档到 `04-tree-domain/done/`。 +2026-06-01 状态:本项已由 `4-44` / `4-45` done 文档和 `task471` / `task476` / `task487` 复跑证据覆盖,不再作为 P0 阻塞项。 + ### P0.2 local folder no-refresh / restore focus 入口文档: @@ -109,6 +115,8 @@ - local folder 子项通过后,`4-34` 归档。 - React / Rust 主壳 focus 证据完成后,`4-28` 归档。 +2026-06-01 状态:本项已由 `4-34` / `4-28` done 文档和后续 tree/filetree lifecycle smoke 证据覆盖,不再作为 P0 阻塞项。 + ## 4. P1:统一工作区身份、BufferStore 和 Page Aggregate 这一步是 MVP 后阶段真正的底座收口。 @@ -133,6 +141,7 @@ - `cargo test -p mnote-web filetree_runtime -- --test-threads=1` - `cargo test -p mnote-web web_shell -- --test-threads=1` - 覆盖 markdown / office / mindmap / raw file / directory 的 browser smoke。 +- 2026-06-01 进展:已补 `scripts/task524-workspace-object-identity-matrix-smoke.js`,覆盖 page `.md`、directory、raw text、mindmap、OnlyOffice 在 FileTree row `workspacePath`、OpenEditorsSnapshot entry、URL `resourceTab`、active tab identity 之间的对齐;验证命令 `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task524-workspace-object-identity-matrix-smoke.js` 已通过。P1.1 的 identity 浏览器矩阵缺口已收口,后续只保留与 `1-5 / 1-6` 历史 checklist 对账。 归档条件: @@ -163,6 +172,8 @@ - `1-6` 中 BufferStore 运行时接入、浏览器读回、冲突 UI 相关项完成。 +2026-06-01 状态:历史 BufferStore 读回 / conflict UI 已由 `1-6` 与 Batch C 证据覆盖;真实 agent writeback -> watcher -> BufferStore -> Page Aggregate -> tiptap 的端到端回收仍归入 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` Phase B,不再作为本文模糊 P1.2 阻塞。 + ### P1.3 Page Aggregate compat 瘦身 入口文档: @@ -177,6 +188,8 @@ 2. 补 GFM AST parser 写侧尾项:web shell 单测、手写 parser 过渡标记、临时适配分支清理。 3. 明确 `PageAggregateClientState` 哪些仍是合法 draft state,哪些应退出事实源地位。 +2026-06-01 收口口径:`body.blockDocument` / `editorDocument` 是 local-first 浏览器主消费面;`PageBody.content` 在 `mnote.page_aggregate.v1` 中继续保留为必填兼容镜像 / fallback,不作为 active editor 初始化优先事实源。`body.content` 改可选或删除必须进入 v2 协议升级或独立迁移 checklist,不能在 v1 兼容期直接删除。 + 验收: - `cargo test -p mnote-web local_markdown -- --test-threads=1` @@ -186,7 +199,7 @@ 归档条件: - `3-13` 已完成 GFM AST 迁移尾项并归档到 `design/03-rust-web/done/`;`web_shell.rs` legacy marks 分支改判为兼容层保留,不再作为迁移阻塞项。 -- `5-6` 剩余 Phase G/H 小尾项完成或拆成更小 checklist 后归档。 +- `5-6` 剩余 Phase G/H 小尾项完成或拆成更小 checklist 后归档;`bugs/05-editor-mainline/done/5-41-page-aggregate-compat-slimming-gaps-v1.md` 已明确 `PageBody.content` 的 v1/v2 边界。 ## 5. P2:tree live cache 与 command context @@ -194,8 +207,10 @@ 入口文档: -- `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md` +- `design/03-rust-web/done/3-3-rust-web-tree-realtime-event-stream-v1.md` - `design/03-rust-web/reference/3-1-rust-web-long-term-checklist-v2.md` +- `design/03-rust-web/process/3-23-sidebar-local-folder-resource-runtime-followup-v1.md` +- `design/05-editor-mainline/done/5-32-filetree-lazy-loading-sidex-alignment-v1.md` 执行目标: @@ -210,13 +225,13 @@ 归档条件: -- `3-3` 中统一 live cache 和 no-refresh 矩阵完成后归档。 +- `3-3` 已归档为 tree realtime event stream 完成证据;剩余 live cache / refresh 收口不得重新打开旧 `3-3`,应拆入 `3-23`、`5-32` 或明确的 `bugs/process`。 ### P2.2 Local folder tree live consumer 收口 入口文档: -- `design/03-rust-web/process/3-18-local-folder-tree-live-consumer-convergence-checklist-v1.md` +- `design/03-rust-web/done/3-18-local-folder-tree-live-consumer-convergence-checklist-v1.md` 执行状态: diff --git a/design/03-rust-web/process/3-24-global-content-type-page-width-settings-v1.md b/design/03-rust-web/done/3-24-global-content-type-page-width-settings-v1.md similarity index 93% rename from design/03-rust-web/process/3-24-global-content-type-page-width-settings-v1.md rename to design/03-rust-web/done/3-24-global-content-type-page-width-settings-v1.md index 8f0dca5c..c663ce3e 100644 --- a/design/03-rust-web/process/3-24-global-content-type-page-width-settings-v1.md +++ b/design/03-rust-web/done/3-24-global-content-type-page-width-settings-v1.md @@ -1,5 +1,11 @@ # 全局与内容类型页面宽度设置 Checklist v1 +> 状态:done +> +> 归档时间:2026-05-31 +> +> 归档说明:实施 checklist 已全量完成,当前真实消费点已覆盖 Markdown、轻量 Office 预览、PDF 预览和 PPTX 快速预览;Mindmap 渲染消费作为后续对应 viewer follow-up,不阻塞本文归档。 + ## 背景 当前 Markdown 页面已有 `wideLayout` 布尔选项,但 Word / PDF / Excel / PPT / Mindmap 等不同内容类型没有统一的页面宽度偏好入口。用户需要在全局设置中分别调整不同类型的默认宽度,避免像思源那样必须写 CSS 才能把阅读宽度调到合适比例。 diff --git a/design/03-rust-web/process/3-25-local-folder-mineru-ocr-sidecar-v1.md b/design/03-rust-web/process/3-25-local-folder-mineru-ocr-sidecar-v1.md new file mode 100644 index 00000000..1c915ab8 --- /dev/null +++ b/design/03-rust-web/process/3-25-local-folder-mineru-ocr-sidecar-v1.md @@ -0,0 +1,413 @@ +# 3-25 Local Folder MinerU OCR Sidecar Checklist v1 + +> 创建时间:2026-05-29 +> 状态:`process` +> Owner:03-rust-web / local-folder resource runtime +> +> 2026-06-01 复核:后端 mock 闭环已落地 `local_ocr.rs` 与 OCR route;真实 MinerU HTTP client 已补本地 HTTP mock 成功路径测试;`task526` 已覆盖 mock OCR API、搜索命中、显式插入链接和 OCR sidecar Markdown resource tab 打开。前端 OCR task runtime、active job store、realtime event 和任务栏 UI 尚未落地。本文继续保持 `process`,不得被本轮 Page AI / OnlyOffice / ChatOnly 收口误归档。 + +## 背景 + +当前 mnote local-first 主线中,本地 Markdown 和同目录资源是默认数据真相。本地图片 / 附件上传已通过 `/api/local-folder/assets/upload` 写入 Markdown 文件附近,并返回 `sourcePath` / `attachmentRef`。现在需要给图片和图片型 PDF 增加 OCR 能力,识别引擎使用 MinerU API。 + +OCR 结果不应默认写进用户正文,也不应只存在 `.mnote` 私有缓存里。用户希望 OCR 附件位于当前 Markdown 文件相同目录下,并用单独文件夹承载,因为一个 Markdown 页面可能对应多个 OCR 文件。 + +## 目标 + +- 对 local-folder 图片和图片型 PDF 提供手动触发 OCR。 +- 使用 MinerU API 识别本地文件,生成 Markdown OCR 结果。 +- OCR 结果作为页面旁路资源落盘到当前 Markdown 同目录的 `{pageStem}.ocr/` 文件夹。 +- OCR 文本默认作为资源 sidecar 被搜索、资源面板和后续 AI 上下文消费,不自动污染正文。 +- 支持用户显式把某个 OCR 结果插入当前正文。 +- 提供全局 OCR 任务进展入口,让用户离开当前资源后仍能看到 OCR 是否完成、失败或需要重试。 +- 保持 local-first 可迁移性:OCR Markdown 文件本身可读、可备份、可编辑;`.mnote` 索引只作为缓存和状态加速。 + +## 非目标 + +- 不做全 workspace 自动 OCR。 +- 不默认把 OCR 文本追加到页面正文。 +- 不把 MinerU token 打印到日志、前端 payload 或用户可见错误里。 +- 不在前端直接调用 MinerU API。 +- 不把 OCR 文件当成普通页面加入 Page Tree。 +- 不为 cloud / Convex / remote source 设计默认 OCR 主路径;第一阶段只覆盖 local-folder。 +- 不承诺精确百分比进度;MinerU API 第一阶段按阶段型状态展示。 +- 不把任务进展做成前端定时轮询主链;优先走现有 realtime / WS / SSE 事件。 + +## 文件布局 + +假设当前页面为: + +```text +docs/Page.md +``` + +附件可能为: + +```text +docs/Page.assets/photo.png +docs/Page.assets/spec.pdf +``` + +OCR 结果目录固定为: + +```text +docs/Page.ocr/ +``` + +OCR 结果文件示例: + +```text +docs/Page.ocr/photo.png.ocr.md +docs/Page.ocr/spec.pdf.ocr.md +``` + +同一页面下存在同名来源文件时,用来源 root-relative path、文件大小和 mtime 派生短 hash: + +```text +docs/Page.ocr/photo.png-8f3a21.ocr.md +``` + +## OCR Markdown frontmatter + +OCR Markdown 是 OCR 正文真相,必须能脱离 `.mnote` 索引独立说明来源。 + +```markdown +--- +mnote_ocr_version: 1 +provider: mineru +model_version: vlm +owner_document: ./Page.md +source_path: ./Page.assets/photo.png +source_root_relative_path: docs/Page.assets/photo.png +source_size: 123456 +source_mtime_ms: 1760000000000 +status: done +created_at: 2026-05-29T12:00:00+08:00 +updated_at: 2026-05-29T12:00:00+08:00 +--- + +识别文本... +``` + +字段口径: + +- `owner_document`:相对 OCR 文件所在目录到 owner Markdown 的相对路径。 +- `source_path`:相对 owner Markdown 所在目录的来源附件路径,和正文中的附件引用口径保持一致。 +- `source_root_relative_path`:相对 workspace root 的来源路径,用于稳定查找和索引。 +- `source_size` / `source_mtime_ms`:用于判断 OCR 是否 stale。 +- `status`:`done`、`failed` 或 `stale`。`queued/running` 只写入 `.mnote/ocr-index.json`,避免半成品 OCR 文件被误读。 + +## `.mnote` 索引 + +`.mnote/ocr-index.json` 是状态缓存,不是 OCR 正文真相。 + +```json +{ + "version": 1, + "entries": { + "docs/Page.assets/photo.png": { + "ownerDocumentId": "local-md:docs~2FPage.md", + "ownerDocumentPath": "docs/Page.md", + "sourceRootRelativePath": "docs/Page.assets/photo.png", + "ocrRootRelativePath": "docs/Page.ocr/photo.png.ocr.md", + "provider": "mineru", + "modelVersion": "vlm", + "status": "done", + "sourceSize": 123456, + "sourceMtimeMs": 1760000000000, + "createdAtMs": 1760000000000, + "updatedAtMs": 1760000000000, + "plainTextPreview": "识别文本前 240 字" + } + } +} +``` + +索引用途: + +- 资源面板快速展示 OCR 状态。 +- 搜索索引快速定位 OCR sidecar。 +- 避免重复提交同一个未变化资源。 +- 记录失败原因,但错误文本需要脱敏,不包含 token、预签名 URL 或完整外部响应体。 + +## 资源归属与树投影 + +- OCR 文件夹 `{pageStem}.ocr/` 应在 File Tree 中作为页面旁路资源文件夹出现。 +- `{pageStem}.ocr/*.ocr.md` 不应作为普通 Markdown 页面进入 Page Tree。 +- local search 不应把 OCR sidecar 当普通 Markdown 页面索引,否则会出现重复页面和错误导航。 +- 搜索 OCR 命中时,应返回 owner page 作为结果,`hasOcr=true`,evidence 指向来源附件和 OCR sidecar。 +- File Tree 可展示 OCR 文件,点击默认作为 Markdown resource tab 打开,不切换成页面文档。 + +## MinerU 调用边界 + +后端读取 MinerU token,前端不接触 token。 + +配置优先级: + +1. `MNOTE_MINERU_API_TOKEN` +2. `MINERU_API_TOKEN` +3. 开发环境可选读取 `~/.hermes/.env` 中的 `MINERU_API_TOKEN` + +第一阶段使用 MinerU `model_version=vlm`。 + +本地文件流程: + +1. `POST https://mineru.net/api/v4/file-urls/batch` 申请上传地址。 +2. 使用 `PUT` 把本地文件上传到预签名 URL。 +3. 轮询 `GET /extract-results/batch/{batch_id}`。 +4. 下载 `full_zip_url`。 +5. 解压并提取结果 Markdown。 +6. 写入 `{pageStem}.ocr/*.ocr.md`。 +7. 更新 `.mnote/ocr-index.json`。 + +## 后端 API + +### 创建或复用 OCR job + +`POST /api/local-folder/ocr/jobs` + +请求: + +```json +{ + "rootUri": "file:///mnt/Data1T/mnote-notes", + "documentId": "local-md:docs~2FPage.md", + "sourcePath": "Page.assets/photo.png", + "sourceRootRelativePath": "docs/Page.assets/photo.png", + "provider": "mineru", + "force": false +} +``` + +响应: + +```json +{ + "ok": true, + "job": { + "status": "done", + "ownerDocumentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "docs/Page.assets/photo.png", + "ocrRootRelativePath": "docs/Page.ocr/photo.png.ocr.md", + "stale": false + } +} +``` + +第一阶段可以同步执行并在请求内返回最终状态;如果真实耗时过长,再升级为后台 job。同步版本必须设置合理超时,并让 UI 显示运行中状态。 + +### 查询 OCR 状态 + +`GET /api/local-folder/ocr/status?rootUri=...&sourceRootRelativePath=...` + +返回 `.mnote/ocr-index.json` 中的状态,并重新对比来源文件 `size/mtime` 判断 `stale`。 + +### 查询 OCR 任务列表 + +`GET /api/local-folder/ocr/jobs?rootUri=...` + +返回当前 root 下 active jobs 和最近完成 / 失败任务,用于全局任务栏首次渲染和刷新后恢复。 + +响应: + +```json +{ + "ok": true, + "jobs": [ + { + "jobId": "ocr_1760000000000_abcd", + "fileName": "spec.pdf", + "ownerDocumentId": "local-md:docs~2FPage.md", + "ownerDocumentPath": "docs/Page.md", + "sourceRootRelativePath": "docs/Page.assets/spec.pdf", + "ocrRootRelativePath": "docs/Page.ocr/spec.pdf.ocr.md", + "status": "mineru_processing", + "stageLabel": "识别中", + "startedAtMs": 1760000000000, + "updatedAtMs": 1760000000000, + "finishedAtMs": null, + "stale": false + } + ] +} +``` + +### 读取 OCR 文本 + +`GET /api/local-folder/ocr/read?rootUri=...&ocrRootRelativePath=...` + +返回 OCR Markdown 文本、frontmatter 摘要、来源路径和 stale 状态。 + +### 插入 OCR 到正文 + +`POST /api/local-folder/ocr/insert` + +只在用户显式触发时执行。默认插入为链接块: + +```markdown +[OCR:photo.png](./Page.ocr/photo.png.ocr.md) +``` + +可选模式 `inlineSection` 插入正文段落: + +```markdown +### OCR:photo.png + + + +识别文本... +``` + +## UI 行为 + +- 编辑器图片 toolbar / 附件右键菜单增加 `OCR 识别`。 +- File Tree 图片/PDF 资源右键菜单增加 `OCR 识别`。 +- PDF preview / image resource tab 显示 OCR 状态和结果入口。 +- 全局任务栏 / 任务抽屉显示运行中、完成、失败和 interrupted 的 OCR 任务。 +- 状态文案只表达:未识别、排队中、上传中、识别中、写入中、已识别、来源已变化、识别失败、已中断。 +- 对 `stale` 结果展示“重新识别”操作,不自动覆盖旧 OCR 文件,除非用户确认或传 `force=true`。 +- OCR 结果打开在 resource tab,不切到普通文档页面。 + +## 搜索与 AI 消费 + +- `includeOcr=false` 时不搜索 OCR sidecar。 +- `includeOcr=true` 时,搜索 OCR 文本并返回 owner page。 +- 搜索 evidence 包含: + - `kind: "ocr"` + - `sourceRootRelativePath` + - `ocrRootRelativePath` + - `snippet` +- 后续 `image_read` / Page AI attachment context 可以读取 OCR sidecar,优先返回已存在 OCR 文本,不让 agent 重复调用 OCR。 + +## 任务进展与全局入口 + +OCR 是慢任务,提交后必须有全局可见状态,不应只依赖 toast 或当前 resource tab。 + +第一阶段进度采用阶段型状态: + +```text +queued -> uploading -> mineru_processing -> downloading -> writing_sidecar -> done +``` + +失败和中断状态: + +```text +failed +interrupted +stale +``` + +状态含义: + +- `queued`:任务已创建,等待执行。 +- `uploading`:正在上传原始图片/PDF 到 MinerU 预签名地址。 +- `mineru_processing`:MinerU 正在识别。 +- `downloading`:正在下载 MinerU 结果包。 +- `writing_sidecar`:正在写入 `{pageStem}.ocr/*.ocr.md` 和 `.mnote/ocr-index.json`。 +- `done`:OCR sidecar 已写入。 +- `failed`:任务失败,可重试。 +- `interrupted`:mnote-web 进程或任务 worker 中断,不能确认完成,可重试。 +- `stale`:来源文件在 OCR 后发生变化。 + +后端维护一个运行时 active job store,并把每次状态变更写入 `.mnote/ocr-index.json`。页面刷新后,已完成、失败、stale 和 interrupted 状态可以从磁盘恢复;正在运行中的内存任务如果因进程重启丢失,应在下次读取时标记为 `interrupted`,由用户重试。 + +状态变化通过现有 realtime / WS / SSE 事件链路广播,不在前端叠加 `setInterval` 轮询主链。事件示例: + +```json +{ + "type": "local_ocr.job.updated", + "jobId": "ocr_1760000000000_abcd", + "rootUri": "file:///mnt/Data1T/mnote-notes", + "ownerDocumentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "docs/Page.assets/spec.pdf", + "ocrRootRelativePath": "docs/Page.ocr/spec.pdf.ocr.md", + "status": "mineru_processing", + "stageLabel": "识别中", + "updatedAtMs": 1760000000000 +} +``` + +前端需要两类展示: + +- 资源局部状态:图片/PDF resource tab、附件菜单、File Tree 行显示当前 OCR 状态。 +- 全局任务栏 / 任务抽屉:底栏或右上角显示 `OCR 2` / `1 个任务进行中`。点开后列出文件名、所属页面、阶段、开始时间、完成/失败状态,以及“打开 OCR”“重试”等操作。 + +第一阶段不强制支持取消。若 MinerU 任务 API 后续提供可靠取消,再补 `cancel` 动作。 + +## 隐私与安全 + +- OCR 必须由用户手动触发。 +- UI 首次触发时应明确提示:文件会发送到 MinerU API。 +- 后端只允许读取 `rootUri` 授权根内文件,禁止绝对路径和 `..` 越界。 +- 日志不记录 token、预签名 URL、完整外部错误响应和 OCR 正文全文。 +- 失败状态写入简短错误码和脱敏消息。 + +## 实施 Checklist + +- [x] 后端:新增 MinerU client,支持本地文件上传、轮询、结果 zip 下载和 Markdown 提取。 +- [x] 后端:新增 OCR 路由模块,提供 job/status/read/insert API。(当前 insert 只支持 link 模式) +- [ ] 后端:新增 OCR active job store,记录 queued/uploading/mineru_processing/downloading/writing_sidecar/done/failed/interrupted/stale。 +- [ ] 后端:OCR 状态变化写入 `.mnote/ocr-index.json` 并广播 `local_ocr.job.updated` 事件。 +- [x] 后端:提供 OCR jobs list API,用于全局任务栏刷新后恢复。 +- [x] 后端:实现 `{pageStem}.ocr/` 路径规划、文件名冲突处理和 UTF-8 OCR Markdown 写入。 +- [x] 后端:实现 `.mnote/ocr-index.json` 读写、stale 检测和脱敏错误记录。 +- [x] 后端:扩展 local-folder 文件分类,让 `{pageStem}.ocr/*.ocr.md` 不进入 Page Tree 普通页面。 +- [x] 搜索:扩展 local search index,`includeOcr=true` 时索引 OCR sidecar 并返回 owner page。 +- [ ] UI:在附件右键菜单、File Tree 资源菜单和资源 tab 增加 OCR 识别入口。 +- [ ] UI:展示 OCR 状态、stale、失败和重新识别动作。 +- [ ] UI:新增全局 OCR 任务栏 / 任务抽屉,显示任务阶段、所属页面、打开 OCR、重试。 +- [~] UI/API:增加“插入 OCR 链接到正文”和可选“插入 OCR 正文段落”动作。(后端 link API 已完成;前端入口和 inlineSection 待做) +- [ ] AI:让 image/PDF 资源上下文优先读取已有 OCR sidecar。 +- [x] 测试:Rust 单测覆盖路径规划、frontmatter、索引读写、stale、越界拒绝。 +- [x] 测试:Rust route 测试覆盖无 token、真实 MinerU HTTP mock 成功、模拟失败和脱敏错误。 +- [x] 测试:local search 测试覆盖 OCR 命中返回 owner page,OCR sidecar 不作为普通页面。 +- [~] Smoke:浏览器覆盖图片/PDF 手动 OCR、全局任务栏状态、打开 OCR resource tab、搜索 OCR 命中、插入 OCR 链接。(当前 `task526` 覆盖 mock OCR API、搜索命中和 insert link;真实 UI/任务栏/打开 OCR resource tab 待做) +- [x] 文档:补充 `scripts/TESTING_REFERENCE.md` 中 OCR smoke 基线。 + +## 验收标准 + +- 本地图片或图片型 PDF 可被用户手动提交 MinerU OCR。 +- OCR 结果落在 owner Markdown 同目录的 `{pageStem}.ocr/` 下,文件为 UTF-8 Markdown。 +- `.mnote/ocr-index.json` 删除后,仍能从 OCR Markdown frontmatter 重建核心绑定关系。 +- OCR 文件不会污染 Page Tree,也不会作为普通页面搜索结果出现。 +- 搜索 OCR 文本时返回 owner page,结果标记 `hasOcr=true`。 +- 用户未显式选择插入时,页面正文不发生变化。 +- 用户提交 OCR 后,即使离开当前资源,也能在全局 OCR 任务栏看到阶段状态;完成后可打开 OCR,失败或中断后可重试。 +- 所有外部 API token 和预签名 URL 均不出现在日志、前端 payload 或错误响应中。 + +## 2026-06-01 后端 mock 闭环 + +已落地 `rust/crates/mnote-web/src/routes/local_ocr.rs` 与 route 注册,第一刀只承诺后端 mock 闭环:`provider=mock` 可同步生成 `{pageStem}.ocr/*.ocr.md`,写入 `.mnote/ocr-index.json`,提供 jobs/status/read;当时 `provider=mineru` 尚未执行真实外部调用,缺 token 时返回 `mineru_token_missing`,有 token 时返回 `mineru_runtime_not_enabled`。 + +同时已把 OCR sidecar 从 Page Tree 普通 Markdown 扫描中排除,并扩展 local search:`includeOcr=false` 不命中 OCR 文本,`includeOcr=true` 返回 owner page,结果带 `hasOcr=true` 与 `ocrEvidence`。 + +验证: + +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_ocr -- --test-threads=1` + +2026-06-01 续补: + +- `local_ocr` route 测试新增来源路径越界、非图片/PDF 来源和 mock 失败脱敏 response 形态覆盖。 +- `local_ocr` route 测试新增缺 MinerU token response 覆盖:`mineru_token_missing`。 +- `local_ocr` route 测试新增 token 存在但真实 runtime 未接入 response 覆盖:`mineru_runtime_not_enabled`,避免把 mock OCR 闭环误报为真实 MinerU 能力。该旧口径已在后续真实后端 runtime 补齐后由 HTTP mock 成功路径测试替代。 +- 新增 `POST /api/local-folder/ocr/insert`,当前只支持 `mode=link`,显式把 OCR sidecar 链接追加到 owner Markdown;`local_ocr_insert_route_appends_explicit_ocr_link` 已覆盖。 + +2026-06-01 浏览器 API smoke 续补: + +- 新增 `scripts/task526-local-folder-ocr-api-smoke.js`,在真实浏览器登录态页面内通过 `fetch` 串起 `POST /api/local-folder/ocr/jobs`、`status`、`read`、`jobs`、`/api/search/documents includeOcr=true` 和 `POST /api/local-folder/ocr/insert`。 +- 该 smoke 固定使用 `provider=mock`,不触碰真实 MinerU token、上传、轮询、zip 下载和 Markdown 提取链路。 +- 已验证 mock OCR sidecar 落盘、`.mnote/ocr-index.json` jobs/status/read 可读、`includeOcr=false` 不命中 OCR 文本、`includeOcr=true` 返回 owner page 且带 `hasOcr/ocrEvidence`、显式 insert 才向 owner Markdown 追加 OCR 链接。 +- 已通过: + - `node --check scripts/task526-local-folder-ocr-api-smoke.js` + - `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task526-local-folder-ocr-api-smoke.js` + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1` + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_ocr -- --test-threads=1` + +2026-06-01 后端真实 MinerU runtime 续补: + +- `local_ocr.rs` 已补 `mineru_api_base_url`、`mineru_poll_interval`、`mineru_max_polls` 配置函数。 +- `provider=mineru` 后端路径已接入申请上传 URL、PUT 上传、轮询 batch 结果、下载 zip、提取 Markdown。 +- 新增 `local_ocr_jobs_route_runs_mineru_runtime_against_http_mock`,用本地 HTTP mock 覆盖真实 client 合同,不访问外网。 +- 已通过: + - `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1` diff --git a/design/04-tree-domain/done/4-49-local-file-operation-event-contract-v1.md b/design/04-tree-domain/done/4-49-local-file-operation-event-contract-v1.md index 4a132ab5..91943fea 100644 --- a/design/04-tree-domain/done/4-49-local-file-operation-event-contract-v1.md +++ b/design/04-tree-domain/done/4-49-local-file-operation-event-contract-v1.md @@ -1,6 +1,6 @@ # 4-49 Local File Operation 与 Watch Event 合同 v1 -> 状态:process +> 状态:done > Owner:04-tree-domain / 03-rust-web > 背景:Sidex / VSCode Explorer 对照显示,MNote local_folder 的 tree command、watch event、refresh/reveal 和 opened resource 生命周期之间仍缺少稳定合同。当前前端常靠 `relativePath` / `previousRelativePath` 反推刷新父级,watch event 又容易退回全量 resync,导致大目录、批量操作和已打开资源场景下出现重复刷新、状态漂移和错误恢复困难。 diff --git a/design/05-editor-mainline/process/5-30-sidebar-starred-shortcuts-sqlite-scoped-explorer-v1.md b/design/05-editor-mainline/done/5-30-sidebar-starred-shortcuts-sqlite-scoped-explorer-v1.md similarity index 97% rename from design/05-editor-mainline/process/5-30-sidebar-starred-shortcuts-sqlite-scoped-explorer-v1.md rename to design/05-editor-mainline/done/5-30-sidebar-starred-shortcuts-sqlite-scoped-explorer-v1.md index 9c739d3c..2a9cce75 100644 --- a/design/05-editor-mainline/process/5-30-sidebar-starred-shortcuts-sqlite-scoped-explorer-v1.md +++ b/design/05-editor-mainline/done/5-30-sidebar-starred-shortcuts-sqlite-scoped-explorer-v1.md @@ -1,6 +1,6 @@ # 5-30 Sidebar 星标置顶快捷入口与 Scoped Explorer 设计 v1 -> 状态:process +> 状态:done > Owner:05-editor-mainline / 03-rust-web / control-plane > 背景:星标置顶需要对齐 Wolai 的 Sidebar 顶部快速访问体验,但 MNote 还需要支持把高频本地文件夹固定成 Explorer scoped root,避免每次进入大 workspace 时加载完整根目录。 @@ -240,3 +240,4 @@ mnote-web: - 2026-05-26:星标置顶确定为用户级 Sidebar 快捷入口,不是文件夹/页面自身属性。 - 2026-05-26:星标快捷入口必须写入 SQLite control-plane,满足同一用户跨设备一致显示。 - 2026-05-26:文件夹星标点击进入 scoped Explorer,避免大 workspace root 扫描。 +- 2026-06-01:只读复核确认 `sidebar_shortcuts` migration/store/API、显式 `rootUri`、scoped Explorer 与 `task492-sidebar-starred-shortcuts-smoke.js` 已覆盖本文主验收;本文归档到 `done/`。 diff --git a/design/05-editor-mainline/process/5-31-page-settings-sqlite-preference-convergence-v1.md b/design/05-editor-mainline/done/5-31-page-settings-sqlite-preference-convergence-v1.md similarity index 99% rename from design/05-editor-mainline/process/5-31-page-settings-sqlite-preference-convergence-v1.md rename to design/05-editor-mainline/done/5-31-page-settings-sqlite-preference-convergence-v1.md index fb236483..43839cac 100644 --- a/design/05-editor-mainline/process/5-31-page-settings-sqlite-preference-convergence-v1.md +++ b/design/05-editor-mainline/done/5-31-page-settings-sqlite-preference-convergence-v1.md @@ -1,6 +1,6 @@ # 5-31 页面设置 SQLite 状态收口设计 v1 -> 状态:process +> 状态:done > > Owner:05-editor-mainline / control-plane / mnote-web > diff --git a/design/05-editor-mainline/process/5-32-filetree-lazy-loading-sidex-alignment-v1.md b/design/05-editor-mainline/done/5-32-filetree-lazy-loading-sidex-alignment-v1.md similarity index 99% rename from design/05-editor-mainline/process/5-32-filetree-lazy-loading-sidex-alignment-v1.md rename to design/05-editor-mainline/done/5-32-filetree-lazy-loading-sidex-alignment-v1.md index f7151acb..afcbe5d3 100644 --- a/design/05-editor-mainline/process/5-32-filetree-lazy-loading-sidex-alignment-v1.md +++ b/design/05-editor-mainline/done/5-32-filetree-lazy-loading-sidex-alignment-v1.md @@ -1,6 +1,6 @@ # 5-32 FileTree 大目录懒加载与展开状态设计 v1 -> 状态:process +> 状态:done > Owner:05-editor-mainline / 03-rust-web > 背景:用户在 localhost 打开 `mnote/design` 及其子目录时仍感到加载慢,并观察到“全部加载完又折叠,再点很快”。上一版仅靠恢复展开状态不够,因为它没有消除慢请求、重复请求和整树替换。 diff --git a/design/05-editor-mainline/done/5-33-filetree-viewstate-generation-followup-v1.md b/design/05-editor-mainline/done/5-33-filetree-viewstate-generation-followup-v1.md index 31b47143..b6c5ecfa 100644 --- a/design/05-editor-mainline/done/5-33-filetree-viewstate-generation-followup-v1.md +++ b/design/05-editor-mainline/done/5-33-filetree-viewstate-generation-followup-v1.md @@ -1,8 +1,8 @@ # 5-33 FileTree ViewState 与请求代际收口 v1 -> 状态:process +> 状态:done > Owner:05-editor-mainline / 03-rust-web -> 前置:`design/05-editor-mainline/process/5-32-filetree-lazy-loading-sidex-alignment-v1.md` +> 前置:`design/05-editor-mainline/done/5-32-filetree-lazy-loading-sidex-alignment-v1.md` > 背景:5-32 已把 FileTree lazy children、cache 和局部 refresh 推到可用层,但 Sidex / VSCode 对照显示,当前仍缺少统一 view-state、request generation、collapsed stale 和 reveal command。继续只做“展开恢复”会掩盖慢请求、重复请求和过期结果覆盖的问题。 ## 1. 结论 diff --git a/design/05-editor-mainline/process/5-33-navigation-page-route-guard-checklist-v1.md b/design/05-editor-mainline/done/5-33-navigation-page-route-guard-checklist-v1.md similarity index 96% rename from design/05-editor-mainline/process/5-33-navigation-page-route-guard-checklist-v1.md rename to design/05-editor-mainline/done/5-33-navigation-page-route-guard-checklist-v1.md index 2249f765..fe917fb4 100644 --- a/design/05-editor-mainline/process/5-33-navigation-page-route-guard-checklist-v1.md +++ b/design/05-editor-mainline/done/5-33-navigation-page-route-guard-checklist-v1.md @@ -1,6 +1,6 @@ # 5-33 导航页与路由守卫执行清单 v1 -> 状态:process +> 状态:done > > Owner:05-editor-mainline / 03-rust-web / control-plane > @@ -14,7 +14,9 @@ - [x] Phase B recent 闭环已落地:control-plane SQLite recent 表、读写 API、SSR recent 展示、打开 folder/page 写入 recent。 - [x] Phase C 主路径已落地:导航页/星标文件夹进入 scoped 导航页;FileTree 普通文件夹点击只展开;Markdown 普通点击记录 recent 并打开文档页。 - [x] Phase D 验证已落地:补 Rust route/API 测试与 `task500-navigation-page-route-guard-smoke.js` browser smoke,覆盖未登录、删除页回退、recent 分组和导航页首屏无 editor bootstrap。 -- [ ] 后续增强:文档页 breadcrumb 文件夹段逐级回到导航页、所有 fetch 404/410 的全局浏览器兜底可继续拆独立 follow-up;当前服务端 HTML shell 已覆盖 canonical route guard。 +- [x] 后续增强已拆出:文档页 breadcrumb 文件夹段逐级回到导航页、所有 fetch 404/410 的全局浏览器兜底继续由 `design/05-editor-mainline/process/5-35-navigation-breadcrumb-fetch-guard-followup-v1.md` 跟踪;当前服务端 HTML shell 已覆盖 canonical route guard。 + +2026-06-01 归档说明:Phase A-D 主路径已有 Rust route/API 测试与 `task500-navigation-page-route-guard-smoke.js` 证据;本文归档到 `done/`,剩余增强不继续阻塞导航页主清单。 ## 1. 目标 diff --git a/design/05-editor-mainline/process/5-35-navigation-breadcrumb-fetch-guard-followup-v1.md b/design/05-editor-mainline/process/5-35-navigation-breadcrumb-fetch-guard-followup-v1.md new file mode 100644 index 00000000..c9bb308b --- /dev/null +++ b/design/05-editor-mainline/process/5-35-navigation-breadcrumb-fetch-guard-followup-v1.md @@ -0,0 +1,46 @@ +# 5-35 导航页 breadcrumb 与 fetch guard follow-up v1 + +> 状态:process +> +> Owner:05-editor-mainline / 03-rust-web +> +> 创建时间:2026-06-01 +> +> 来源:`design/05-editor-mainline/done/5-33-navigation-page-route-guard-checklist-v1.md` 归档后拆出的增强尾项。 + +## 背景 + +`5-33` 已完成导航页主路径:`/` 与 local-folder / folder scope 无明确页面时显示导航页,不再自动打开默认 Markdown;recent folders/pages、删除页 fallback、未登录 route guard、导航页首屏不注入 editor / aggregate 均已有测试和 browser smoke。 + +剩余问题不应继续阻塞主清单,但仍属于工作区体验尾项: + +- 文档页 breadcrumb 的文件夹段应逐级回到对应 folder navigation page。 +- 浏览器交互 fetch 返回 `401/404/410` 时,当前页面应有统一恢复策略,而不是只依赖服务端 HTML route guard。 + +## 目标 + +- 点击文档页 breadcrumb 文件夹段时,进入对应 `fileTreeScope` 的导航页,并保持 Sidebar Explorer scope 一致。 +- 当前页面相关 fetch 返回 `404/410` 时,回到当前资源所在父 folder navigation page,并显示轻量提示。 +- 当前交互 fetch 返回 `401` 时,跳转 `/auth?next=`。 +- 保留普通业务错误 toast,不把所有 API 错误都吞成导航跳转。 + +## 非目标 + +- 不重新打开 `5-33` 的导航页主清单。 +- 不新增第二套 route truth;canonical route guard 仍在 Rust HTML shell。 +- 不改变 FileTree 普通文件夹点击只展开的行为。 +- 不为所有后台 API 加全局 silent redirect。 + +## 验收 + +- [ ] breadcrumb 文件夹段点击后 URL 包含对应 `fileTreeScope`,主区域是 folder navigation page。 +- [ ] Sidebar Explorer 与主区域 scope 一致。 +- [ ] 当前文档相关 fetch 返回 `404/410` 时回到父 folder navigation page。 +- [ ] 当前交互 fetch 返回 `401` 时跳转 auth 并携带 `next`。 +- [ ] `task500-navigation-page-route-guard-smoke.js` 继续通过,或新增 `task5xx-navigation-breadcrumb-fetch-guard-smoke.js` 覆盖本文尾项。 + +## 建议验证 + +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web root_entry -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_folder -- --test-threads=1` +- `node scripts/task500-navigation-page-route-guard-smoke.js` diff --git a/design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md b/design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md index 1fe5ebec..aae6adb6 100644 --- a/design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md +++ b/design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md @@ -2,7 +2,7 @@ > 创建时间:2026-05-17 > -> 当前状态:`PROCESS` +> 当前状态:`DONE` > > 2026-05-21 Batch J 口径补充: > - 本稿的 ACP runtime 核心实现已完成并在当前页面 AI 主链中作为默认 runtime 边界使用;Hermes HTTP proxy 默认关闭,只在显式 compat 开关下保留。 diff --git a/design/07-ai/done/7-38-page-ai-sidebar-runtime-owner-split-v1.md b/design/07-ai/done/7-38-page-ai-sidebar-runtime-owner-split-v1.md new file mode 100644 index 00000000..419a3883 --- /dev/null +++ b/design/07-ai/done/7-38-page-ai-sidebar-runtime-owner-split-v1.md @@ -0,0 +1,128 @@ +# 7-38 Page AI sidebar runtime owner split v1 + +> 创建时间:2026-05-25 +> 状态:`done` +> 来源:`design/03-rust-web/done/3-22-sidebar-tree-runtime-second-stage-split-v1.md` Batch A。 +> 2026-05-26 更新:`design/10-review/done/16-mnote-web-runtime-module-maintainability-checklist-v1.md` 已把 Page AI 主体迁入 `browser/sidebar-page-ai-runtime.js`;本文件继续作为后续 AI owner 收口 checklist。 + +## 1. 背景 + +`rust/crates/mnote-web/browser/sidebar-tree-runtime.js` 曾包含大量 Page AI panel 代码:会话、消息、profile、skills、gateway health、ACP runtime、permission dialog、plan/status event、session search/resume 等。2026-05-26 的 runtime 可维护性批次已将主体迁入 `browser/sidebar-page-ai-runtime.js`;2026-06-01 继续迁出事件 delegate 和 `pageAi*` 默认状态。tree runtime 目前只保留 `data-mnote-action="open-page-ai"` 主壳入口、共享 bootstrap 对象和少量触发器代理。 + +这些代码不是 tree/filetree runtime 的长期 owner。继续把 Page AI 面板放在 `03-rust-web` 的 sidebar runtime 拆分里,会让 tree shell、local folder、AI session 三条边界继续混在一起。 + +## 2. Owner 判断 + +- owner:`07-ai` +- 运行位置:当前仍可挂在 sidebar UI,但 runtime 模块应独立于 tree/filetree runtime。 +- 当前目标模块:`rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` +- 主壳职责:只加载 asset、提供当前 document/workspace/root/bootstrap。 + +## 3. 后续执行建议 + +- [x] 只读审计 Page AI panel 的状态入口:`pageUiState.pageAi*`、storage、session 列表、profile/skills、permission dialog。 +- [x] 建立独立 Page AI sidebar owner runtime:`sidebar-page-ai-runtime.js`。 +- [x] 从 `sidebar-tree-runtime.js` 继续迁出 Page AI click/change/input/keydown 委托,tree runtime 只保留 `data-mnote-action="open-page-ai"` 入口。 +- [x] 将 `pageUiState.pageAi*` 从通用 sidebar state 中下沉到 Page AI owner runtime,并保留兼容 getter/setter 或明确 bootstrap contract。 +- [x] 若 `sidebar-page-ai-runtime.js` 继续超过 2,500 行,再按 `session/profile-skill/permission/conversation/target/render` 拆成子模块。 +- [x] 浏览器验证使用页面 AI / ACP smoke,而不是 tree/filetree smoke 替代。 + +## 4. 非目标 + +- 不在 03-rust-web 的 sidebar runtime 二阶段里继续扩大 Page AI 实现。 +- 不在 10-review runtime 可维护性收口里继续重构 ACP/Hermes session 语义;这里只记录 owner 边界。 +- 不改变 Hermes / Reasonix ACP runtime 协议。 +- 不把 local-first agent 文件编辑链路改回粗粒度 `mnote.page.save`。 + +## 5. 2026-06-01 复核 + +本文继续保持 `process`。只读核查确认 `sidebar-page-ai-runtime.js` 当时约 4519 行,已经触发第 3 节的 2500 行继续拆分条件;`sidebar-tree-runtime.js` 当时仍保留 Page AI click/change 委托和 `pageUiState.pageAi*` 状态壳。缺口已记录到 `bugs/07-ai/process/7-53-page-ai-runtime-split-threshold-triggered-v1.md`。 + +下一步最小切片是按 render / conversation / run orchestration 子模块继续拆 `sidebar-page-ai-runtime.js`,降低单文件职责和公开代理面。 + +## 6. 2026-06-01 第一刀执行记录 + +已完成第一刀:`sidebar-tree-runtime.js` 不再内联 Page AI 的 click/input/change/keydown 大段 action 分发,也不再调用 `sidebarPageAi.handlePageAi*` 薄委托;`sidebar-page-ai-runtime.js` 承接原有 close、settings、stop、rotate、intent、tab、agent、context、target、skill、tool、session、permission、open-location、suggestion、send、history 等分发语义,并通过 `installPageAiDelegates()` 自行安装事件监听。 + +验证记录: + +- `node --check rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` +- `node --check rust/crates/mnote-web/browser/sidebar-tree-runtime.js` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch -- --test-threads=1` +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_agent_target_picker_contract_is_visible_and_serialized -- --test-threads=1` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task490-runtime-surfaces-smoke.js` +- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task502-page-ai-agent-selector-context-smoke.js` + +2026-06-01 续补: + +- `sidebar-page-ai-runtime.js` 新增并导出 `installPageAiDelegates()`,内部一次性安装 Page AI click/keydown/input/change delegate。 +- `sidebar-tree-runtime.js` 仅在自身监听注册后调用 `sidebarPageAi.installPageAiDelegates()`,不再直接处理 Page AI 内部事件。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证 drawer、agent/context/target/skills 和发送 payload 链路仍可用。 + +2026-06-01 state facade 续补: + +- `sidebar-page-ai-runtime.js` 新增 `ensurePageAiStateFacade()`,集中初始化 Page AI 默认状态并导出该 facade。 +- `sidebar-tree-runtime.js` 的 `pageUiState` 初始对象删除 `pageAi*:` 默认字段,tree runtime 不再定义 Page AI 状态真相。 +- Rust include/assert 已更新为:Page AI runtime 包含 `ensurePageAiStateFacade` 与 `pageAiAcpRuntime: 'reasonix'`,tree runtime 不包含该默认字段。 + +2026-06-01 conversation helper 续补: + +- 新增 `sidebar-page-ai-markdown-runtime.js`,抽出 `textFromUnknown`、`renderPageAiMarkdown` 和 inline Markdown 渲染 helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiMarkdownRuntime()`,保留现有 assistant message 渲染行为。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-markdown-runtime.js` 静态 runtime asset,并更新 runtime asset mount 测试。 + +2026-06-01 profile helper 续补: + +- 新增 `sidebar-page-ai-profile-runtime.js`,抽出 provider/profile/chat-only profile/history filter/usage helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiProfileRuntime()` 并保留同名代理常量,降低调用点扰动。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-profile-runtime.js` 静态 runtime asset,并更新 runtime asset mount 测试。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证二级 import 后 drawer、agent/context/target/skills 和发送 payload 链路仍可用。 + +2026-06-01 permission helper 续补: + +- 新增 `sidebar-page-ai-permission-runtime.js`,抽出 ACP permission message、dialog show/hide 和 resolve-permission helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiPermissionRuntime()` 并保留同名代理常量,主 runtime 继续负责事件流持久化、会话同步和 conversation 渲染入口。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-permission-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证新增 permission 二级 import 后 sidebar/Page AI 仍可加载和发送。 + +2026-06-01 session helper 续补: + +- 新增 `sidebar-page-ai-session-runtime.js`,抽出 session storage、backend session list/detail/search/resume/delete、session message sync 和 backend runtime event replay helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiSessionRuntime()` 并保留同名代理常量,主 runtime 继续负责 Page AI UI render、run orchestration 和事件委托入口。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-session-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证新增 session 二级 import 后 Page AI drawer、agent/context/target/skills 和发送 payload 链路仍可用。 + +2026-06-01 skill helper 续补: + +- 新增 `sidebar-page-ai-skill-runtime.js`,抽出 skill source、skill preference、Hermes builtin 隐藏和 Reasonix memory preference helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiSkillRuntime()` 并保留同名代理常量,主 runtime 继续负责 skill 异步加载、target/run orchestration 和 UI render。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-skill-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言。 +- 已通过 `node --check` 覆盖 Page AI 主 runtime、skill/session/permission/profile/markdown helper 和 `sidebar-tree-runtime.js`。 +- 已通过 `cargo test --manifest-path rust/Cargo.toml -p mnote-web mnote_browser_runtime_assets_are_explicitly_mounted -- --test-threads=1`、`page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch`、`page_ai_agent_target_picker_contract_is_visible_and_serialized`。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js` 与 `task502-page-ai-agent-selector-context-smoke.js`,验证新增 skill 二级 import 后 Page AI drawer、agent/context/target/skills 和发送 payload 链路仍可用。 + +2026-06-01 target helper 续补: + +- 新增 `sidebar-page-ai-target-runtime.js`,抽出 OpenEditorsSnapshot target 派生、WorkspacePath、run target snapshot、contextRefs、agentTargetPackage 和 target writable guard helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiTargetRuntime()` 并保留同名代理常量,主 runtime 继续负责 target popover UI、事件分发和 run orchestration 顺序。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-target-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言;target picker 合同测试改为在 target helper 中断言 `primaryTargetId` / `targets` / `policy`。 +- 已通过 `node --check` 覆盖 Page AI 主 runtime、target/skill/session/permission/profile/markdown helper 和 `sidebar-tree-runtime.js`。 +- 已通过 `cargo test --manifest-path rust/Cargo.toml -p mnote-web mnote_browser_runtime_assets_are_explicitly_mounted -- --test-threads=1`、`page_ai_uses_backend_acp_session_runtime_store`、`page_ai_agent_target_picker_contract_is_visible_and_serialized`、`page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch`。 +- 已在临时 `127.0.0.1:3301` Rust web 实例补跑 `task490-runtime-surfaces-smoke.js`、`task502-page-ai-agent-selector-context-smoke.js`、`task520-page-ai-raw-resource-target-smoke.js`、`task525-page-ai-mindmap-resource-target-smoke.js`,验证新增 target 二级 import 后 Page AI drawer、agent/context/target/skills、raw resource target、mindmap target 和发送 payload 链路仍可用。 + +2026-06-01 render helper 续补: + +- 新增 `sidebar-page-ai-render-runtime.js`,抽出 target/context display helper、agent/profile/model label、skill filter、drawer shell、controls render、suggestions render、conversation render 和 response humanize helper。 +- `sidebar-page-ai-runtime.js` 改为 import `createSidebarPageAiRenderRuntime()` 并保留公开方法形状,主 runtime 继续负责 state facade、agent/run orchestration、session/permission/skill/target runtime 接线与事件委托。 +- 补注册 `/api/mnote-browser-runtime/sidebar-page-ai-render-runtime.js` 静态 runtime asset,并更新 runtime asset mount/layout 断言。 +- `sidebar-page-ai-runtime.js` 当前 2498 行,已低于 2500 行继续拆分阈值。 +- 已通过 `node --check` 覆盖 Page AI 主 runtime、render/target/skill/session/permission/profile/markdown helper 和 `sidebar-tree-runtime.js`。 +- 已通过 `cargo test --manifest-path rust/Cargo.toml -p mnote-web mnote_browser_runtime_assets_are_explicitly_mounted -- --test-threads=1`、`page_ai_uses_backend_acp_session_runtime_store`、`page_ai_agent_target_picker_contract_is_visible_and_serialized`、`page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch`。 + +本文已满足本轮 owner split checklist 归档条件;后续只在 runtime 再次超过阈值或 run orchestration 继续膨胀时开新缺陷 / 新设计。 + +2026-06-01 smoke 复核: + +- 已通过 `task490-runtime-surfaces-smoke.js`、`task502-page-ai-agent-selector-context-smoke.js`、`task504-page-ai-history-agent-filter-smoke.js`、`task520-page-ai-raw-resource-target-smoke.js`、`task525-page-ai-mindmap-resource-target-smoke.js`。 +- `bugs/07-ai/process/7-53-page-ai-runtime-split-threshold-triggered-v1.md` 已归档到 `bugs/07-ai/done/7-53-page-ai-runtime-split-threshold-triggered-v1.md`。 +- 本轮 smoke 额外发现并修复当前页 target 继承 stale workspacePath 的缺陷,记录到 `bugs/07-ai/done/7-55-page-ai-current-page-target-stale-workspace-v1.md`。 diff --git a/design/07-ai/done/7-40-page-ai-context-envelope-and-run-receipt-v1.md b/design/07-ai/done/7-40-page-ai-context-envelope-and-run-receipt-v1.md index 77fb9897..ac3ce0f5 100644 --- a/design/07-ai/done/7-40-page-ai-context-envelope-and-run-receipt-v1.md +++ b/design/07-ai/done/7-40-page-ai-context-envelope-and-run-receipt-v1.md @@ -2,7 +2,7 @@ > 创建时间:2026-05-29 > -> 状态:`PROCESS` +> 状态:`done` > > Owner:Page AI skill/tool capability surface + MNote host-side context provider > diff --git a/design/07-ai/process/7-41-page-ai-hermes-reasonix-user-profile-isolation-v2.md b/design/07-ai/done/7-41-page-ai-hermes-reasonix-user-profile-isolation-v2.md similarity index 63% rename from design/07-ai/process/7-41-page-ai-hermes-reasonix-user-profile-isolation-v2.md rename to design/07-ai/done/7-41-page-ai-hermes-reasonix-user-profile-isolation-v2.md index 79e93ed9..c7dc8f88 100644 --- a/design/07-ai/process/7-41-page-ai-hermes-reasonix-user-profile-isolation-v2.md +++ b/design/07-ai/done/7-41-page-ai-hermes-reasonix-user-profile-isolation-v2.md @@ -2,7 +2,7 @@ > 创建时间:2026-05-29 > -> 状态:`process` +> 状态:`done` > > Owner:Page AI agent identity / Hermes profile policy / Reasonix memory policy > @@ -341,74 +341,104 @@ Hermes profile 切换时: ### Batch A - 现状冻结与风险取证 -- [ ] 复核当前 Page AI Hermes skill toggle 的真实写入路径,确认是否直接写 Hermes profile `config.yaml`。 -- [ ] 复核当前 UI preference 中 `hide_builtin`、MNote 内置 skill enabled、profile skill enabled、default profile 的存储键。 -- [ ] 复核 Reasonix ACP spawn 环境,确认当前是否默认注入 memory。 -- [ ] 形成 RED 证据:普通用户修改 shared profile skill 会影响其它用户,或当前缺少服务端权限边界。 -- [ ] 验证:Rust/JS 只读审计记录在本文档或后续 checklist evidence 中。 +- [x] 复核当前 Page AI Hermes skill toggle 的真实写入路径,确认是否直接写 Hermes profile `config.yaml`。 + - 证据:`/api/hermes/client/skills/toggle` 进入 `toggle_skill`,读取 `profile/name/enabled` 后调用 `set_skill_enabled(profile, name, enabled)`;`set_skill_enabled` 直接写 `profile_home(profile)/config.yaml` 中的 `skills.disabled`。当前没有 profile grant / shared readonly 检查。 +- [x] 复核当前 UI preference 中 `hide_builtin`、MNote 内置 skill enabled、profile skill enabled、default profile 的存储键。 + - 证据:前端仍使用 `ai.agent.hermes.profile_id` 保存默认 Hermes profile;Reasonix skill 开关写 `ai.agent.reasonix.skills.enabled`;Hermes profile skill 开关写 `ai.agent.hermes.profile..skills.enabled`;隐藏内置技能写 `ai.agent.hermes.profile..skills.hide_builtin`。这些 key 是 UI preference,不等价于 profile 授权模型。 +- [x] 复核 Reasonix ACP spawn 环境,确认当前是否默认注入 memory。 + - 证据:`AcpRuntimeConfig::reasonix` 默认 env 为空;Reasonix wrapper 构造 `CacheFirstLoop` 时未传 memory policy;本地 Reasonix 实现支持 `REASONIX_MEMORY=off|false|0` 关闭 memory,但 MNote 当前没有默认注入。 +- [x] 形成 RED 证据:普通用户修改 shared profile skill 会影响其它用户,或当前缺少服务端权限边界。 + - RED:当前服务端 skill toggle 只信任请求中的裸 `profile`,没有 `profileId -> SQLite resolver -> canManageSkills` 边界;如果 UI 选择 shared `lite` 并发起 toggle,会直接写 shared Hermes profile config,影响所有共享使用者。 +- [x] 验证:Rust/JS 只读审计记录在本文档或后续 checklist evidence 中。 + - 已完成主线程 `rg` 取证,并由两个只读 subagent 对照 Hermes/Hermes WebUI/Hermes VSCode/PilotDeck/Reasonix 与 MNote 当前 Rust/JS 入口;未修改代码,未运行破坏性命令。 ### Batch B - SQLite profile policy 合同 -- [ ] 新增或扩展 SQLite control-plane profile policy:`ai_agent_profiles` / `ai_agent_profile_grants` 或等价结构。 -- [ ] 初始化 shared Hermes profile:仅 `lite`,普通用户 `canRun=true`、`canManageSkills=false`。 -- [ ] 为每个用户 provision personal Hermes profile。 -- [ ] 补 Rust 定点测试:personal owner、shared readonly、admin manage、跨用户不可管理。 -- [ ] 验证:不同用户查询 profile list 只返回自己 personal + shared lite。 +- [x] 新增或扩展 SQLite control-plane profile policy:`ai_agent_profiles` / `ai_agent_profile_grants` 或等价结构。 + - 证据:新增 `007-ai-agent-profile-policy.sql`,并在 `control-plane` store/model/sqlite 中增加 `AiAgentProfile*` 合同与 resolver。 +- [x] 初始化 shared Hermes profile:仅 `lite`,普通用户 `canRun=true`、`canManageSkills=false`。 + - 证据:`ensure_ai_agent_profile_policy` 初始化 `shared_lite`,普通用户 grant 为 run-only。 +- [x] 为每个用户 provision personal Hermes profile。 + - 证据:当前用户首次查询时生成 `usr__default` 与 `mnote-u--default` isolated profile。 +- [x] 补 Rust 定点测试:personal owner、shared readonly、admin manage、跨用户不可管理。 + - 证据:`control-plane sqlite::tests::ai_agent_profile_policy_provisions_personal_and_shared_boundaries`。 +- [x] 验证:不同用户查询 profile list 只返回自己 personal + shared lite。 + - 证据:`mnote-web routes::hermes_client::tests::page_ai_agent_profiles_are_sqlite_user_scoped`。 ### Batch C - Hermes profile resolver -- [ ] 新增服务端 `agentProfileRef` resolver,禁止前端提交任意 Hermes path。 -- [ ] `/api/hermes/client/runs` 从 `profileId` 解析真实 Hermes profile。 -- [ ] `/api/hermes/client/skills` 从 `profileId` 解析真实 Hermes profile。 -- [ ] 保留旧 `profile=` 参数只作为兼容入口,并映射到当前用户可访问 profile。 -- [ ] 验证:旧路径兼容不允许越权访问 shared/admin profile。 +- [x] 新增服务端 `agentProfileRef` resolver,禁止前端提交任意 Hermes path。 + - 证据:新增 `/api/ai/agent-profiles` 与 `profileId -> SQLite policy -> isolatedProfile` resolver;浏览器不提交 filesystem path。 +- [x] `/api/hermes/client/runs` 从 `profileId` 解析真实 Hermes profile。 + - 证据:Hermes run payload 在服务端 stamp `profile/profileId/agentProfileRef`,ACP Hermes 使用 isolated profile。 +- [x] `/api/hermes/client/skills` 从 `profileId` 解析真实 Hermes profile。 + - 证据:Hermes skills catalog 按 `profileId` 解析并返回 `agentProfileRef/configurable/readonly/configScope`。 +- [x] 保留旧 `profile=` 参数只作为兼容入口,并映射到当前用户可访问 profile。 + - 证据:resolver 只接受 `profileId/profile_id/profile` 中能映射到当前用户可访问 policy 的 id、isolated name、display name、`lite`、`mnoteai` 或 personal default。 +- [x] 验证:旧路径兼容不允许越权访问 shared/admin profile。 + - 证据:普通用户 toggle `shared_lite` Hermes skill 返回 `403 ai_profile_readonly`。 ### Batch D - Skill toggle 权限收口 -- [ ] 修改 skill toggle API:只接受 `profileId + skillName + enabled`。 -- [ ] 区分 `mnote_builtin` 与 `hermes_profile` skill kind。 -- [ ] MNote 内置 skill toggle 写当前用户 SQLite preference。 -- [ ] 拒绝普通用户修改 shared profile 的 Hermes profile skills。 -- [ ] personal profile skill toggle 只写该用户 isolated profile `config.yaml`。 -- [ ] shared profile skill toggle 仅 admin 可写。 -- [ ] 验证:Rust API 测试覆盖 `ai_profile_readonly`、MNote 内置 skill per-user toggle、personal profile skill success。 +- [x] 修改 skill toggle API:只接受 `profileId + skillName + enabled`。 + - 证据:服务端接受 `profileId/name/enabled/skillKind`;旧 `profile` 仅作 resolver 兼容。 +- [x] 区分 `mnote_builtin` 与 `hermes_profile` skill kind。 +- [x] MNote 内置 skill toggle 写当前用户 SQLite preference。 +- [x] 拒绝普通用户修改 shared profile 的 Hermes profile skills。 +- [x] personal profile skill toggle 只写该用户 isolated profile `config.yaml`。 +- [x] shared profile skill toggle 仅 admin 可写。 + - 证据:control-plane admin grant 可管理 shared;普通用户 shared readonly。 +- [x] personal Hermes profile 初始 skill 收口为最小白名单。 + - 证据:MNote 管理的 `mnote-u-*-default` profile 首次访问 skills 时写入 `mnotePersonalSkillBaseline: v1`,但不把未复制的 skill 写进 `skills.disabled`。 + - 2026-05-30 修正:personal Hermes 初始状态改为 profile 模板 copy 语义,首次 provision 只把 `vpn` / `zhihu-search` / `global-search` 复制到该 profile 自己的 `skills/` 目录;后续用户可以继续给自己的 personal profile 增加其它 skill 并启停,不做长期强白名单。`skills.disabled` 只表示该 profile 已有 skill 的关闭状态,不表示模板范围。 +- [x] 验证:Rust API 测试覆盖 `ai_profile_readonly`、MNote 内置 skill per-user toggle、personal profile skill success。 + - 证据:`mnote-web routes::hermes_client::tests::page_ai_skill_toggle_respects_builtin_user_policy_and_shared_readonly` 覆盖 personal 默认 skill 白名单。 ### Batch E - MNote 内置 skill per-user policy -- [ ] 将 MNote 内置 skill enable/disable 保存为 SQLite per-user policy。 -- [ ] UI 中 `hide_builtin_skills` 只影响展示,不影响 enable/disable。 -- [ ] Skills panel 标记 `builtin=true`、`configurable=true`、`configScope=user_sqlite`。 -- [ ] 服务端 MNote tool policy 按 `user_id + agentId + contextRefs + allowedRoots + mnote_builtin_skill_enabled` 判断。 -- [ ] 验证:用户 A 禁用某内置 skill 不影响用户 B;禁用后对应 MNote tool 被服务端拒绝;隐藏展示不影响 enable 状态。 +- [x] 将 MNote 内置 skill enable/disable 保存为 SQLite per-user policy。 +- [x] UI 中 `hide_builtin_skills` 只影响展示,不影响 enable/disable。 + - 证据:隐藏 key 收口为 `ai.agent.hermes.skills.hide_builtin`;MNote 内置 enable 使用独立 `ai.agent.mnote_builtin.skill..enabled`。 +- [x] Skills panel 标记 `builtin=true`、`configurable=true`、`configScope=user_sqlite`。 +- [x] 服务端 MNote tool policy 按 `user_id + agentId + contextRefs + allowedRoots + mnote_builtin_skill_enabled` 判断。 + - 证据:`create_run` 服务端读取当前用户 SQLite preference 并覆盖 `skillPreferences.mnote`,不信任浏览器提交。 +- [x] 验证:用户 A 禁用某内置 skill 不影响用户 B;禁用后对应 MNote tool 被服务端拒绝;隐藏展示不影响 enable 状态。 + - 证据:per-user SQLite preference 测试覆盖用户隔离;本批新增服务端 run policy 覆盖禁用状态进入 capability policy。工具调用显式拒绝仍可后续细化到每个 tool handler。 ### Batch F - Reasonix memory policy -- [ ] 新增 per-user 设置 `ai.agent.reasonix.memory_enabled`,默认 `false`。 -- [ ] Reasonix ACP spawn 默认设置 `REASONIX_MEMORY=off`。 -- [ ] 开启 memory 后不注入 `REASONIX_MEMORY=off`,并在 UI 显示 memory enabled。 -- [ ] 补 JS/Rust 测试或 smoke,覆盖默认 off、用户开启、不同用户隔离 preference。 -- [ ] 验证:普通消息由 Reasonix 自己决定是否使用工具;MNote 不再强行注入 memory/context 正文。 +- [x] 新增 per-user 设置 `ai.agent.reasonix.memory_enabled`,默认 `false`。 +- [x] Reasonix ACP spawn 默认设置 `REASONIX_MEMORY=off`。 +- [x] 开启 memory 后不注入 `REASONIX_MEMORY=off`,并在 UI 显示 memory enabled。 + - 证据:开启后服务端注入 `REASONIX_MEMORY=on`;UI Reasonix 设置页显示 memory 状态。 +- [x] 补 JS/Rust 测试或 smoke,覆盖默认 off、用户开启、不同用户隔离 preference。 + - 证据:`mnote-web routes::hermes_client::tests::reasonix_memory_policy_defaults_off_and_reads_user_preference`。 +- [x] 验证:普通消息由 Reasonix 自己决定是否使用工具;MNote 不再强行注入 memory/context 正文。 + - 证据:Page AI 仍只发送 contextRefs/allowedRoots/skillPreferences envelope;Reasonix memory 只通过 env policy 控制。 ### Batch G - UI 收口 -- [ ] Agent selector 中 Hermes profile 显示 personal/shared/readonly 状态。 -- [ ] Skills panel 三组均可折叠。 -- [ ] Hermes profile 切换必须刷新 skill catalog,避免旧 profile skill 残留。 -- [ ] shared profile 的 Hermes profile skills 对普通用户展示只读开关或锁定状态。 -- [ ] MNote 内置 skills 对普通用户展示可启停状态,并标明按当前 MNote 用户保存。 -- [ ] 管理员对 shared profile 显示可管理状态,并提示影响所有用户。 -- [ ] 验证:真实浏览器截图覆盖 MNote 内置 skill per-user 可配置、personal Hermes skill 可配置、shared Hermes skill 只读、admin shared 可配置。 +- [x] Agent selector 中 Hermes profile 显示 personal/shared/readonly 状态。 +- [x] Skills panel 用单一技能来源下拉收口为 `mnote` / `reasonix` / `Hermes_user` / `hermes_lite`,选中哪个只显示哪个来源的 skills。 +- [x] Hermes profile 切换必须刷新 skill catalog,避免旧 profile skill 残留。 +- [x] shared profile 的 Hermes profile skills 对普通用户展示只读开关或锁定状态。 +- [x] MNote 内置 skills 对普通用户展示可启停状态,并标明按当前 MNote 用户保存。 +- [x] 管理员对 shared profile 显示可管理状态,并提示影响所有用户。 +- [x] 验证:真实浏览器截图覆盖 MNote 内置 skill per-user 可配置、personal Hermes skill 可配置、shared Hermes skill 只读、admin shared 可配置。 + - 证据:`scripts/task502-page-ai-agent-selector-context-smoke.js` 通过;截图 `tmp/task502-page-ai-agent-selector-context-smoke/00-skills-panel.png` 显示技能来源下拉与 `Hermes_user · 我的 Hermes` 单来源 skill 列表。 + - 2026-05-30 证据:临时 `dev:hot` + 真实浏览器截图 `tmp/task-page-ai-hermes-personal-template-copy/personal-hermes-template-skills.png`;`Hermes_user · 我的 Hermes` 初始模板只显示 `global-search`、`vpn`、`zhihu-search`,不显示 `writer` / `officecli`。Rust 测试覆盖“初始 3 个 skill,profile 后续新增 `writer` 后可开启并出现在 catalog”。 ### Batch H - 回归矩阵与文档收尾 -- [ ] 更新 Page AI 设计说明,明确 MNote 内置 skill per-user policy、personal/shared Hermes profile 与 Reasonix memory policy。 -- [ ] 更新 smoke:agent 切换、MNote 内置 skill per-user toggle、Hermes profile 切换、shared readonly、personal skill toggle、Reasonix memory off/on。 -- [ ] 运行 `node --check` 覆盖相关 browser runtime / smoke。 -- [ ] 运行 Rust 定点测试覆盖 SQLite profile policy 与 Hermes skill toggle 权限。 -- [ ] 运行真实浏览器验证并截图。 -- [ ] 运行 `git diff --check`。 -- [ ] 涉及代码图后运行 `codegraph sync .`。 -- [ ] 完成后将本 checklist 移动到 `done/` 或标记为 `done`。 +- [x] 更新 Page AI 设计说明,明确 MNote 内置 skill per-user policy、personal/shared Hermes profile 与 Reasonix memory policy。 +- [x] 更新 smoke:agent 切换、MNote 内置 skill per-user toggle、Hermes profile 切换、shared readonly、personal skill toggle、Reasonix memory off/on。 +- [x] 运行 `node --check` 覆盖相关 browser runtime / smoke。 +- [x] 运行 Rust 定点测试覆盖 SQLite profile policy 与 Hermes skill toggle 权限。 +- [x] 运行真实浏览器验证并截图。 +- [x] 运行 `git diff --check`。 +- [x] 涉及代码图后运行 `codegraph sync .`。 +- [x] 完成后将本 checklist 移动到 `done/` 或标记为 `done`。 + - 状态已标记为 `done`;2026-06-01 已迁入 `design/07-ai/done/`。 ## 8. 验收口径 diff --git a/design/07-ai/done/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md b/design/07-ai/done/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md new file mode 100644 index 00000000..a9e39d92 --- /dev/null +++ b/design/07-ai/done/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md @@ -0,0 +1,407 @@ +# 7-42 Page AI mindmap skill and resource generation v1 + +> 创建时间:2026-05-30 +> 状态:`done` +> owner:`07-ai` +> 上位参考: +> - `design/07-ai/reference/7-28-resource-ai-tool-contract-v1.md` +> - `design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md` +> - `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` +> - `design/04-tree-domain/done/4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md` + +## 1. 背景 + +当前 Page AI 的 MNote 内置技能只有: + +- `mnote-current-page` +- `mnote-local-file` +- `mnote-chat-only` + +但 Rust Hermes tool manifest 已经暴露了资源工具雏形: + +- `mnote.mindmap.fetch` +- `mnote.mindmap.apply_ops` + +这说明底层 resource tool 方向已经存在,缺口不在“完全没有工具”,而在 Page AI 缺少一个明确的 `mnote-mindmap` 内置 skill 来指导 agent 何时读取、何时写入、如何生成新的思维导图资源,以及如何把 PDF / Office / Markdown 等材料整理成新的 `.mindmap.json`。 + +用户明确的长期目标是: + +> AI 能总结 PDF,并整理新建出思维导图。 + +因此本设计不能只覆盖“编辑已有导图节点”,还必须覆盖“从外部材料生成新导图资源”的闭环。 + +## 2. 原型观察 + +### 2.1 KMind plugin + +`reference-code/kmind-plugin` 体现的关键点: + +- 以 `simple-mind-map` 风格树结构为核心。 +- 支持多根、MOC、文档树导图、节点超链接、TODO、主题、布局、导入导出。 +- 节点可以携带思源块/文档引用,说明 mindmap 节点不只是纯文本,还可能是资源引用容器。 +- 导入导出会处理 markdown、Freemind、XMind 等格式,但最终仍要落回 mindmap runtime 可消费的数据结构。 + +对 MNote 的启发: + +- AI 不能把思维导图降级为普通 Markdown 大纲后直接覆盖文件。 +- AI 写入必须尽量保留未知扩展字段,如节点样式、引用、视图状态、主题配置。 +- 从 PDF 生成导图时,应先生成结构化 outline,再转换成 mindmap tree,而不是让模型手写完整 runtime JSON。 + +### 2.2 lx-doc mind-map + +`reference-code/lx-doc/mind-map` 体现的关键点: + +- 思维导图项目独立部署,工作台只负责文件/资源管理。 +- 文件内容是完整对象,典型形态为: + - `root` + - `theme` + - `layout` + - `config` + - `view` +- runtime 使用 `setFullData` 恢复完整文件,用 `getData(true)` 保存全量配置。 +- `root` 是 `simple-mind-map` 树:`{ data: { text, uid, ... }, children: [...] }`。 + +对 MNote 的启发: + +- mindmap 是 Resource Tree 对象,不是 Markdown 正文的一部分。 +- Markdown 页面只保留占位、链接或嵌入引用。 +- AI 读写 mindmap 时应围绕 resource 文件、object identity 和 resource capability 工作。 + +## 3. 当前 MNote 数据合同 + +当前默认 `.mindmap.json` 不是裸树,而是 envelope: + +```json +{ + "data": { + "children": [], + "data": { + "expand": true, + "isActive": false, + "text": "KMIND", + "uid": "root" + } + }, + "view": { + "state": { + "scale": 1, + "sx": 0, + "sy": 0, + "x": -44.99991989135742, + "y": -15.500006675720217 + }, + "transform": { + "a": 1, + "b": 0, + "c": 0, + "d": 1, + "e": -44.99991989135742, + "f": -15.500006675720217, + "originX": 0, + "originY": 0, + "rotate": 0, + "scaleX": 1, + "scaleY": 1, + "shear": 0, + "translateX": -44.99991989135742, + "translateY": -15.500006675720217 + } + } +} +``` + +设计约束: + +- 最终写入文件必须保持 envelope。 +- 根节点位于 `data.data`。 +- 子节点位于 `data.children`。 +- 新建导图默认 `data.data.uid` 为 `root`。 +- 新建导图默认 `data.data.text` 可由用户材料标题覆盖;没有标题时使用 `KMIND`。 +- `view` 默认使用上述稳定模板;AI 不应自行发明缩放和位移。 +- 修改已有导图时必须保留未知字段,包括 `view`、节点样式、节点引用、主题和未来扩展字段。 + +## 4. 目标 + +- 新增 Page AI MNote 内置 skill:`mnote-mindmap`。 +- 让 Hermes / Reasonix 在 Page AI 中知道如何读写当前页面或当前资源 tab 的 mindmap。 +- 让 AI 能从 PDF、Office、Markdown、当前页内容或用户粘贴文本生成层级 outline,再创建新的 mindmap resource。 +- 让新建导图进入 Resource Tree / File Tree / Page Tree 的正确边界,而不是写进 Markdown 正文。 +- 保留 local-first 主线:本地 `.mindmap.json` 是本地导图资源真相;Rust control-plane 负责授权、审计和 resource identity。 + +## 5. 非目标 + +- 不在本批实现 PDF OCR / MinerU / Office 文本抽取本身。 +- 不把 mindmap 正文存进 Markdown 页面。 +- 不让 Page AI 前端私有拼第二套 mindmap 真相。 +- 不让 agent 直接大段重写完整 JSON 作为默认写入方式。 +- 不引入轮询刷新链路;写入后的 UI 同步应走已有 watcher、resource session 或命令结果驱动刷新。 + +## 6. Skill 设计 + +新增内置 skill: + +```text +id: mnote-mindmap +title: MNote mindmap editing +description: Read, update, summarize, or create MNote mindmap resources, including generating a new mindmap from PDF or document outlines. +agentIds: hermes, reasonix +readOnly: false +requiresContextRefs: current_page, file, folder, resource +toolNames: + - mnote.context.snapshot + - mnote.context.resolve_target + - mnote.mindmap.fetch + - mnote.mindmap.apply_ops + - mnote.mindmap.create_from_outline +``` + +Skill 正文规则: + +- 只有用户明确要求“思维导图 / mindmap / KMind / 脑图 / 从材料生成导图”时启用。 +- 先解析目标资源,再读取内容。 +- 若当前 Page AI target 是 mindmap resource tab,优先使用该资源。 +- 若当前页面包含 mindmap embed,占位中的 `mindmapId` / `sourcePath` 是候选资源。 +- 若用户要求“从 PDF 生成导图”,先读取或请求 PDF 摘要/outline,再调用 mindmap 创建工具。 +- 写前必须确认 `permissionLevel=read_write`,并确认 `allowedResourceIds` 覆盖目标资源。 +- 写后必须回读,并报告新建或变更的 `.mindmap.json`。 +- 生成 outline 时必须使用“中心主题 + 一级分类 + 短语化节点”的导图结构;禁止把 PDF/文档摘要以长段落塞进节点文本。 +- 节点文本优先使用关键词或短语,一个节点只表达一个概念;层级必须用 `children` 表达,引用/页码/来源信息优先放入 `sourceRefs` 或 metadata。 + +## 7. Tool 面设计 + +### 7.1 `mnote.mindmap.fetch` + +保留现有命名,增强返回结构。 + +输入: + +- `workspaceId` +- `documentId` +- `mindmapId` +- `rootUri` +- `resourcePath` +- `scope`: `tree | subtree | markdown_summary | full_envelope` +- `nodeId` +- `aiAccessScope` + +输出: + +- `objectIdentity` +- `resourceKind=mindmap` +- `mindmapId` +- `resourcePath` +- `revision` +- `envelope`,仅 `full_envelope` 返回 +- `root` +- `nodes` +- `edges` +- `markdownSummary` +- `source=local_folder` + +约束: + +- `tree` / `markdown_summary` 默认不返回完整 envelope,避免 prompt 被视图和样式噪音污染。 +- `full_envelope` 只在需要保留或精确 patch 文件时使用。 + +### 7.2 `mnote.mindmap.apply_ops` + +当前该工具已存在,但 local resource 非 dry-run 仍提示 agent 用原生 patch。后续应收口为真正结构化写入工具。 + +输入: + +- `workspaceId` +- `documentId` +- `mindmapId` +- `rootUri` +- `resourcePath` +- `expectedRevision` +- `ops` +- `aiAccessScope` + +建议 ops: + +- `updateText` +- `insertChild` +- `insertSiblingAfter` +- `deleteNode` +- `setHyperlink` +- `setRefs` +- `appendNote` +- `patchView` +- `setLayout` +- `setTheme` + +约束: + +- 默认只改 `data` 树和指定 metadata。 +- 未知字段必须透传。 +- `expectedRevision` 不匹配时拒写或返回 conflict。 +- 写入后返回 `revision`、`changedFiles`、`markdownSummary`。 + +### 7.3 `mnote.mindmap.create_from_outline` + +新增工具,用于“从材料生成新导图”。 + +输入: + +- `workspaceId` +- `documentId` +- `rootUri` +- `targetPageId` 或 `targetDocumentId` +- `resourcePath`,可选;缺省由服务端生成唯一 `.mindmap.json` +- `title` +- `outline` +- `sourceRefs` +- `aiAccessScope` +- `embedIntoPage`,默认 `true` + +`outline` 建议格式: + +```json +[ + { + "text": "一级主题", + "children": [ + { + "text": "二级主题", + "children": [] + } + ] + } +] +``` + +输出: + +- `objectIdentity` +- `mindmapId` +- `resourcePath` +- `envelope` +- `revision` +- `changedFiles` +- `embedResult` +- `markdownSummary` + +写入规则: + +- 服务端负责把 outline 转换为默认 envelope。 +- 根节点文本使用 `title`,没有标题时使用 `KMIND`。 +- 子节点 uid 由服务端稳定生成,避免模型生成重复 uid。 +- 默认 view 使用当前稳定模板。 +- 若 `embedIntoPage=true`,通过 Resource Tree / Markdown embed 合同把资源绑定到当前页面。 + +## 8. PDF 到导图闭环 + +长期目标链路: + +```text +PDF resource + -> 文本/OCR/章节提取 + -> AI 生成层级 outline + -> mnote.mindmap.create_from_outline + -> 写入 .mindmap.json + -> Resource Tree 绑定当前页面 + -> 打开 mindmap resource tab +``` + +本设计只冻结后半段合同: + +- PDF 摘要工具输出应是 `title + outline + sourceRefs`。 +- `sourceRefs` 可以记录 PDF 文件、页码、章节、引用片段。 +- mindmap 节点可把 `sourceRefs` 保存到节点 `refs` 或 `note`,但第一版只要求保留到工具审计和 root metadata。 + +后续可接入: + +- `mnote.office.fetch_summary` +- `mnote.pdf.extract_outline` +- MinerU markdown 识别结果 +- 本地 Markdown / 当前页正文 + +## 9. 权限与审计 + +读写 mindmap resource 必须同时满足: + +- 当前 actor 拥有 root 的 read 或 write grant。 +- `aiAccessScope.permissionLevel` 覆盖目标操作。 +- `allowedResourceIds` 包含 `mindmapId`、`resourcePath` 或 `objectIdentity`。 +- 写入只能落在授权 root 内。 + +审计必须记录: + +- tool name +- sessionId / runId / traceId +- actorId +- documentId +- mindmapId +- resourcePath +- sourceRefs +- changedFiles +- before / after revision + +## 10. Page AI 上下文 + +Page AI run payload 应能携带当前 mindmap target: + +```json +{ + "contextRefs": [ + { + "kind": "resource", + "resourceKind": "mindmap", + "objectIdentity": "resource:mindmap:{documentId}:{mindmapId}", + "documentId": "{documentId}", + "mindmapId": "{mindmapId}", + "resourcePath": "{relativePath}", + "rootUri": "{rootUri}" + } + ] +} +``` + +来源优先级: + +1. 当前打开的 mindmap resource tab。 +2. 当前页面选中的 mindmap block/embed。 +3. 当前页面中唯一 mindmap embed。 +4. 用户明确给出的资源文件路径。 +5. 创建新导图时,当前页面作为目标页面。 + +## 11. 验收清单 + +- [x] MNote 内置技能面板出现 `MNote mindmap editing`。 +- [x] `mnote-mindmap` 开关能进入 `skillPreferences.mnote`。 +- [x] `mnote.skill.read` 能读取 `mnote-mindmap` 正文。 +- [x] `mnote-mindmap` 正文说明导图 outline 格式:中心主题、一级分类、短语化节点、避免长段落。 +- [x] `mnote.mindmap.fetch` 可读取默认 envelope 并返回 root/nodes/summary。 +- [x] `mnote.mindmap.create_from_outline` 可生成符合默认 envelope 的 `.mindmap.json`。 +- [x] `mnote.mindmap.create_from_outline` 在显式 `embedIntoPage=true` 时可把新导图链接写入当前 local-md 页面。 +- [x] `task503-mindmap-skill-capability-smoke` 可直接读取 skill、创建能力导图、fetch 回读、绑定当前页面,并用真实登录浏览器截图验证。 +- [x] 新建导图能绑定当前页面并在 File Tree / Resource Tab 可见。 +- [x] `mnote.mindmap.apply_ops` 写入后保留 `view` 和未知字段。 +- [x] shared/read-only scope 下写工具拒绝。 +- [x] revision 不匹配时拒写或返回 conflict。 +- [x] PDF outline fixture 可生成 mindmap resource。 +- [x] 真实 mindmap resource tab 能注入 Page AI `active_editor` contextRef / `targetPackage`。 + +当前第一批已完成 Page AI skill 发现、skill 正文读取、默认 envelope fetch、`create_from_outline` 写入、显式 `embedIntoPage=true` 页面链接绑定、`task502` Page AI skill payload smoke,以及 `task503` 直接 tool 创建导图 + 页面绑定 + 浏览器截图 smoke。第二批已完成 `apply_ops` 最小结构化写入、`view` / 未知字段保留、revision conflict 和 shared/read-only 拒写单测;2026-06-01 已修复 `task455` 暴露的 local-folder embedded mindmap 刷新后 id 退化,根因是 `blockDocument.blocks[].attrs` 未保留 `mindmapId/sourcePath/rootNodeId` 且浏览器 conversion 未读取 blockDocument attrs;新增 `task525` 覆盖真实 mindmap resource tab 的 Page AI `active_editor` contextRef / `targetPackage`。 + +2026-06-01 复核:`mnote.mindmap.apply_ops` 已从 native patch 提示收口到最小结构化写入,当前支持 `updateText` / `updateNode`、`insertChild` / `addChild`、`deleteNode`,并由 `hermes_tools_mindmap_apply_ops_writes_and_preserves_envelope_fields`、`hermes_tools_mindmap_apply_ops_rejects_stale_revision`、`hermes_tools_mindmap_apply_ops_shared_read_is_forbidden` 覆盖。resource tab / File Tree 可见性和 Page AI mindmap contextRefs 已由 `task455`、`task524`、`task525` 验收。后续若继续扩展 `moveNode`、`insertSiblingAfter`、`setHyperlink`、`setRefs`、`appendNote`、`patchView`,应另拆 P2 follow-up,不阻塞本轮最小闭环。 + +## 12. 第一批执行建议 + +第一批只做可控闭环: + +1. 新增 `skills/mnote-mindmap/SKILL.md`。 +2. 在 `hermes_tools::skill` 注册 `mnote-mindmap`。 +3. 补 Page AI skill 面板 smoke。 +4. 补 `mnote.skill.read` 单测。 +5. 增强 `mnote.mindmap.fetch` 输出,识别当前默认 envelope。 +6. 新增 `mnote.mindmap.create_from_outline` dry-run 与真实写入。 +7. 用 fixture 模拟 PDF 摘要,不接真实 PDF OCR。 + +第二批再做: + +1. 补浏览器 smoke,验证新建或修改后的 mindmap 能在 File Tree / Resource Tab 可见。 +2. 接入真实 PDF / Office / MinerU 结果。 +3. 在资源 tab 中把当前 mindmap 自动注入 Page AI contextRefs。 +4. 按真实编辑需求继续扩展 `apply_ops`,例如 `moveNode`、`insertSiblingAfter`、`setHyperlink`、`setRefs`、`appendNote`、`patchView`。 diff --git a/design/07-ai/done/7-44-chatonly-doubao-session-binding-v1.md b/design/07-ai/done/7-44-chatonly-doubao-session-binding-v1.md new file mode 100644 index 00000000..da8ac46a --- /dev/null +++ b/design/07-ai/done/7-44-chatonly-doubao-session-binding-v1.md @@ -0,0 +1,292 @@ +# 7-44 ChatOnly Doubao session binding v1 + +> 创建时间:2026-05-31 +> +> 状态:`done` +> +> Owner:Page AI ChatOnly / Hermes ACP runtime / SQLite control-plane / OpenClaw Doubao Web provider +> +> 上位依据: +> - `design/07-ai/done/7-25-acp-session-runtime-enhancement-plan-v1.md` +> - `design/07-ai/done/7-30-acp-session-load-resume-checklist-v1.md` +> - `design/07-ai/done/7-33-acp-session-info-plan-ui-checklist-v1.md` +> - `design/07-ai/done/7-39-page-ai-agent-selector-context-authorization-settings-v1.md` +> - `design/07-ai/done/7-41-page-ai-hermes-reasonix-user-profile-isolation-v2.md` + +## 1. 背景 + +MNote Page AI 的 `Chat-only / 豆包` 当前已经能通过 Hermes ACP -> OpenClaw `doubao-web` provider 调用豆包网页,但 MNote 会话与豆包网页会话还没有稳定统一: + +- MNote 的会话主记录在 SQLite `ai_runtime_runs` / `ai_runtime_events`,按 `user_id + workspace_id + session_id` 查询和软删除。 +- Hermes / ACP 层有 `acpSessionId`,MNote 会通过 `session.info.updated` 持久化它。 +- OpenClaw `doubao-web` provider 会从豆包 SSE 中捕获 `conversation_id`,并在进程内 `sessionMap` 中用 ACP `sessionId` 复用豆包会话。 +- 该 `sessionMap` 没有落 SQLite。MNote 重启、OpenClaw 重启、跨用户、删除会话时,都无法可靠知道某个 MNote ChatOnly session 对应哪条豆包网页 conversation。 + +2026-05-31 已验证 ChatOnly 豆包重复会话的直接根因是 Hermes `title_generation` 复用了豆包主模型;当前已在 `openclaw-doubao-chat` profile 禁用标题生成辅助调用。但这只解决“一条消息变两条豆包会话”的触发点,不解决会话生命周期统一。 + +## 2. 目标 + +本阶段目标是让 MNote 成为 ChatOnly 豆包会话的本地控制面: + +- MNote ChatOnly session 与豆包 `conversation_id` 建立持久绑定。 +- 后续同一个 MNote session 继续发送时,复用同一个豆包 conversation。 +- 删除 MNote session 时,对豆包远端 conversation 做最佳努力删除。 +- 多用户会话隔离以 SQLite `user_id` 为准,不能只依赖 OpenClaw 全局内存。 +- 删除失败不能阻塞 MNote 本地删除,但必须可审计、可重试。 + +非目标: + +- 不把豆包网页作为 MNote 会话真源。 +- 不同步豆包网页中用户手工创建的所有历史会话。 +- 不承诺豆包接口稳定可用;远端删除属于 provider-specific best effort。 +- 不在 ChatOnly 下申请 MNote 文件写权限。 + +## 3. 豆包侧接口取证 + +### 3.1 发送 / 继续会话 + +当前 OpenClaw `DoubaoWebClientBrowser` 发送消息使用: + +```text +POST /samantha/chat/completion +``` + +请求体关键字段: + +```json +{ + "completion_option": { + "need_create_conversation": false, + "is_delete": false + }, + "conversation_id": "38428454119180290" +} +``` + +当 `conversation_id` 为空或 `"0"` 时,豆包创建新 conversation。响应 SSE 中会出现 `conversation_id`,OpenClaw 已能解析并打印: + +```text +[Doubao Web Browser] Captured conversation_id: ... +``` + +### 3.2 删除会话 + +从豆包当前 Web UI 已加载脚本和 CDP 请求监听确认,删除会话优先走 IM cmd 链路: + +```text +POST /im/conversation/batch_del_user_conv +``` + +请求头关键字段: + +```text +content-type: application/json; encoding=utf-8 +accept: application/json, text/plain, */* +agw-js-conv: str +``` + +请求体结构: + +```json +{ + "cmd": 4171, + "uplink_body": { + "batch_delete_user_conversation_uplink_body": { + "conversation_id": ["38428454119180290"], + "delete_all": false, + "conversation_type": 3 + } + }, + "sequence_id": "uuid", + "channel": 2, + "version": "1" +} +``` + +其中 `conversation_type: 3` 对应 `ONE_TO_BOT_CHAT`。 + +脚本中还存在旧 wrapper: + +```text +POST /samantha/im/conversation/batch_delete +``` + +但当前删除弹窗路径使用 `/im/conversation/batch_del_user_conv`,实现优先采用该路径。 + +## 4. 数据合同 + +新增 SQLite 控制面表: + +```text +ai_external_conversation_bindings +- id +- user_id # 当前 MNote 用户,必填 +- workspace_id # 可空,但列表/删除必须按上下文过滤 +- mnote_session_id # MNote ChatOnly session_id +- acp_session_id # Hermes/OpenClaw ACP sessionId,可空,收到后补齐 +- agent_id # chat_only +- profile # openclaw-doubao-chat 等 +- provider # doubao-web +- remote_conversation_id # 豆包 conversation_id +- remote_url # https://www.doubao.com/chat/{remote_conversation_id} +- status # active | local_deleted | remote_deleted | remote_delete_failed +- metadata_json # 捕获来源、失败原因、最后响应摘要 +- created_at +- updated_at +- deleted_at +``` + +约束: + +- `user_id + provider + remote_conversation_id` 唯一,避免同一豆包会话绑定给多个 MNote 用户。 +- `user_id + mnote_session_id + provider` 唯一,避免一个 MNote session 绑定多条豆包远端会话。 +- 查询、恢复、删除都必须带当前 `user_id`;不能只按 `mnote_session_id` 查。 + +## 5. 数据流 + +### 5.1 创建 MNote ChatOnly session + +1. 前端 `pageAiEnsureHermesSession(forceCreate=true)` 调 `/api/hermes/client/sessions`。 +2. mnote-web 写入 SQLite session index run,`status=session.created`。 +3. 不立即创建豆包远端 conversation。 +4. 绑定表暂不写,或写入一行 `remote_conversation_id=NULL` 的 pending 记录。 + +### 5.2 第一次发送 + +1. mnote-web 创建 run,payload 携带 `sessionId`、`agentId=chat_only`、`profile=openclaw-doubao-chat`。 +2. mnote-web 查询绑定表;若无 `remote_conversation_id`,不传远端 ID。 +3. OpenClaw 发送给豆包,豆包创建新 conversation。 +4. OpenClaw 从 SSE 捕获 `conversation_id`。 +5. OpenClaw 需要把 `conversation_id` 作为结构化事件回传给 MNote。建议事件: + +```json +{ + "event": "provider.conversation.bound", + "data": { + "provider": "doubao-web", + "remoteConversationId": "38428454119180290", + "remoteUrl": "https://www.doubao.com/chat/38428454119180290" + } +} +``` + +6. mnote-web 在 `persist_acp_runtime_event` 中识别该事件,upsert `ai_external_conversation_bindings`。 + +### 5.3 继续发送 + +1. mnote-web 根据 `user_id + mnote_session_id + provider=doubao-web` 查询绑定。 +2. 若找到 active `remote_conversation_id`,在传给 ACP/OpenClaw 的 payload 中加入: + +```json +{ + "providerConversation": { + "provider": "doubao-web", + "remoteConversationId": "38428454119180290" + } +} +``` + +3. OpenClaw Doubao provider 使用该 ID 调 `/samantha/chat/completion`,设置 `need_create_conversation=false`。 +4. 若豆包返回会话不存在或被删除,OpenClaw 发 `provider.conversation.missing`;MNote 标记绑定异常,由用户决定是否新建远端会话。 + +### 5.4 删除 MNote session + +1. 用户在 MNote 删除 ChatOnly 会话。 +2. mnote-web 先对 `ai_runtime_runs` 做本地软删除。 +3. mnote-web 将绑定表标记为 `local_deleted`。 +4. 若存在 `remote_conversation_id`,调用 OpenClaw / provider adapter 执行远端删除: + +```text +POST /im/conversation/batch_del_user_conv +``` + +5. 成功后标记 `remote_deleted`。 +6. 失败时保持本地删除已完成,绑定标记 `remote_delete_failed`,`metadata_json` 记录错误码、响应摘要和时间。 +7. UI 提示:`MNote 会话已删除,豆包远端删除失败,可稍后重试`。 + +## 6. API 边界 + +### 6.1 mnote-web 内部 API + +建议新增 provider conversation helper,不把豆包细节散落在 `hermes_client.rs`: + +```text +rust/crates/mnote-web/src/provider_conversations.rs +``` + +职责: + +- 解析 run payload 中的 ChatOnly provider。 +- 读写 `ai_external_conversation_bindings`。 +- 将 binding 注入 ACP run payload。 +- 处理 `provider.conversation.bound/missing/deleted/delete_failed` 事件。 + +### 6.2 OpenClaw Doubao provider + +需要在 OpenClaw `doubao-web` provider 增加三个能力: + +- 从 ACP/context payload 读取 `providerConversation.remoteConversationId`。 +- 捕获新 `conversation_id` 后发结构化事件,不能只写 console log。 +- 暴露 `deleteConversation(remoteConversationId)`,内部走 `/im/conversation/batch_del_user_conv`。 + +第一阶段如果 ACP 不支持 provider 自定义 RPC,可先由 mnote-web 调一个 OpenClaw 本地 HTTP helper;但长期应收口到 provider adapter。 + +## 7. 错误处理 + +| 场景 | 行为 | +| --- | --- | +| 豆包创建成功但未捕获 `conversation_id` | run 仍完成;绑定缺失;下一轮可能新建远端会话;UI 标记未绑定 | +| 绑定表有 ID,但豆包返回不存在 | 标记 `remote_missing`,提示用户重新绑定或新建 | +| 删除 MNote 本地成功,豆包远端失败 | 不回滚本地删除;标记 `remote_delete_failed` | +| 多用户尝试绑定同一远端 ID | 拒绝后写 audit,避免跨用户串会话 | +| OpenClaw 重启 | SQLite 绑定仍在;下一轮从 MNote 注入远端 ID | +| 豆包接口变更 | 本地会话不受影响;远端能力降级为不可用 | + +## 8. 验收 + +### 8.1 单元 / 集成 + +- `control-plane`:binding upsert / lookup / local delete / remote delete status transition。 +- `mnote-web`:ChatOnly run payload 能注入已有 `remoteConversationId`。 +- `mnote-web`:`provider.conversation.bound` 事件能写入 SQLite。 +- `mnote-web`:删除 session 时先软删除本地,再 best-effort 调 provider delete。 + +### 8.2 真实浏览器 smoke + +1. 使用测试账号登录 `http://localhost:3000`。 +2. 创建 ChatOnly / 豆包新会话,发送 marker A。 +3. 复查: + - MNote SQLite 有一个 `mnote_session_id -> remote_conversation_id` 绑定。 + - 豆包日志 `Captured conversation_id` 一次。 +4. 在同一 MNote 会话发送 marker B。 +5. 复查: + - 豆包日志第二次 `Conversation ID` 等于第一次捕获值。 + - 豆包网页同一 conversation 中出现 A 与 B。 +6. 删除 MNote 会话。 +7. 复查: + - MNote 会话列表不再显示该 session。 + - SQLite binding status 为 `remote_deleted` 或 `remote_delete_failed`。 + - 若远端删除成功,豆包网页侧该 conversation 从列表移除或打开后显示已删除。 + +2026-06-01 验证记录: + +- `node scripts/task512-chatonly-doubao-sync-smoke.js` 通过:豆包远端 conversation 绑定、同会话回复、MNote session 删除、provider delete 和 SQLite binding `remote_deleted` 均通过。 +- `node scripts/task513-chatonly-provider-sync-smoke.js deepseek` 通过:DeepSeek `remoteConversationId` 绑定、provider delete 和 SQLite binding `remote_deleted` 均通过。 +- `node scripts/task513-chatonly-provider-sync-smoke.js gemini` 通过:Gemini conversation URL 绑定、provider delete 和 SQLite binding `remote_deleted` 均通过。 +- `cargo test --manifest-path rust/Cargo.toml -p control-plane external_conversation -- --test-threads=1` 通过:binding user scope 与状态迁移。 +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib provider_conversation -- --test-threads=1` 通过:provider conversation 注入与 bound event 持久化。 +- `cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib chatonly_doubao_session_delete_calls_provider_and_marks_remote_deleted -- --test-threads=1` 通过:删除 session 后 provider delete 与 `remote_deleted` 状态。 + +## 9. 实施清单 + +- [x] 在 `control-plane` 增加 `ai_external_conversation_bindings` schema、store trait 和 SQLite 实现。 +- [x] 在 `mnote-web` 增加 provider conversation helper,避免继续扩大 `hermes_client.rs`。 +- [x] 在 ACP run 创建时为 ChatOnly / 豆包注入已有 `remoteConversationId`。 +- [x] 在 ACP event 持久化时处理 `provider.conversation.bound`。 +- [x] 在 session delete 路径中加入远端删除 best-effort 状态机。 +- [x] 在 OpenClaw Doubao provider 中加入结构化 conversation bound 事件。 +- [x] 在 OpenClaw Doubao provider 中加入 `deleteConversation`,走 `/im/conversation/batch_del_user_conv`。 +- [x] 补真实浏览器 smoke:`node scripts/task512-chatonly-doubao-sync-smoke.js` 覆盖远端 conversation id 绑定、豆包同会话 marker、session 删除、provider delete 和 SQLite binding `remote_deleted`。 +- [x] 补跨 provider 真实浏览器 smoke:`node scripts/task513-chatonly-provider-sync-smoke.js deepseek` 与 `node scripts/task513-chatonly-provider-sync-smoke.js gemini`。 +- [x] 补 MNote Rust 单测:control-plane binding、mnote-web provider conversation 注入、bound event 持久化和 session delete provider 状态。 +- [x] OpenClaw provider 源码不在本仓库,provider 单测缺口已转入 `bugs/07-ai/process/7-49-chatonly-openclaw-provider-unit-test-gap-v1.md` 跟踪,不阻塞 MNote 侧设计归档。 diff --git a/design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md b/design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md index 90c1318b..f129fc23 100644 --- a/design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md +++ b/design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md @@ -6,7 +6,7 @@ > > 上位依据: > - `/mnt/Data1T/mnote/ARCHITECTURE.md` -> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` +> - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md` > - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md` > - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-6-page-aggregate-alignment-checklist-v1.md` > - `/mnt/Data1T/mnote/design/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md` @@ -276,10 +276,10 @@ Rust SQLite control-plane 是默认控制面,负责: ### Phase A0:target selection 与 UI -- [ ] Page AI composer 可见 target chip,显示 workspace、target title、resourceKind、scope、readonly / dirty 状态。 -- [ ] 点击 target chip 打开 target picker,可在当前焦点、打开 tabs、树选中项之间选择。 -- [ ] 默认 target 来自 last focused editor / resource tab,不来自第一个 tab、URL documentId 或树选中项。 -- [ ] mindmap / office / raw file target 显示真实 resourceKind,不伪装成 markdown page。 +- [x] Page AI composer 可见 target chip,显示 workspace、target title、resourceKind、scope、readonly / dirty 状态。 +- [x] 点击 target chip 打开 target picker,可在当前焦点、打开 tabs、树选中项之间选择。 +- [x] 默认 target 来自 last focused editor / resource tab,不来自第一个 tab、URL documentId 或树选中项。 +- [x] mindmap / office / raw file target 显示真实 resourceKind,不伪装成 markdown page。 - [ ] 多 tab / 跨 workspace / dirty write 时发送前需要显式确认。 - [ ] 发送后消息记录冻结 target 摘要,切换 tab 不影响已启动 run。 @@ -291,6 +291,8 @@ Rust SQLite control-plane 是默认控制面,负责: - [ ] allowed roots 外文件写入被 agent runtime 或 MNote 审计层拒绝。 - [ ] readonly 页面不会启动可写 run,或只启动只读问答 run。 +2026-06-01 进展:已补第一版 `mnote.agent_target_package.v1` 运行时输入。前端 Page AI run body 会从当前 `editorTarget` / `WorkspacePath` 生成 `targetPackage`;后端会 sanitize 该包,并把 local-first `aiAccessScope.allowedFiles` / `allowedFilePaths` 与 `agentRunEnvelope.allowedFiles` 从该包派生。`task502-page-ai-agent-selector-context-smoke.js` 已覆盖 page / mindmap / OnlyOffice target picker 与 payload 冻结;`task520-page-ai-raw-resource-target-smoke.js` 已覆盖真实 raw local resource tab,并断言 `objectIdentity` 不退化为 `[object Object]`。当前仍未完成跨 workspace 多选确认和真实 agent 写入回收 smoke,因此本文继续保持 `process`。 + ### Phase B:写入回收与前台同步 - [ ] agent 修改当前 `.md` 后,MNote 能回收 changed files / diff。 diff --git a/design/07-ai/process/7-38-page-ai-sidebar-runtime-owner-split-v1.md b/design/07-ai/process/7-38-page-ai-sidebar-runtime-owner-split-v1.md deleted file mode 100644 index 7a02a579..00000000 --- a/design/07-ai/process/7-38-page-ai-sidebar-runtime-owner-split-v1.md +++ /dev/null @@ -1,35 +0,0 @@ -# 7-38 Page AI sidebar runtime owner split v1 - -> 创建时间:2026-05-25 -> 状态:`process` -> 来源:`design/03-rust-web/done/3-22-sidebar-tree-runtime-second-stage-split-v1.md` Batch A。 -> 2026-05-26 更新:`design/10-review/done/16-mnote-web-runtime-module-maintainability-checklist-v1.md` 已把 Page AI 主体迁入 `browser/sidebar-page-ai-runtime.js`;本文件继续作为后续 AI owner 收口 checklist。 - -## 1. 背景 - -`rust/crates/mnote-web/browser/sidebar-tree-runtime.js` 曾包含大量 Page AI panel 代码:会话、消息、profile、skills、gateway health、ACP runtime、permission dialog、plan/status event、session search/resume 等。2026-05-26 的 runtime 可维护性批次已将主体迁入 `browser/sidebar-page-ai-runtime.js`,tree runtime 目前只保留 sidebar click/change/input 委托、少量 `pageUiState.pageAi*` 状态壳和触发器代理。 - -这些代码不是 tree/filetree runtime 的长期 owner。继续把 Page AI 面板放在 `03-rust-web` 的 sidebar runtime 拆分里,会让 tree shell、local folder、AI session 三条边界继续混在一起。 - -## 2. Owner 判断 - -- owner:`07-ai` -- 运行位置:当前仍可挂在 sidebar UI,但 runtime 模块应独立于 tree/filetree runtime。 -- 当前目标模块:`rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` -- 主壳职责:只加载 asset、提供当前 document/workspace/root/bootstrap。 - -## 3. 后续执行建议 - -- [x] 只读审计 Page AI panel 的状态入口:`pageUiState.pageAi*`、storage、session 列表、profile/skills、permission dialog。 -- [x] 建立独立 Page AI sidebar owner runtime:`sidebar-page-ai-runtime.js`。 -- [ ] 从 `sidebar-tree-runtime.js` 继续迁出 Page AI click/change/input/keydown 委托,tree runtime 只保留 `data-mnote-action="open-page-ai"` 入口。 -- [ ] 将 `pageUiState.pageAi*` 从通用 sidebar state 中下沉到 Page AI owner runtime,并保留兼容 getter/setter 或明确 bootstrap contract。 -- [ ] 若 `sidebar-page-ai-runtime.js` 继续超过 2,500 行,再按 `session/profile-skill/permission/conversation` 拆成子模块。 -- [ ] 浏览器验证使用页面 AI / ACP smoke,而不是 tree/filetree smoke 替代。 - -## 4. 非目标 - -- 不在 03-rust-web 的 sidebar runtime 二阶段里继续扩大 Page AI 实现。 -- 不在 10-review runtime 可维护性收口里继续重构 ACP/Hermes session 语义;这里只记录 owner 边界。 -- 不改变 Hermes / Reasonix ACP runtime 协议。 -- 不把 local-first agent 文件编辑链路改回粗粒度 `mnote.page.save`。 diff --git a/design/07-ai/process/7-43-onlyoffice-plugin-bridge-design-v1.md b/design/07-ai/process/7-43-onlyoffice-plugin-bridge-design-v1.md new file mode 100644 index 00000000..11bd77d1 --- /dev/null +++ b/design/07-ai/process/7-43-onlyoffice-plugin-bridge-design-v1.md @@ -0,0 +1,273 @@ +# 7-43 ONLYOFFICE Plugin Bridge Design v1 + +> 状态:process +> +> Owner:07-ai / ONLYOFFICE live bridge / Page AI target runtime +> +> 创建时间:2026-06-01 +> +> 当前口径:工具层 session/scope 安全阻断、真实 iframe 非 dry-run 写入落点验证、Page AI target picker 到真实 Office iframe session 的端到端绑定均已落地;本文继续留在 `process`,用于承接后续 recipe 扩展、Office 主文档加载噪音治理和 P2 plugin bridge 产品化收口,不再作为 P0 安全阻断项。 + +## 背景 + +当前本机 ONLYOFFICE DocumentServer 由 Docker 暴露在宿主机 `8082`: + +- `mnote-onlyoffice-documentserver`: `onlyoffice/documentserver:9.4.0` +- 端口映射:`0.0.0.0:8082 -> container:80` +- MNote 公开入口:`http://127.0.0.1:3000` +- MNote 通过 `/onlyoffice-server` 反代 DocumentServer 静态资源和 editor iframe + +实测当前社区镜像不暴露 `docEditor.createConnector()`,因此不能依赖 ONLYOFFICE Automation API。MNote 的 live Office agent 能力应建立在社区版可用的 Plugin API 上:插件 iframe 使用 `window.Asc.plugin.executeMethod()` 和 `window.Asc.plugin.callCommand()` 调用 Office JavaScript API。 + +## 目标 + +建立一个完整、可扩展、可验证的 MNote ONLYOFFICE bridge 插件,让 Hermes、Reasonix 和页面 AI 能通过白名单 recipe 操作当前打开的 ONLYOFFICE 文档。 + +目标能力: + +- 支持当前浏览器内打开的 Word、Excel、PPT live editor session。 +- 通过 MNote bridge API 派发命令,插件执行后回传 JSON-safe result。 +- 不暴露任意 JavaScript 执行能力。 +- 写操作继续遵守 MNote tool 权限、`dryRun` 和 `idempotencyKey` 模型。 +- 所有新增 recipe 必须同时支持 browser direct smoke 和 tool API smoke。 + +非目标: + +- 不绕过 ONLYOFFICE Developer / Automation API 授权。 +- 不后台批量编辑任意 Office 文件;离线批处理仍优先使用 `officecli`。 +- 不把 ONLYOFFICE iframe DOM 当作编辑接口。 + +## 端口与 URL 边界 + +有两条 base URL,必须严格区分: + +1. 浏览器可达的 MNote origin + - 示例:`http://127.0.0.1:3000` + - 用途:插件 iframe 调用 MNote bridge API。 + - 来源:`location.origin` + - 禁止替换成 `host.docker.internal`,因为浏览器侧可能无法解析。 + +2. DocumentServer 容器可达的 MNote origin + - 示例:`http://host.docker.internal:3000` + - 用途:`document.url`、`callbackUrl`、local file proxy。 + - 来源:`ONLYOFFICE_DOCUMENT_URL_BASE` + - 依赖 compose 中 `extra_hosts: ["host.docker.internal:host-gateway"]`。 + +DocumentServer 静态资源访问: + +- Browser -> MNote -> DocumentServer +- URL 形态:`/onlyoffice-server/web-apps/apps/api/documents/api.js` +- 插件 SDK:`/onlyoffice-server/sdkjs-plugins/v1/plugins.js` + +## 加载流程 + +1. 用户打开 `/onlyoffice?...`。 +2. MNote 页面加载 `/onlyoffice-server/web-apps/apps/api/documents/api.js`。 +3. MNote 构造 `DocsAPI.DocEditor` config。 +4. `document.url` 和 `callbackUrl` 使用 `ONLYOFFICE_DOCUMENT_URL_BASE`,供 DocumentServer 容器访问。 +5. `editorConfig.plugins.pluginsData` 指向: + `/api/onlyoffice/bridge/plugin/config?sessionId=...&apiBase=http://127.0.0.1:3000` +6. ONLYOFFICE 加载 MNote bridge 插件 iframe。 +7. 插件 iframe 调用 `POST /api/onlyoffice/bridge/session` 注册 session。 +8. 插件通过 `GET /api/onlyoffice/bridge/commands/next` 长轮询取命令。 +9. 插件执行白名单 recipe。 +10. 插件调用 `POST /api/onlyoffice/bridge/results` 回传结果。 + +## Bridge API + +当前 API: + +- `GET /api/onlyoffice/bridge/plugin/config` +- `GET /api/onlyoffice/bridge/plugin/index` +- `GET /api/onlyoffice/bridge/plugin/index/config.json` +- `GET /api/onlyoffice/bridge/plugin/index/{state}` +- `POST /api/onlyoffice/bridge/session` +- `GET /api/onlyoffice/bridge/session/current` +- `POST /api/onlyoffice/bridge/session/close` +- `GET /api/onlyoffice/bridge/sessions` +- `GET /api/onlyoffice/bridge/capabilities` +- `POST /api/onlyoffice/bridge/commands` +- `GET /api/onlyoffice/bridge/commands/next` +- `POST /api/onlyoffice/bridge/results` +- `GET /api/onlyoffice/bridge/results` + +## Session 模型 + +每个打开的 ONLYOFFICE 页面生成一个 `sessionId`: + +```text +mnote-oo-{docKey}-{tabRandomSuffix} +``` + +服务端保存: + +- `sessionId` +- `editorType`: `word` / `cell` / `slide` +- `documentId` +- `assetId` +- `fileType` +- `docKey` +- `pageOrigin` +- `lastSeenMillis` +- `pendingCommands` +- `pendingResults` + +多 session 规则: + +- 插件每次 `/commands/next` 都刷新 `lastSeenMillis`。 +- `session.current` 返回最近活跃 session。 +- 严肃写操作应显式传 `onlyofficeSessionId`,避免多标签页误写。 +- 不同 editorType 的 recipe 必须在插件或 wrapper 层做类型保护。 + +2026-06-01 复核:`sessionId` 已加入浏览器 tab 级随机后缀,tool 侧读写均要求显式 `onlyofficeSessionId` / `bridgeSessionId`,并按 `aiAccessScope.allowedResourceIds` 校验 session resource。当前 HTTP / mock plugin 层已有 `task515`、`task516`、`task517` 证据;真实 ONLYOFFICE iframe / DocumentServer 层新增 `task518` 证据,覆盖插件 autostart 注册、同一文档双 tab 不共用 session、A/B 资源 scope mismatch 403、授权 dry-run 200,以及非 dry-run 写入 B 后导出验证 A 不含 marker、B 含 marker。随后 `task523` 补齐 Page AI target picker 到 Office session 的端到端 UI 绑定:真实文档页打开 Office resource tab 后,Page AI run payload 会把 iframe live `onlyofficeSessionId` 冻结进 `editorTarget`、`targetPackage` 和 `targetPackage.targets[0]`,且不再把 Office target 当作 Markdown buffer 查询 `/api/documents/buffer-state`。 + +2026-06-01 续补:已新增 `GET /api/onlyoffice/bridge/session/current`、`POST /api/onlyoffice/bridge/session/close`,session state 已写入 `docKey` / `pageOrigin`,`/onlyoffice` 页面会把当前文档 `docKey` 与页面 origin 透传给 bridge plugin config。已通过 `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1`、`node scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js`、`MNOTE_UI_BASE_URL=http://127.0.0.1:3302 node scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js`。本文继续保留在 `process`,只跟踪 Office 主文档加载噪音治理和第三批 recipe 逐项实测。 + +## Recipe 分层 + +### 已实现 recipe + +Word / 通用: + +- `selection.get` +- `document.insert_text` +- `document.replace_selection` +- `document.insert_html` +- `document.export` +- `document.search_replace` +- `document.insert_table` +- `document.get_comments` +- `document.add_comment` + +Excel: + +- `sheet.get_sheets` +- `sheet.add_sheet` +- `sheet.rename_sheet` +- `sheet.get_range` +- `sheet.get_range_values` +- `sheet.get_values` +- `sheet.set_value` +- `sheet.set_formula` +- `sheet.batch_set_values` +- `sheet.set_range_values` +- `sheet.format_range` +- `sheet.set_dimensions` +- `sheet.sort_range` +- `sheet.add_chart` + +PPT: + +- `presentation.get_slides` +- `presentation.get_slide_texts` +- `presentation.get_shapes` +- `presentation.add_text_slide` +- `presentation.replace_text` +- `presentation.set_shape_text` +- `presentation.delete_slide` +- `presentation.add_table` +- `presentation.clear_slide` +- `presentation.add_shape` + +### 第二批 recipe 说明 + +Word: + +- `document.search_replace` 走 `Api.GetDocument().SearchAndReplace(...)`,适合精确文本替换。纯搜索暂不单独暴露,全文读取先用 `document.export`。 +- `document.insert_table` 走 `Api.CreateTable(cols, rows)` 并 `doc.Push(table)`,可用二维 `data` 填充单元格;默认插入到文档末尾。 +- `document.get_comments` 走 `doc.GetAllComments()`,返回评论 id、作者、正文和引用文本。 +- `document.add_comment` 支持给当前选区或 `document_start` 添加评论;选区模式依赖 `doc.GetRangeBySelect()`,没有选区时应显式用 `target=document_start`。 + +Excel: + +- `sheet.get_sheets` 走 `Api.GetSheets()` / `worksheet.GetName()`,用于让 agent 明确当前 workbook sheet 结构。 +- `sheet.add_sheet` 走 `Api.AddSheet(name)`,用于创建新 sheet。 +- `sheet.rename_sheet` 走 `worksheet.SetName(name)`,用于重命名指定 `sheetIndex` 的 sheet。 +- `sheet.get_range_values` / `sheet.set_range_values` 接受 A1 地址(例如 `B2:C3`),适合按用户可见坐标读写二维区域。 +- `sheet.set_range_values` 只写入 A1 range 与 `values` 的交集;调用方需要保证二维数据尺寸符合预期。 +- `sheet.format_range` 走 `ApiRange` 格式 API,只暴露小范围字体粗斜体、下划线、填充色、字体色、字号、字体名、对齐、数字格式。 +- `sheet.set_dimensions` 走 `worksheet.SetColumnWidth` / `worksheet.SetRowHeight`,行列索引沿插件内部零基坐标。 +- `sheet.sort_range` 走 `range.SetSort(...)`,只暴露 A1 range、range 内 1 基 `keyColumn`、升降序和是否有表头。 +- `sheet.add_chart` 走 `worksheet.AddChart(...)`,只暴露 A1 数据区、图表类型、基础尺寸和插入位置。 + +PPT: + +- `presentation.get_slide_texts` 读取 slide 文本框纯文本。 +- `presentation.replace_text` 替换文本框纯文本;命中的文本框会按纯文本段落重建,不承诺保留复杂 run 格式。 +- `presentation.get_shapes` 返回每页 shape 的 `slideIndex` / `shapeIndex` / `shapeId` / text 预览,用于精确定位文本框。 +- `presentation.set_shape_text` 按 `slideIndex + shapeIndex` 重写指定 shape 文本;只面向文本 shape,复杂样式不承诺保留。 +- `presentation.delete_slide` 删除指定 `slideIndex` 的幻灯片;调用前应先读 slide 列表确认目标页。 +- `presentation.add_table` 走 `Api.CreateTable(...)` + `slide.AddObject(table)`,支持二维 `data` 和基础位置/尺寸。 +- `presentation.clear_slide` 走 `slide.RemoveAllObjects()`,用于清空指定页对象;这是高影响写操作,调用前必须确认目标页。 +- `presentation.add_shape` 走 `Api.CreateShape(...)` + `slide.AddObject(shape)`,只暴露基础形状、文字、填充色、位置和尺寸。 + +### 第三批 recipe + +- 已实装:Excel 排序、Excel 图表、PPT 表格。 +- 已实装:PPT 清空页对象、PPT 添加基础形状。 +- 待验证后再扩:Word 图片、修订、content controls、表格增删行列和样式。 +- 待验证后再扩:Excel 筛选、工作表删除/移动。 +- 待验证后再扩:PPT 图片、重排 slide、主题/布局、shape 样式与位置。 +- PDF/forms 字段读取与填写。 + +第三批必须逐项实测,不允许凭 API 名称直接暴露给 agent。 + +## 权限与安全 + +插件只负责执行当前 editor session 允许的动作;MNote 负责业务权限: + +- 当前用户是否能打开文档。 +- agent 是否拥有 `office.read` / `office.write`。 +- 写操作是否显式 `dryRun`。 +- 写操作是否带 `idempotencyKey`。 + +安全规则: + +- 不暴露 raw JavaScript。 +- action 必须是白名单。 +- payload 必须 schema 校验。 +- 批量单元格读写默认限制为 5000 cells。 +- result 必须 JSON-safe。 +- command timeout 默认 25 秒。 + +## 验证基线 + +每个 recipe 必须三层验证: + +1. Rust unit test + - manifest 暴露 + - route dispatch + - dryRun / 权限 / queue result + +2. Browser direct smoke + - 直接调用 `window.__MNOTE_ONLYOFFICE_BRIDGE__.run(...)` + - 不经过 Hermes/Reasonix + - 必须保存截图并检查 `pageErrors=[]` + +3. Tool API smoke + - 通过 `/api/hermes/tools/mnote/call` + - 验证 agent 实际可调用 + +完成标准: + +- `cargo test -p mnote-web onlyoffice_ -- --test-threads=1` +- `cargo test -p mnote-web hermes_tools_manifest_returns_first_batch_tools -- --test-threads=1` +- `cargo test -p mnote-web skill_registry_exposes_onlyoffice_live_skill_to_agents -- --test-threads=1` +- `node scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js` 覆盖 bridge plugin index 在 mock `Asc.plugin` 环境中的 session 注册、`docKey/pageOrigin` 元数据、command 消费、result 回传和 token 拒绝。 +- `node scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js` 覆盖真实 ONLYOFFICE iframe / DocumentServer 下插件 autostart、同文档双 tab session salt、bridge command 回收、resource scope 拒写、授权 dry-run,以及授权 B session 非 dry-run 写入后 A/B 导出内容不串台。 +- `node scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js` 覆盖真实文档页内 Office resource tab -> Page AI target picker -> run payload 的 live session 绑定,并断言 Office target 不触发 Markdown buffer-state 404。 +- 关键 browser direct smoke 通过 +- `git diff --check` 通过 +- `codegraph sync .` 已运行 + +2026-06-01 进展:已新增并通过 `task517-onlyoffice-bridge-plugin-direct-smoke.js`,证明 MNote bridge plugin index 与 bridge HTTP loop 可在真实 Chromium 中直接运行。随后新增并通过 `task518-onlyoffice-real-iframe-session-scope-smoke.js`,在真实 ONLYOFFICE iframe / DocumentServer 下打开同一 docx 两个 tab 和另一个 docx,验证三者 sessionId 均不同、同文档双 tab 共享 docKey 但使用不同随机 salt、`selection.get` 可各自回收、scope=A 显式调用 B session 返回 403、scope=B 授权 dry-run 返回 200。`task518` 已继续扩展为非 dry-run 写入 B 并导出 A/B 内容验证不串台,截图保存在 `tmp/task518-onlyoffice-real-iframe-session-scope-smoke/screenshots/office-a-after-write.png` 与 `office-b-after-write.png`。`task523-page-ai-onlyoffice-real-target-session-smoke.js` 已补齐 Page AI target picker 到 `onlyofficeSessionId` 的真实 UI 绑定,截图保存在 `tmp/task523-page-ai-onlyoffice-real-target-session-smoke/screenshots/`。 + +`task518` 已把 Office 主文档加载噪音分类为三类:bridge plugin translation 404、ONLYOFFICE 内置插件噪音、主文档加载失败。smoke 会断言 bridge session 注册和 command loop 正常,并把 `errorCode=-18` 或 editor 未 ready 归为主文档失败,避免把打不开文档误判为插件噪音。 + +补充验证矩阵: + +- `task515-onlyoffice-live-scope-http-smoke.js`:覆盖缺 explicit session、scope mismatch、缺 scope 和授权 dry-run。 +- `task516-onlyoffice-bridge-multisession-browser-smoke.js`:覆盖 A/B bridge session token、`docKey/pageOrigin` 元数据、command queue、result 回收互不串台,以及 `session/current` / `session/close`。 +- `task517-onlyoffice-bridge-plugin-direct-smoke.js`:覆盖 mock `Asc.plugin` 下 plugin index 注册、`docKey/pageOrigin` 透传和 command loop。 +- `task518-onlyoffice-real-iframe-session-scope-smoke.js`:覆盖真实 ONLYOFFICE iframe / DocumentServer 下插件 autostart、同文档双 tab session 隔离、A/B session command 回收、`resource:onlyoffice` scope mismatch 403、授权 scope dry-run、授权非 dry-run 写入 B 后导出验证 A 不含 marker / B 含 marker。 +- `task523-page-ai-onlyoffice-real-target-session-smoke.js`:覆盖真实文档页打开 Office resource tab、等待 iframe bridge ready、通过 Page AI target picker 选择 Office target,并验证 live `onlyofficeSessionId` 写入 run payload。 diff --git a/design/07-ai/process/7-35-reasonix-browser-test-contract-v1.md b/design/07-ai/reference/7-35-reasonix-browser-test-contract-v1.md similarity index 92% rename from design/07-ai/process/7-35-reasonix-browser-test-contract-v1.md rename to design/07-ai/reference/7-35-reasonix-browser-test-contract-v1.md index c9b77631..28e98296 100644 --- a/design/07-ai/process/7-35-reasonix-browser-test-contract-v1.md +++ b/design/07-ai/reference/7-35-reasonix-browser-test-contract-v1.md @@ -2,7 +2,7 @@ ## 状态 -- 状态:process +- 状态:reference - Owner:AI runtime / browser verification - 背景:Reasonix 已能作为独立辅助 agent 执行只读审计,但浏览器验收若只输出自然语言结论,主控仍需重测,无法稳定节省 token。 @@ -144,3 +144,7 @@ Codex/Hermes 只有在以下条件满足时,才能把 Reasonix 浏览器结果 - [ ] 使用 `bugs/0524.md` 中至少 1 个 bug 进行 Reasonix 浏览器试跑。 - [ ] 试跑后记录 accepted findings / false leads / retest required。 - [ ] 根据试跑结果更新本合同或 Reasonix skill。 + +## 2026-06-01 降级说明 + +本合同对应的全局 `reasonix-browser-tester` skill 已存在,并已包含 `result.json`、截图、console/network、isolated context、`modified_files` 等 artifact 要求。本文不再作为 MNote 产品 runtime 的 active process 项,降级为协作参考;后续若要继续试跑 `bugs/0524.md`,应在 Reasonix skill 维护流程或独立复盘文档中跟踪。 diff --git a/design/07-ai/process/7-36-reasonix-skill-maintainer-v1.md b/design/07-ai/reference/7-36-reasonix-skill-maintainer-v1.md similarity index 91% rename from design/07-ai/process/7-36-reasonix-skill-maintainer-v1.md rename to design/07-ai/reference/7-36-reasonix-skill-maintainer-v1.md index 4273579c..97d0ed70 100644 --- a/design/07-ai/process/7-36-reasonix-skill-maintainer-v1.md +++ b/design/07-ai/reference/7-36-reasonix-skill-maintainer-v1.md @@ -2,7 +2,7 @@ ## 状态 -- 状态:process +- 状态:reference - Owner:AI runtime / multi-agent collaboration - 背景:Hindsight 解决了 Reasonix 过程可追溯问题,但不能自动让 Reasonix 下一次更好。需要一个主控驱动的最小自进化机制,由 Codex/Hermes 根据真实 run 修改 Reasonix skill、任务模板和 handoff 合同。 @@ -144,3 +144,7 @@ Reasonix 协同复盘: - [ ] 用 `bugs/0524.md` 至少跑一轮真实 Reasonix 协同测试。 - [ ] 根据真实结果更新 skill 或明确“不需要修改”。 - [ ] 在 Hindsight / MemPalace 中保留协作复盘摘要。 + +## 2026-06-01 降级说明 + +全局 `reasonix-skill-maintainer` skill 已存在,并已要求读取 `result.json`、`process-handoff`、Hindsight recall,评估 `accepted_findings`、`false_leads`、`missing_evidence`、`retest_required` 和 `token_saving_estimate`。本文不再作为 MNote 产品 runtime 的 active process 项,降级为协作参考;后续真实 run 复盘直接走全局 skill。 diff --git a/design/07-ai/process/7-37-claudecode-reasonix-worker-evaluation-0524-bug2-v1.md b/design/07-ai/reference/7-37-claudecode-reasonix-worker-evaluation-0524-bug2-v1.md similarity index 100% rename from design/07-ai/process/7-37-claudecode-reasonix-worker-evaluation-0524-bug2-v1.md rename to design/07-ai/reference/7-37-claudecode-reasonix-worker-evaluation-0524-bug2-v1.md diff --git a/design/09-siyuan-reference/reference/9-siyuan-reference-boundary-and-adoption-v1.md b/design/09-siyuan-reference/reference/9-siyuan-reference-boundary-and-adoption-v1.md index 2f5292d4..7660142d 100644 --- a/design/09-siyuan-reference/reference/9-siyuan-reference-boundary-and-adoption-v1.md +++ b/design/09-siyuan-reference/reference/9-siyuan-reference-boundary-and-adoption-v1.md @@ -2,7 +2,7 @@ > 更新时间:2026-05-11 > -> 当前状态:`reference`。本文只保留思源参考边界与可借鉴能力,不覆盖 `01-05` 当前优先级,也不作为当前执行 checklist。 +> 当前状态:`reference`。本文只保留思源参考边界与可借鉴能力,不覆盖 `CURRENT_ARCHITECTURE.md` / `1-8` 当前口径,也不作为当前执行 checklist。 > > 上位依据: > - `/mnt/Data1T/mnote/ARCHITECTURE.md` diff --git a/design/10-review/done/08-kernel-architecture-next-priority-review-and-checklist.md b/design/10-review/done/08-kernel-architecture-next-priority-review-and-checklist.md index 0fb7040d..d43fe876 100644 --- a/design/10-review/done/08-kernel-architecture-next-priority-review-and-checklist.md +++ b/design/10-review/done/08-kernel-architecture-next-priority-review-and-checklist.md @@ -15,6 +15,8 @@ > - `/mnt/Data1T/mnote/design/old/07-ai/process/7-14-local-first-ai-markdown-editing-convergence-v1.md` > - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md` > - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md` +> +> 2026-06-01 口径补充:本文是 2026-05-18 的历史 review / checklist 快照。文中把 `mnote.doc.markdown_edit` 描述为简单正文编辑主路径的结论已被后续 local-first agent 文件编辑控制面覆盖;当前 local-first 普通 Markdown 编辑主路径以 `AGENTS.md`、`ARCHITECTURE.md` 与 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 为准。`mnote.doc.markdown_edit` 只保留为 remote/cloud/compat fallback 或结构校验辅助。 --- diff --git a/design/10-review/done/09-page-ai-fast-block-edit-runtime-review.md b/design/10-review/done/09-page-ai-fast-block-edit-runtime-review.md index d3cecab2..2798a67c 100644 --- a/design/10-review/done/09-page-ai-fast-block-edit-runtime-review.md +++ b/design/10-review/done/09-page-ai-fast-block-edit-runtime-review.md @@ -14,7 +14,9 @@ ## 0. 归档说明 -本文件是 2026-05-16 的页面 AI fast block edit 历史审查快照。其记录的 `local_rule` / `doc_apply_block_ops` 快路径已被后续 `mnote.doc.markdown_edit` 主路径替代,不再作为当前 runtime 口径。 +本文件是 2026-05-16 的页面 AI fast block edit 历史审查快照。其记录的 `local_rule` / `doc_apply_block_ops` 快路径曾被后续 `mnote.doc.markdown_edit` 阶段替代,但该阶段也已被 local-first agent 文件编辑控制面覆盖;本文不再作为当前 runtime 口径。 + +2026-06-01 当前有效口径:local-first 普通 Markdown 编辑默认走授权文件引用 + allowed roots/files + agent 原生 patch/diff + watcher/BufferStore/Page Aggregate 同步;`mnote.doc.markdown_edit` 只保留为 remote/cloud/compat fallback 或结构校验辅助。 当前有效口径见: @@ -23,7 +25,7 @@ - [7-18 AI markdown_edit 阶段状态合同漂移](../../../bugs/07-ai/done/7-18-ai-markdown-edit-phase-state-contract-drift-v1.md) - [7-20 page_ai_workflow 绕过 Hermes tool executor / audit / toggle](../../../bugs/07-ai/done/7-20-page-ai-workflow-bypasses-hermes-tool-executor-v1.md) -归档后的结论:`page-ai/block-edit-workflow` 仍是简单编辑 fast-path 入口,但当前实现应通过模型生成 markdown search/replace / full_content 后调用 `mnote.doc.markdown_edit`,并复用统一 mnote tool executor;`mnote.doc.apply_block_ops` / `mnote.block.*` 只作为结构性块操作辅助。 +归档时的历史结论:`page-ai/block-edit-workflow` 仍是简单编辑 fast-path 入口,但当时实现应通过模型生成 markdown search/replace / full_content 后调用 `mnote.doc.markdown_edit`,并复用统一 mnote tool executor;`mnote.doc.apply_block_ops` / `mnote.block.*` 只作为结构性块操作辅助。该结论不指导当前 local-first 新实现。 ## 1. 本轮结论 diff --git a/design/10-review/done/10-current-mnote-ai-runtime-review-v1.md b/design/10-review/done/10-current-mnote-ai-runtime-review-v1.md index 2998bc74..0a2f9864 100644 --- a/design/10-review/done/10-current-mnote-ai-runtime-review-v1.md +++ b/design/10-review/done/10-current-mnote-ai-runtime-review-v1.md @@ -5,6 +5,8 @@ > 执行状态:`done` > > 范围:当前主线中 Page Aggregate 单一真源、页面 AI 快速编辑、`mnote.doc.markdown_edit` 与 ACP / Hermes runtime 的源码级定向审查。 +> +> 2026-06-01 口径补充:本文是 2026-05-17 的历史 review 快照。文中“`mnote.doc.markdown_edit` 是简单正文编辑主路径”的结论已被后续 local-first agent 文件编辑控制面覆盖;当前 local-first 普通 Markdown 编辑主路径以 `AGENTS.md`、`ARCHITECTURE.md` 与 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 为准,即授权文件引用 + allowed roots/files + agent 原生 patch/diff + watcher/BufferStore/Page Aggregate 同步。`mnote.doc.markdown_edit` 只保留为 remote/cloud/compat fallback 或结构校验辅助。 ## 1. 本轮结论 diff --git a/design/10-review/done/11-current-full-architecture-review-v1.md b/design/10-review/done/11-current-full-architecture-review-v1.md index 91249643..e25ecd39 100644 --- a/design/10-review/done/11-current-full-architecture-review-v1.md +++ b/design/10-review/done/11-current-full-architecture-review-v1.md @@ -8,6 +8,8 @@ ## 1. 结论 +> 2026-05-31 口径补充:本文是 2026-05-17 的阶段性 review 快照。文中关于 `mnote.doc.markdown_edit` “已是简单正文编辑主路径”的表述已被后续 local-first agent 文件编辑控制面覆盖;当前 local-first 普通 Markdown 编辑主路径以 `AGENTS.md`、`CURRENT_ARCHITECTURE.md`、`ARCHITECTURE.md` 与 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 为准,即“授权文件引用 + allowed roots + agent 原生 patch/diff + watcher/BufferStore/Page Aggregate 同步”。`mnote.doc.markdown_edit` 只作为 cloud / remote agent / compat fallback 或复杂结构辅助边界。 + 当前 mnote 的主线架构已经成形。本轮 review 识别出的 P0 / 内核优先缺陷已完成代码修复、文档迁移和定向验证。 已修复的四个关键冲突: diff --git a/design/10-review/process/17-sidex-mnote-workbench-gap-execution-checklist-v1.md b/design/10-review/done/17-sidex-mnote-workbench-gap-execution-checklist-v1.md similarity index 100% rename from design/10-review/process/17-sidex-mnote-workbench-gap-execution-checklist-v1.md rename to design/10-review/done/17-sidex-mnote-workbench-gap-execution-checklist-v1.md diff --git a/design/10-review/done/18-current-design-and-bug-hunt-review-v1.md b/design/10-review/done/18-current-design-and-bug-hunt-review-v1.md new file mode 100644 index 00000000..27643b49 --- /dev/null +++ b/design/10-review/done/18-current-design-and-bug-hunt-review-v1.md @@ -0,0 +1,99 @@ +# 18 当前 design 口径与 bug 寻找 Review v1 + +> 创建时间:2026-05-31 +> +> 状态:`done` +> +> 范围:当前仓库、历史 design 口径、active `process/`、现有 `bugs/process`、近期未提交 OnlyOffice / Page AI / control-plane 改动。 + +## 1. 结论 + +当前上位口径仍一致: + +- `tree-first graph kernel` 是长期对象真相层。 +- local-first workspace 是默认产品形态,本地 `.md` 是页面正文真相。 +- Rust `mnote-web` 是 3000 主执行面。 +- `leptos-tiptap` 是默认 Markdown 前端编辑器。 +- Page Aggregate 是 Rust-first 投影收口方向,但仍需要 compat 瘦身。 +- Page AI 的 local-first 主路径是“授权文件引用 + allowed roots + Hermes/Reasonix 原生 patch/diff + watcher/BufferStore 同步”,不是继续扩 `mnote.doc.markdown_edit` 或 `/api/page-ai/block-edit-workflow`。 + +本轮发现的主要问题不是架构方向动摇,而是 `design/process` 状态与真实完成态、索引入口和 bug 记录之间出现漂移。 + +## 2. 已更新口径 + +- `design/README.md` 的当前入口已从不存在的 `design/01-05-current-priority-overview.md` 改为 `CURRENT_ARCHITECTURE.md` 与 `1-8`。 +- `1-8` 已补充 2026-05-31 口径复核:历史 `01-05` 已迁入 `old/`;已归档的 `3-3` / `3-18` 不再作为 active process 入口。 +- 本 review 作为本轮设计治理和 bug 寻找事实源,后续 worker 不应凭旧路径恢复过时优先级。 + +## 3. design 漂移 + +已确认的漂移: + +- `design/README.md` 与 `1-8` 曾引用已不存在的 `design/01-05-current-priority-overview.md`。 +- `design/03-rust-web/process/3-24-global-content-type-page-width-settings-v1.md` checklist 全部完成并有实施记录;2026-05-31 已迁入 `design/03-rust-web/done/3-24-global-content-type-page-width-settings-v1.md`。 +- `design/05-editor-mainline/done/5-31-page-settings-sqlite-preference-convergence-v1.md` 已迁入 `done/`;剩余风险另按后续 bug / follow-up 跟踪。 +- `design/05-editor-mainline/done/5-32-filetree-lazy-loading-sidex-alignment-v1.md` 已迁入 `done/`;tree live cache 后续入口改看 `3-23` 与相关 bug。 +- `design/05-editor-mainline/process/5-33-navigation-page-route-guard-checklist-v1.md` 顶部落地状态和正文 checklist 状态不一致,需要拆成已完成核心与剩余 follow-up。 +- `design/07-ai/done/7-41-page-ai-hermes-reasonix-user-profile-isolation-v2.md` 文件头状态为 `done`;2026-06-01 已迁入 `done/`。 +- `design/10-review/process/17-sidex-mnote-workbench-gap-execution-checklist-v1.md` 文件头状态为 `done`;2026-05-31 已迁入 `design/10-review/done/17-sidex-mnote-workbench-gap-execution-checklist-v1.md`。 +- `design/10-review/done/11-current-full-architecture-review-v1.md` 中“`mnote.doc.markdown_edit` 已是简单正文编辑主路径”的历史判断已被 `7-18` 和当前 AGENTS/ARCHITECTURE 覆盖;只能作为当时 review 快照,不应指导 local-first 新实现。 + +## 4. 新增 bug 条目 + +本轮已写入: + +- `bugs/10-review/done/10-18-design-process-status-and-stale-entry-v1.md` +- `bugs/07-ai/process/7-45-onlyoffice-live-bridge-session-isolation-v1.md` +- `bugs/07-ai/process/7-46-onlyoffice-live-tool-resource-scope-bypass-v1.md` +- `bugs/05-editor-mainline/process/5-40-onlyoffice-bridge-plugin-noise-regression-v1.md` +- `bugs/07-ai/process/7-47-local-first-ai-markdown-edit-mouth-drift-v1.md` +- `bugs/10-review/done/10-19-agents-md-broken-design-references-v1.md` +- `bugs/03-rust-web/done/3-26-sidebar-dev-hot-reload-setinterval-v1.md` +- `bugs/10-review/done/10-20-testing-reference-missing-new-smokes-v1.md` + +其中 `10-18`、`10-19`、`10-20` 已完成治理收口并迁入 `done/`;OnlyOffice / Page AI / Sidebar 等运行时缺陷仍按各自 bug 条目继续跟踪验收。 + +## 5. 下一步建议 + +P0: + +- [x] 修复 OnlyOffice live bridge session identity 与 resource scope 两个风险,优先补 Rust 单测,随后做 clean browser 多 tab smoke。 +- [x] 继续补 OnlyOffice 真实浏览器集成证据:真实 Office iframe 双 tab 不串台,以及 Page AI target=A 时不能读写 Office resource=B。 +- [x] `AGENTS.md` 断裂引用、Sidebar dev hot reload guard、`5-31` / `5-32` / `7-41` 归档已经收口。 +- [~] `5-33` breadcrumb / fetch guard follow-up 仍保留在 `process/`。 +- [x] 更新 `10-review/done/11` 或追加覆盖说明,防止 worker 继续把 `mnote.doc.markdown_edit` 当 local-first 主写入口。 + +P1: + +- [~] 继续推进 `7-18` Agent Target Resolver:target chip/picker、dirty guard、allowed roots/files 与写后 diff 回收。 +- [x] 推进 `WorkspacePath/ObjectIdentity + BufferStore + Page Aggregate compat 瘦身`,让 FileTree、Open Editors、resource tab 与 AI target resolver 消费同一身份与 buffer 状态。 +- [x] 保留 `3-23` 与 `7-38` 作为 runtime owner follow-up,不重新打开 raw string 大拆分议题。 + +P2: + +- [x] `7-42` mindmap 第二批:结构化 `apply_ops`、resource tab contextRefs、revision conflict。 +- [~] `3-25` MinerU OCR:后端真实 MinerU runtime 已补;任务状态事件、全局任务栏和 UI 入口仍待做。 +- [x] `7-43` OnlyOffice Plugin Bridge:P0/P1 安全主路径与 P2 recipe 矩阵已收口,后续新增 recipe 按矩阵逐项验证。 +- [x] `7-44` ChatOnly Doubao binding:MNote 侧 binding 与 Doubao provider 单测已完成。 +- [ ] `7-49` ChatOnly OpenClaw provider:DeepSeek / Gemini provider 级 delete helper 与单测仍在外部仓库阻塞,不能在 MNote 内归档。 + +本轮已将 `task503`、`task504`、`task512`、`task513`、`task514`、`task515`、`task516` 补入 `scripts/TESTING_REFERENCE.md`;对应 smoke 已实跑,运行结果由 `bugs/10-review/done/10-20-*` 记录。 + +## 6. 验证记录 + +原始 review 是设计/bug 审查和文档更新,未修业务代码,未跑浏览器 smoke。2026-06-01 后续收口已补跑 `task514` 与 `task517`,并登记 `task517` 到 smoke reference;OnlyOffice 真实 iframe / DocumentServer 验收仍未完成。 + +已执行只读检查: + +- `git status --short` +- `find design -maxdepth 3 -type f` +- `find bugs -maxdepth 3 -type f` +- `codegraph status .` +- 多组 `rg` / `sed` / `nl` 证据核对 + +已使用: + +- 2 个只读 subagent:架构口径审查、bug 候选审查。 +- 2 个 Reasonix 只读 worker:design governance audit、bug hunt candidate audit。 + +注意:当前工作区有大量未提交改动,且 CodeGraph 显示新增文件 pending。本轮只对本轮新增/修改的文档负责,不回滚、不移动用户已有改动。 diff --git a/design/README.md b/design/README.md index 251fc7a0..ae13bd75 100644 --- a/design/README.md +++ b/design/README.md @@ -23,12 +23,13 @@ ## 当前优先级入口 -- `01-05` 当前有效主线与优先级,请先看: - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` -- Runtime 模块级拆分完成态,请看: - `/mnt/Data1T/mnote/design/10-review/done/16-mnote-web-runtime-module-maintainability-checklist-v1.md` +- 当前有效架构口径,请先看: + `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md` - MVP 后阶段剩余 `process/` 的执行顺序,请看: `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md` +- Runtime 模块级拆分完成态,请看: + `/mnt/Data1T/mnote/design/10-review/done/16-mnote-web-runtime-module-maintainability-checklist-v1.md` +- 历史 `01-05-current-priority-overview` 已迁入 `design/old/`,只能作为归档快照,不再作为当前入口。 ## 主线顺序 @@ -82,7 +83,7 @@ - `90-reference/` - 跨域生态参考资料,不参与 `[done]/[process]/[draft]/[reference]/[recycle]` 状态判断 - - 引用时只能作为生态资料或背景材料,不能覆盖 `ARCHITECTURE.md`、`AGENTS.md`、`01-05` 当前优先级或对应主线 `process/` / `done/` 设计稿 + - 引用时只能作为生态资料或背景材料,不能覆盖 `CURRENT_ARCHITECTURE.md`、`ARCHITECTURE.md`、`AGENTS.md`、`1-8` 当前执行顺序或对应主线 `process/` / `done/` 设计稿 - `old/` - 已废弃或被替代的历史稿件,标题统一标记 `[recycle]` - 每个大类继续按 `process/` 与 `done/` 分层 diff --git a/docs/superpowers/plans/2026-05-29-local-folder-mineru-ocr-sidecar.md b/docs/superpowers/plans/2026-05-29-local-folder-mineru-ocr-sidecar.md new file mode 100644 index 00000000..6f71c8ef --- /dev/null +++ b/docs/superpowers/plans/2026-05-29-local-folder-mineru-ocr-sidecar.md @@ -0,0 +1,506 @@ +# Local Folder MinerU OCR Sidecar Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 为 mnote local-folder 图片和图片型 PDF 增加手动 MinerU OCR,并把 OCR Markdown 保存到 owner 页面同目录的 `{pageStem}.ocr/` 文件夹。 + +**Architecture:** 后端负责 MinerU API、权限校验、OCR active job store、OCR sidecar 写入和 `.mnote/ocr-index.json` 状态缓存;前端触发本地 OCR API,并通过资源局部状态和全局 OCR 任务栏展示阶段型进度。OCR 文本默认作为资源 sidecar 被搜索、resource tab 和后续 AI 消费,只有用户显式操作时才插入正文。 + +**Tech Stack:** Rust `mnote-web` routes/services、local-folder metadata、MinerU HTTP API、browser runtime JS、Node smoke、Rust cargo tests。 + +--- + +## File Structure + +- Create: `rust/crates/mnote-web/src/routes/local_ocr.rs` + - OCR API request/response types、路径规划、sidecar frontmatter、active job store、`.mnote/ocr-index.json` 读写、MinerU client facade。 +- Modify: `rust/crates/mnote-web/src/routes/mod.rs` + - 注册 `/api/local-folder/ocr/jobs`、`/jobs/:id`、`/status`、`/read`、`/insert`。 +- Modify: `rust/crates/mnote-web/src/routes/local_folder_source.rs` + - 暴露必要的 local-folder path helper,识别 `.ocr/*.ocr.md` 为 OCR resource 而不是普通页面。 +- Modify: `rust/crates/mnote-web/src/routes/local_search_index.rs` + - 读取 OCR index/sidecar,`includeOcr=true` 时搜索 OCR 文本并返回 owner page。 +- Modify: `rust/crates/mnote-web/src/routes/search.rs` + - local-folder 分支把 `filters.include_ocr` 传给 local search index。 +- Modify: `rust/crates/mnote-web/browser/sidebar-attachment-open-runtime.js` + - 附件菜单展示 OCR 识别、查看 OCR、插入 OCR 链接。 +- Modify: `rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js` + - File Tree 图片/PDF 资源右键菜单接入 OCR 操作。 +- Modify: `rust/crates/mnote-web/browser/document-resource-tab-runtime.js` + - image/PDF resource tab 展示 OCR 状态和 OCR Markdown。 +- Create: `rust/crates/mnote-web/browser/local-ocr-task-runtime.js` + - 全局 OCR 任务栏 / 任务抽屉,消费 OCR jobs API 和 `local_ocr.job.updated` 事件。 +- Modify: `rust/crates/mnote-web/src/ssr/pages/layout.rs` + - 挂载全局 OCR task runtime,并断言 runtime 依赖显式注入。 +- Test: `rust/crates/mnote-web/src/routes/local_ocr.rs` inline tests +- Test: `rust/crates/mnote-web/src/routes/local_search_index.rs` inline tests +- Create: `scripts/task506-local-folder-mineru-ocr-smoke.js` + - 浏览器 smoke,使用 mock MinerU 或 test-only route fixture。 + +## Task 1: OCR Sidecar Path And Metadata Contract + +**Files:** +- Create: `rust/crates/mnote-web/src/routes/local_ocr.rs` + +- [ ] **Step 1: Add failing unit tests for sidecar path planning** + +Add tests that assert: + +- owner `docs/Page.md` creates OCR dir `docs/Page.ocr/` +- source `docs/Page.assets/photo.png` creates `docs/Page.ocr/photo.png.ocr.md` +- duplicate source leaf names get a short hash suffix +- OCR frontmatter contains `owner_document`, `source_path`, `source_root_relative_path`, `provider`, `model_version`, `source_size`, `source_mtime_ms` + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_sidecar -- --test-threads=1 +``` + +Expected: FAIL because `local_ocr` does not exist yet. + +- [ ] **Step 2: Implement path planning and frontmatter builder** + +Implement focused helpers in `local_ocr.rs`: + +- `plan_ocr_sidecar_path(root, owner_document_path, source_root_relative_path, source_metadata)` +- `build_ocr_markdown(frontmatter, mineru_markdown)` +- `parse_ocr_frontmatter(markdown)` + +Keep these helpers pure and unit-testable. + +- [ ] **Step 3: Verify tests pass** + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_sidecar -- --test-threads=1 +``` + +Expected: PASS. + +## Task 2: OCR Index Cache + +**Files:** +- Modify: `rust/crates/mnote-web/src/routes/local_ocr.rs` + +- [ ] **Step 1: Add failing tests for `.mnote/ocr-index.json`** + +Cover: + +- write/read one entry +- `stale=true` when source size or mtime changed +- missing source returns `stale=true` with no panic +- failed entry stores only a short sanitized error + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_index -- --test-threads=1 +``` + +Expected: FAIL. + +- [ ] **Step 2: Implement index read/write and stale detection** + +Use atomic JSON write, following existing `write_json_atomic` style in `local_folder_source.rs`. + +Index path: + +```text +/.mnote/ocr-index.json +``` + +Entry key: + +```text +sourceRootRelativePath +``` + +- [ ] **Step 3: Verify tests pass** + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_index -- --test-threads=1 +``` + +Expected: PASS. + +## Task 3: MinerU Client And OCR Job API + +**Files:** +- Modify: `rust/crates/mnote-web/src/routes/local_ocr.rs` +- Modify: `rust/crates/mnote-web/src/routes/mod.rs` + +- [ ] **Step 1: Add route tests with a mock MinerU client** + +Cover: + +- missing token returns `mineru_token_missing` +- root escape source path is rejected +- successful mock OCR writes `{pageStem}.ocr/*.ocr.md` +- successful mock OCR exposes queued/running/done states through jobs list +- each mock OCR state change emits `local_ocr.job.updated` +- failed mock OCR writes failed index entry without token or upload URL + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_jobs -- --test-threads=1 +``` + +Expected: FAIL. + +- [ ] **Step 2: Implement API routes** + +Routes: + +- `POST /api/local-folder/ocr/jobs` +- `GET /api/local-folder/ocr/jobs` +- `GET /api/local-folder/ocr/jobs/{jobId}` +- `GET /api/local-folder/ocr/status` +- `GET /api/local-folder/ocr/read` + +Token lookup order: + +1. `MNOTE_MINERU_API_TOKEN` +2. `MINERU_API_TOKEN` +3. `~/.hermes/.env` `MINERU_API_TOKEN` for local development + +- [ ] **Step 3: Implement MinerU local file flow** + +Use MinerU API: + +- `POST /api/v4/file-urls/batch` +- `PUT` local bytes to pre-signed URL +- `GET /api/v4/extract-results/batch/{batch_id}` +- download zip +- extract first Markdown file + +Never log token or pre-signed URL. + +- [ ] **Step 4: Implement active job store and status stages** + +Add in-process active job state with these statuses: + +```text +queued +uploading +mineru_processing +downloading +writing_sidecar +done +failed +interrupted +stale +``` + +Each state change must update `.mnote/ocr-index.json` and return a redacted job payload. On process restart, any persisted `queued/uploading/mineru_processing/downloading/writing_sidecar` entry without active in-memory job is exposed as `interrupted`. + +- [ ] **Step 5: Broadcast OCR job events** + +Emit a local realtime event for every state change: + +```json +{ + "type": "local_ocr.job.updated", + "jobId": "ocr_1760000000000_abcd", + "rootUri": "file:///tmp/mnote-ocr", + "ownerDocumentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "docs/Page.assets/spec.pdf", + "ocrRootRelativePath": "docs/Page.ocr/spec.pdf.ocr.md", + "status": "mineru_processing", + "stageLabel": "识别中", + "updatedAtMs": 1760000000000 +} +``` + +Use the existing realtime / WS / SSE event path. Do not add a foreground `setInterval` polling loop as the primary update mechanism. + +- [ ] **Step 6: Register routes** + +Add route registrations in `routes/mod.rs`. + +- [ ] **Step 7: Verify route tests pass** + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_jobs -- --test-threads=1 +``` + +Expected: PASS. + +## Task 4: Keep OCR Markdown Out Of Page Tree + +**Files:** +- Modify: `rust/crates/mnote-web/src/routes/local_folder_source.rs` +- Modify: `rust/crates/mnote-web/src/routes/local_search_index.rs` + +- [ ] **Step 1: Add failing tests for `.ocr/*.ocr.md` classification** + +Cover: + +- `docs/Page.ocr/photo.png.ocr.md` is not a normal local Markdown document +- File Tree can still expose it as resource +- Page Tree projection excludes it + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_resource_classification -- --test-threads=1 +``` + +Expected: FAIL. + +- [ ] **Step 2: Implement OCR sidecar detection** + +Add a helper such as: + +```rust +fn is_local_ocr_sidecar_path(path: &Path) -> bool +``` + +Rules: + +- parent directory name ends with `.ocr` +- file name ends with `.ocr.md` +- file has `mnote_ocr_version` frontmatter when content is available + +Use path-only detection for tree projection and frontmatter-backed detection for search/index. + +- [ ] **Step 3: Verify classification tests pass** + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_resource_classification -- --test-threads=1 +``` + +Expected: PASS. + +## Task 5: Search OCR Text + +**Files:** +- Modify: `rust/crates/mnote-web/src/routes/local_search_index.rs` +- Modify: `rust/crates/mnote-web/src/routes/search.rs` + +- [ ] **Step 1: Add failing local search tests** + +Cover: + +- `includeOcr=false` does not match OCR sidecar text +- `includeOcr=true` matches OCR sidecar text +- result `documentId` is owner page id +- result has `hasOcr=true` +- evidence contains source and sidecar paths + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_ocr -- --test-threads=1 +``` + +Expected: FAIL. + +- [ ] **Step 2: Pass includeOcr into local search** + +Update `search.rs` local-folder branch to pass `filters.include_ocr.unwrap_or(false)`. + +- [ ] **Step 3: Extend local search index** + +Add OCR sidecar collection separate from normal documents. Search OCR text only when `include_ocr=true`, then project owner page result. + +- [ ] **Step 4: Verify search tests pass** + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_ocr -- --test-threads=1 +``` + +Expected: PASS. + +## Task 6: Browser Runtime OCR Actions + +**Files:** +- Modify: `rust/crates/mnote-web/browser/sidebar-attachment-open-runtime.js` +- Modify: `rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js` +- Modify: `rust/crates/mnote-web/browser/document-resource-tab-runtime.js` +- Create: `rust/crates/mnote-web/browser/local-ocr-task-runtime.js` +- Modify: `rust/crates/mnote-web/src/ssr/pages/layout.rs` + +- [ ] **Step 1: Add JS syntax and SSR wiring tests** + +Add or extend tests that assert OCR runtime dependencies are mounted and no `ReferenceError`-prone implicit globals are used. + +Run: + +```bash +node --check rust/crates/mnote-web/browser/sidebar-attachment-open-runtime.js +node --check rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js +node --check rust/crates/mnote-web/browser/document-resource-tab-runtime.js +node --check rust/crates/mnote-web/browser/local-ocr-task-runtime.js +cargo test --manifest-path rust/Cargo.toml -p mnote-web ocr_runtime -- --test-threads=1 +``` + +Expected before implementation: Rust OCR runtime assertions fail. + +- [ ] **Step 2: Add OCR actions** + +Add actions: + +- `OCR 识别` +- `查看 OCR` +- `插入 OCR 链接` +- `重新识别` + +Only show OCR actions for image and PDF resources. + +- [ ] **Step 3: Add OCR status fetch/render** + +Use `/api/local-folder/ocr/status` and `/read`. Show states: + +- 未识别 +- 排队中 +- 上传中 +- 识别中 +- 写入中 +- 已识别 +- 来源已变化 +- 识别失败 +- 已中断 + +- [ ] **Step 4: Add global OCR task bar and drawer** + +Create a global OCR task entry in the shell: + +- collapsed label: `OCR 2` or `1 个 OCR 任务进行中` +- drawer rows: file name, owner page, stage label, start time, completion/failure state +- row actions: `打开 OCR`, `重试` +- state source: initial `GET /api/local-folder/ocr/jobs?rootUri=...` plus `local_ocr.job.updated` events + +The runtime must update from events and explicit command results. It must not rely on `setInterval` polling as the main update path. + +- [ ] **Step 5: Verify JS and Rust wiring** + +Run the commands from Step 1. + +Expected: PASS. + +## Task 7: Insert OCR Link Into Markdown + +**Files:** +- Modify: `rust/crates/mnote-web/src/routes/local_ocr.rs` +- Modify: browser runtime files from Task 6 + +- [ ] **Step 1: Add route tests for insert** + +Cover: + +- inserting link appends `[OCR:photo.png](./Page.ocr/photo.png.ocr.md)` +- insert requires explicit OCR sidecar path +- insert respects local resource conflict detection +- `inlineSection` inserts heading, source comment and OCR body + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_insert -- --test-threads=1 +``` + +Expected: FAIL. + +- [ ] **Step 2: Implement `POST /api/local-folder/ocr/insert`** + +Use existing local resource write patterns and keep 正文 mutation explicit. + +- [ ] **Step 3: Wire UI insert actions** + +After successful insert, refresh current document through existing save/refresh event path, not polling. + +- [ ] **Step 4: Verify insert tests pass** + +Run: + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr_insert -- --test-threads=1 +``` + +Expected: PASS. + +## Task 8: Browser Smoke + +**Files:** +- Create: `scripts/task506-local-folder-mineru-ocr-smoke.js` +- Modify: `scripts/TESTING_REFERENCE.md` + +- [ ] **Step 1: Write smoke script** + +The smoke should: + +- create a temp local-folder workspace +- create `Page.md` +- place one PNG fixture and one PDF fixture next to it +- use mock MinerU mode or a test-only fixture response +- trigger OCR from UI +- assert `Page.ocr/*.ocr.md` exists +- assert the global OCR task bar shows running state after submission +- assert the global OCR task drawer moves to done after the mocked OCR event +- assert OCR resource tab opens +- assert search with `includeOcr=true` returns owner page +- assert insert link mutates `Page.md` only after explicit click + +- [ ] **Step 2: Run smoke** + +Run: + +```bash +MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3000 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task506-local-folder-mineru-ocr-smoke.js +``` + +Expected: `ok=true` and no browser `pageErrors`. + +- [ ] **Step 3: Update testing reference** + +Add the smoke to `scripts/TESTING_REFERENCE.md` under local-folder resource/browser smoke. + +## Task 9: Final Verification + +- [ ] **Step 1: Run focused Rust tests** + +```bash +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1 +cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_ocr -- --test-threads=1 +``` + +- [ ] **Step 2: Run browser runtime checks** + +```bash +node --check rust/crates/mnote-web/browser/sidebar-attachment-open-runtime.js +node --check rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js +node --check rust/crates/mnote-web/browser/document-resource-tab-runtime.js +node --check rust/crates/mnote-web/browser/local-ocr-task-runtime.js +``` + +- [ ] **Step 3: Run smoke** + +```bash +MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3000 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task506-local-folder-mineru-ocr-smoke.js +``` + +- [ ] **Step 4: Check diff hygiene** + +```bash +git diff --check -- rust/crates/mnote-web/src/routes/local_ocr.rs rust/crates/mnote-web/src/routes/mod.rs rust/crates/mnote-web/src/routes/local_folder_source.rs rust/crates/mnote-web/src/routes/local_search_index.rs rust/crates/mnote-web/src/routes/search.rs rust/crates/mnote-web/browser/sidebar-attachment-open-runtime.js rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js rust/crates/mnote-web/browser/document-resource-tab-runtime.js rust/crates/mnote-web/browser/local-ocr-task-runtime.js scripts/task506-local-folder-mineru-ocr-smoke.js scripts/TESTING_REFERENCE.md +``` + +- [ ] **Step 5: Sync CodeGraph before commit** + +```bash +codegraph sync . +codegraph status . +``` + +Expected: no pending CodeGraph changes. diff --git a/docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md b/docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md new file mode 100644 index 00000000..50113a33 --- /dev/null +++ b/docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md @@ -0,0 +1,394 @@ +# Page AI Mindmap Skill Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add the `mnote-mindmap` built-in Page AI skill and a first working `mnote.mindmap.create_from_outline` tool that generates MNote-compatible `.mindmap.json` envelopes from outlines. + +**Architecture:** Keep Page AI skill discovery in `hermes_tools::skill`, keep tool manifest shape in `hermes_tools::manifest`, and keep resource file behavior in `hermes_tools::resource`. The first implementation uses local-folder resource files and does not introduce PDF OCR; PDF is represented by outline fixtures that later PDF/Office tools can feed into `create_from_outline`. + +**Tech Stack:** Rust `mnote-web`, Axum route tests, serde_json, local-folder access guards, existing Hermes tool call pipeline. + +--- + +## Files + +- Create: `skills/mnote-mindmap/SKILL.md` +- Modify: `rust/crates/mnote-web/src/hermes_tools/skill.rs` +- Modify: `rust/crates/mnote-web/src/hermes_tools/manifest.rs` +- Modify: `rust/crates/mnote-web/src/hermes_tools/resource.rs` +- Modify: `rust/crates/mnote-web/src/routes/hermes_tools.rs` +- Modify: `rust/crates/mnote-web/src/routes/hermes_client.rs` +- Modify: `scripts/task502-page-ai-agent-selector-context-smoke.js` +- Test: `cargo test -p mnote-web --lib hermes_tools_mindmap --manifest-path rust/Cargo.toml` +- Test: `cargo test -p mnote-web --lib skill_registry --manifest-path rust/Cargo.toml` +- Test: `cargo test -p mnote-web --lib skill_read_returns_mindmap_skill_content --manifest-path rust/Cargo.toml` +- Test: `cargo test -p mnote-web --lib page_ai_skill_toggle_respects_builtin_user_policy_and_shared_readonly --manifest-path rust/Cargo.toml` +- Test: `node --check scripts/task502-page-ai-agent-selector-context-smoke.js` +- Smoke: `node scripts/task502-page-ai-agent-selector-context-smoke.js` +- Check: `git diff --check -- skills/mnote-mindmap/SKILL.md rust/crates/mnote-web/src/hermes_tools/skill.rs rust/crates/mnote-web/src/hermes_tools/manifest.rs rust/crates/mnote-web/src/hermes_tools/resource.rs rust/crates/mnote-web/src/routes/hermes_tools.rs rust/crates/mnote-web/src/routes/hermes_client.rs scripts/task502-page-ai-agent-selector-context-smoke.js docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md design/07-ai/process/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md` + +## Task 1: Built-in Skill Registration + +**Files:** +- Create: `skills/mnote-mindmap/SKILL.md` +- Modify: `rust/crates/mnote-web/src/hermes_tools/skill.rs` + +- [x] **Step 1: Write the failing skill registry test** + +Add a test in `rust/crates/mnote-web/src/hermes_tools/skill.rs`: + +```rust +#[test] +fn skill_registry_exposes_mindmap_skill_to_agents() { + let reasonix_skills = skill_summaries_for_agent(Some("reasonix")); + let skill = reasonix_skills + .iter() + .find(|skill| skill["id"] == "mnote-mindmap") + .expect("reasonix should see mindmap skill"); + assert_eq!(skill["readOnly"], false); + assert!( + skill["requiresContextRefs"] + .as_array() + .expect("context refs") + .iter() + .any(|value| value == "resource") + ); + assert!( + skill["toolNames"] + .as_array() + .expect("tool names") + .iter() + .any(|name| name == "mnote.mindmap.create_from_outline") + ); +} +``` + +- [x] **Step 2: Run the targeted failing test** + +Run: + +```bash +cargo test -p mnote-web --lib skill_registry_exposes_mindmap_skill_to_agents --manifest-path rust/Cargo.toml +``` + +Expected: FAIL because `mnote-mindmap` is not registered. + +- [x] **Step 3: Add the skill file** + +Create `skills/mnote-mindmap/SKILL.md` with the tool decision rules from `design/07-ai/process/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md`. + +- [x] **Step 4: Register the skill** + +Add a `MnoteSkill` entry in `rust/crates/mnote-web/src/hermes_tools/skill.rs`: + +```rust +MnoteSkill { + id: "mnote-mindmap", + title: "MNote mindmap editing", + description: "Read, update, summarize, or create MNote mindmap resources, including generating a new mindmap from PDF or document outlines.", + agent_ids: &["hermes", "reasonix"], + read_only: false, + requires_context_refs: &["current_page", "file", "folder", "resource"], + tool_names: &[ + "mnote.context.snapshot", + "mnote.context.resolve_target", + "mnote.mindmap.fetch", + "mnote.mindmap.apply_ops", + "mnote.mindmap.create_from_outline", + ], + content: include_str!("../../../../../skills/mnote-mindmap/SKILL.md"), +}, +``` + +- [x] **Step 5: Re-run the skill tests** + +Run: + +```bash +cargo test -p mnote-web --lib skill_registry --manifest-path rust/Cargo.toml +``` + +Expected: PASS for the skill registry tests. + +## Task 2: Tool Manifest Contract + +**Files:** +- Modify: `rust/crates/mnote-web/src/hermes_tools/manifest.rs` +- Modify: `rust/crates/mnote-web/src/routes/hermes_tools.rs` + +- [x] **Step 1: Write the failing manifest test** + +Extend `hermes_tools_manifest_returns_first_batch_tools` or add a dedicated test that asserts: + +```rust +let create_tool = tools + .iter() + .find(|tool| tool["name"] == "mnote.mindmap.create_from_outline") + .expect("create_from_outline tool"); +assert_eq!( + create_tool["inputSchema"]["properties"]["outline"]["type"], + "array" +); +assert!( + create_tool["capabilityScope"] + .as_array() + .expect("scope") + .iter() + .any(|scope| scope == "mindmap.write") +); +``` + +- [x] **Step 2: Run the failing manifest test** + +Run: + +```bash +cargo test -p mnote-web --lib hermes_tools_manifest_returns_first_batch_tools --manifest-path rust/Cargo.toml +``` + +Expected: FAIL because the manifest does not expose `mnote.mindmap.create_from_outline`. + +- [x] **Step 3: Add `mindmap_create_from_outline_tool()`** + +Add a write tool with required fields `documentId`, `mindmapId`, `outline`, `sessionId`, `runId`, `toolCallId`, `traceId`, and `actorId`. Include optional `title`, `rootUri`, `resourcePath`, `sourceRefs`, `embedIntoPage`, and `aiAccessScope`. + +- [x] **Step 4: Add routing for the new tool** + +In `execute_mnote_tool_call`, route: + +```rust +"mnote.mindmap.create_from_outline" => resource::mindmap_create_from_outline(&context, &input).await, +``` + +- [x] **Step 5: Re-run the manifest test** + +Run: + +```bash +cargo test -p mnote-web --lib hermes_tools_manifest_returns_first_batch_tools --manifest-path rust/Cargo.toml +``` + +Expected: PASS for manifest exposure. + +## Task 3: Mindmap Envelope Generation + +**Files:** +- Modify: `rust/crates/mnote-web/src/hermes_tools/resource.rs` +- Modify: `rust/crates/mnote-web/src/routes/hermes_tools.rs` + +- [x] **Step 1: Write the failing create-from-outline route test** + +Add an Axum test in `rust/crates/mnote-web/src/routes/hermes_tools.rs` that calls `mnote.mindmap.create_from_outline` with: + +```json +{ + "mindmapId": "maps/generated.mindmap.json", + "resourcePath": "maps/generated.mindmap.json", + "title": "PDF 摘要导图", + "outline": [ + { + "text": "章节一", + "children": [ + { "text": "要点 1", "children": [] } + ] + } + ], + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/generated.mindmap.json"] + } +} +``` + +Assert the response and written file: + +```rust +assert_eq!(payload["result"]["resourceKind"], "mindmap"); +assert_eq!(payload["result"]["root"]["data"]["text"], "PDF 摘要导图"); +assert_eq!(payload["result"]["root"]["children"][0]["data"]["text"], "章节一"); +assert_eq!(payload["result"]["envelope"]["data"]["data"]["uid"], "root"); +assert!(root.join("maps").join("generated.mindmap.json").exists()); +``` + +- [x] **Step 2: Run the failing create test** + +Run: + +```bash +cargo test -p mnote-web --lib hermes_tools_mindmap_create_from_outline_writes_default_envelope --manifest-path rust/Cargo.toml +``` + +Expected: FAIL because the tool does not exist. + +- [x] **Step 3: Implement outline conversion helpers** + +Implement helpers in `resource.rs`: + +- `default_mindmap_view() -> Value` +- `mindmap_envelope_from_outline(title: &str, outline: &Value) -> Result` +- `mindmap_outline_items_to_children(outline: &[Value], path: &str) -> Vec` + +Generated child node shape: + +```json +{ + "data": { + "expand": true, + "isActive": false, + "text": "节点文本", + "uid": "node_1_1" + }, + "children": [] +} +``` + +- [x] **Step 4: Implement `mindmap_create_from_outline`** + +Use existing write guard and path guard: + +- call `ensure_resource_write_contract` +- build `ResourceToolTarget` +- call `ensure_resource_scope_allowed` +- resolve write path under authorized root +- create parent directory +- write pretty JSON +- return envelope, root, markdown summary, revision, and changed file path + +- [x] **Step 5: Re-run the create test** + +Run: + +```bash +cargo test -p mnote-web --lib hermes_tools_mindmap_create_from_outline_writes_default_envelope --manifest-path rust/Cargo.toml +``` + +Expected: PASS. + +## Task 4: Fetch Envelope Awareness + +**Files:** +- Modify: `rust/crates/mnote-web/src/hermes_tools/resource.rs` +- Modify: `rust/crates/mnote-web/src/routes/hermes_tools.rs` + +- [x] **Step 1: Write a failing fetch test for the default envelope** + +Add a test that writes a file shaped as: + +```json +{ + "data": { + "children": [], + "data": { + "expand": true, + "isActive": false, + "text": "KMIND", + "uid": "root" + } + }, + "view": { + "state": { "scale": 1, "sx": 0, "sy": 0, "x": 0, "y": 0 }, + "transform": { "a": 1, "b": 0, "c": 0, "d": 1, "e": 0, "f": 0 } + } +} +``` + +Then call `mnote.mindmap.fetch` with `scope=full_envelope` and assert: + +```rust +assert_eq!(payload["result"]["root"]["data"]["text"], "KMIND"); +assert_eq!(payload["result"]["envelope"]["data"]["data"]["uid"], "root"); +``` + +- [x] **Step 2: Run the failing fetch test** + +Run: + +```bash +cargo test -p mnote-web --lib hermes_tools_mindmap_fetch_reads_default_envelope --manifest-path rust/Cargo.toml +``` + +Expected: FAIL if fetch only understands naked simple-mind-map trees. + +- [x] **Step 3: Normalize envelope root** + +Update `collect_mindmap_nodes` and response building to use: + +```rust +fn mindmap_root_value(value: &Value) -> &Value { + value.get("data") + .filter(|data| data.get("data").is_some() || data.get("children").is_some()) + .unwrap_or(value) +} +``` + +Return `envelope` only when `scope == "full_envelope"`. + +- [x] **Step 4: Re-run fetch tests** + +Run: + +```bash +cargo test -p mnote-web --lib hermes_tools_mindmap_fetch --manifest-path rust/Cargo.toml +``` + +Expected: PASS for existing and envelope fetch tests. + +## Task 5: Final Verification + +**Files:** +- All touched files from prior tasks. + +- [x] **Step 1: Run non-mutating Rust format audit** + +Run: + +```bash +cargo fmt --manifest-path rust/Cargo.toml --all --check +``` + +Actual: command reports existing workspace-wide rustfmt drift, including unrelated dirty files and pre-existing touched-file formatting. Do not run mutating `cargo fmt` in this dirty worktree without an explicit cleanup decision. + +- [x] **Step 2: Run targeted Rust tests** + +Run: + +```bash +cargo test -p mnote-web --lib skill_registry --manifest-path rust/Cargo.toml +cargo test -p mnote-web --lib skill_read_returns_mindmap_skill_content --manifest-path rust/Cargo.toml +cargo test -p mnote-web --lib hermes_tools_manifest_returns_first_batch_tools --manifest-path rust/Cargo.toml +cargo test -p mnote-web --lib hermes_tools_mindmap --manifest-path rust/Cargo.toml +cargo test -p mnote-web --lib page_ai_skill_toggle_respects_builtin_user_policy_and_shared_readonly --manifest-path rust/Cargo.toml +``` + +Expected: all targeted tests pass. + +- [x] **Step 3: Run JS syntax and browser smoke checks** + +Run: + +```bash +node --check scripts/task502-page-ai-agent-selector-context-smoke.js +node scripts/task502-page-ai-agent-selector-context-smoke.js +``` + +Expected: syntax check passes; smoke captures `skillPreferences.mnote["mnote-mindmap"] = false` in the Page AI run payload. + +- [x] **Step 4: Run diff whitespace check** + +Run: + +```bash +git diff --check -- skills/mnote-mindmap/SKILL.md rust/crates/mnote-web/src/hermes_tools/skill.rs rust/crates/mnote-web/src/hermes_tools/manifest.rs rust/crates/mnote-web/src/hermes_tools/resource.rs rust/crates/mnote-web/src/routes/hermes_tools.rs rust/crates/mnote-web/src/routes/hermes_client.rs scripts/task502-page-ai-agent-selector-context-smoke.js docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md design/07-ai/process/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md +``` + +Expected: no output. + +- [x] **Step 5: Inspect scoped git diff** + +Run: + +```bash +git diff -- skills/mnote-mindmap/SKILL.md rust/crates/mnote-web/src/hermes_tools/skill.rs rust/crates/mnote-web/src/hermes_tools/manifest.rs rust/crates/mnote-web/src/hermes_tools/resource.rs rust/crates/mnote-web/src/routes/hermes_tools.rs rust/crates/mnote-web/src/routes/hermes_client.rs scripts/task502-page-ai-agent-selector-context-smoke.js docs/superpowers/plans/2026-05-30-page-ai-mindmap-skill.md design/07-ai/process/7-42-page-ai-mindmap-skill-and-resource-generation-v1.md +``` + +Expected: diff only contains `mnote-mindmap`, `create_from_outline`, envelope fetch support, tests, and docs. diff --git a/infra/onlyoffice/docker-compose.yml b/infra/onlyoffice/docker-compose.yml index a90e2a62..8c36bb1a 100644 --- a/infra/onlyoffice/docker-compose.yml +++ b/infra/onlyoffice/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.9" services: onlyoffice-documentserver: - image: onlyoffice/documentserver:9.3.1 + image: onlyoffice/documentserver:9.4.0 container_name: mnote-onlyoffice-documentserver restart: unless-stopped ports: diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 5c22a1f9..713f020f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -14,6 +14,23 @@ dependencies = [ "url", ] +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -99,6 +116,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "async-lock" version = "3.4.2" @@ -273,12 +299,37 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "camino" version = "1.2.2" @@ -301,6 +352,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -330,6 +383,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.6.0" @@ -475,6 +538,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "control-plane" version = "0.1.0" @@ -543,6 +612,30 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -565,6 +658,12 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + [[package]] name = "deranged" version = "0.5.8" @@ -585,6 +684,17 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -724,6 +834,16 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -1277,6 +1397,15 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "interpolator" version = "0.5.0" @@ -1335,6 +1464,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.95" @@ -1558,6 +1697,27 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "manyhow" version = "0.11.4" @@ -1599,6 +1759,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.0" @@ -1659,6 +1829,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", + "zip", ] [[package]] @@ -1790,6 +1961,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2540,6 +2721,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "siphasher" version = "1.0.3" @@ -3707,6 +3894,15 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yansi" version = "1.0.1" @@ -3782,6 +3978,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" @@ -3816,8 +4026,78 @@ dependencies = [ "syn", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "getrandom 0.3.4", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "thiserror 2.0.18", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/rust/crates/bridge-runtime/src/lib.rs b/rust/crates/bridge-runtime/src/lib.rs index eb7e370e..b1d5b7c2 100644 --- a/rust/crates/bridge-runtime/src/lib.rs +++ b/rust/crates/bridge-runtime/src/lib.rs @@ -202,6 +202,10 @@ fn legacy_command_function_name(name: &str) -> BridgeResult<&'static str> { Ok("documents:updateContent") } "mindmaps.put" => Ok("mindmaps:put"), + "tree.resource.archive" => Ok("treeResource:archive"), + "tree.resource.restore" => Ok("treeResource:restore"), + "tree.resource.purge" => Ok("treeResource:purge"), + "tree.resource.rename" => Ok("treeResource:rename"), _ => Err(retired_bridge_error()), } } @@ -6416,6 +6420,25 @@ fn legacy_block_projection_attrs(block: &Value, block_type: &str) -> Value { attrs.insert("language".into(), json!(language)); } } + if block_type == "mindmap" { + for (raw_key, canonical_key) in [ + ("mindmapId", "mindmapId"), + ("mindmap_id", "mindmapId"), + ("sourcePath", "sourcePath"), + ("source_path", "sourcePath"), + ("rootNodeId", "rootNodeId"), + ("root_node_id", "rootNodeId"), + ] { + if let Some(value) = props + .and_then(|map| map.get(raw_key)) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + { + attrs.insert(canonical_key.into(), json!(value)); + } + } + } Value::Object(attrs) } @@ -13086,6 +13109,50 @@ mod tests { ); } + #[test] + fn page_aggregate_block_document_preserves_mindmap_projection_attrs() { + let result = execute_runtime_query(RuntimeInput::Query { + context: demo_context(), + query: RuntimeQueryEnvelopeWire { + name: "page.aggregate.get".into(), + payload: json!({ + "documentId": "doc_mindmap", + "workspaceId": "ws_1", + }), + }, + data: Some(json!({ + "meta": { + "id": "doc_mindmap", + "workspace_id": "ws_1", + "title": "Mindmap Page" + }, + "content": { + "content": [ + { + "id": "local-block-1", + "type": "mindmap", + "props": { + "mindmapId": "mindmap-123456.json", + "sourcePath": "mindmap-123456.json", + "rootNodeId": "root" + }, + "content": [] + } + ], + "revision": 3, + "conflict_detection_key": "doc_mindmap:3" + } + })), + }) + .expect("page aggregate query should build"); + + let block = &result["body"]["blockDocument"]["blocks"][0]; + assert_eq!(block["type"], json!("mindmap")); + assert_eq!(block["attrs"]["mindmapId"], json!("mindmap-123456.json")); + assert_eq!(block["attrs"]["sourcePath"], json!("mindmap-123456.json")); + assert_eq!(block["attrs"]["rootNodeId"], json!("root")); + } + #[test] fn page_aggregate_get_prefers_editor_document_over_legacy_content() { let result = execute_runtime_query(RuntimeInput::Query { diff --git a/rust/crates/control-plane/migrations/007-ai-agent-profile-policy.sql b/rust/crates/control-plane/migrations/007-ai-agent-profile-policy.sql new file mode 100644 index 00000000..ab7a945e --- /dev/null +++ b/rust/crates/control-plane/migrations/007-ai-agent-profile-policy.sql @@ -0,0 +1,43 @@ +-- 007-ai-agent-profile-policy.sql +-- Page AI agent profile policy: SQLite 是 profile 授权与归属真相层。 + +CREATE TABLE IF NOT EXISTS ai_agent_profiles ( + id TEXT PRIMARY KEY, + agent_id TEXT NOT NULL, + profile_kind TEXT NOT NULL, + owner_user_id TEXT NOT NULL DEFAULT '', + base_profile_name TEXT NOT NULL, + isolated_profile_name TEXT NOT NULL, + display_name TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'active', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + revision INTEGER NOT NULL DEFAULT 1, + UNIQUE(agent_id, profile_kind, owner_user_id, base_profile_name) +); + +CREATE INDEX IF NOT EXISTS idx_ai_agent_profiles_agent + ON ai_agent_profiles(agent_id, status); + +CREATE INDEX IF NOT EXISTS idx_ai_agent_profiles_owner + ON ai_agent_profiles(owner_user_id, status); + +CREATE TABLE IF NOT EXISTS ai_agent_profile_grants ( + id TEXT PRIMARY KEY, + profile_id TEXT NOT NULL REFERENCES ai_agent_profiles(id) ON DELETE CASCADE, + user_id TEXT NOT NULL DEFAULT '', + role TEXT NOT NULL, + can_run INTEGER NOT NULL DEFAULT 1, + can_manage_skills INTEGER NOT NULL DEFAULT 0, + can_manage_config INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + revision INTEGER NOT NULL DEFAULT 1, + UNIQUE(profile_id, user_id, role) +); + +CREATE INDEX IF NOT EXISTS idx_ai_agent_profile_grants_profile + ON ai_agent_profile_grants(profile_id); + +CREATE INDEX IF NOT EXISTS idx_ai_agent_profile_grants_user + ON ai_agent_profile_grants(user_id); diff --git a/rust/crates/control-plane/migrations/008-ai-external-conversation-bindings.sql b/rust/crates/control-plane/migrations/008-ai-external-conversation-bindings.sql new file mode 100644 index 00000000..c1f678d1 --- /dev/null +++ b/rust/crates/control-plane/migrations/008-ai-external-conversation-bindings.sql @@ -0,0 +1,28 @@ +CREATE TABLE IF NOT EXISTS ai_external_conversation_bindings ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + workspace_id TEXT, + mnote_session_id TEXT NOT NULL, + acp_session_id TEXT, + agent_id TEXT NOT NULL, + profile TEXT NOT NULL, + provider TEXT NOT NULL, + remote_conversation_id TEXT NOT NULL, + remote_url TEXT, + status TEXT NOT NULL DEFAULT 'active', + metadata_json TEXT NOT NULL DEFAULT '{}', + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + revision INTEGER NOT NULL DEFAULT 1, + CHECK (status IN ('active', 'local_deleted', 'remote_deleted', 'remote_delete_failed', 'remote_missing')) +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_external_conv_user_session_provider +ON ai_external_conversation_bindings(user_id, mnote_session_id, provider); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_external_conv_user_provider_remote +ON ai_external_conversation_bindings(user_id, provider, remote_conversation_id); + +CREATE INDEX IF NOT EXISTS idx_ai_external_conv_lookup +ON ai_external_conversation_bindings(user_id, workspace_id, mnote_session_id, provider, status); diff --git a/rust/crates/control-plane/src/migrations.rs b/rust/crates/control-plane/src/migrations.rs index 5ffdcbed..7dbc5a8c 100644 --- a/rust/crates/control-plane/src/migrations.rs +++ b/rust/crates/control-plane/src/migrations.rs @@ -30,6 +30,14 @@ const MIGRATIONS: &[(&str, &str)] = &[ "v6-navigation-recent", include_str!("../migrations/006-navigation-recent.sql"), ), + ( + "v7-ai-agent-profile-policy", + include_str!("../migrations/007-ai-agent-profile-policy.sql"), + ), + ( + "v8-ai-external-conversation-bindings", + include_str!("../migrations/008-ai-external-conversation-bindings.sql"), + ), ]; /// Create the `_migrations` meta-table if it does not exist. @@ -113,6 +121,9 @@ mod tests { "sidebar_shortcuts", "user_ui_preferences", "user_navigation_recent", + "ai_agent_profiles", + "ai_agent_profile_grants", + "ai_external_conversation_bindings", ] { assert!(table_exists(&conn, table), "table {table} should exist"); } diff --git a/rust/crates/control-plane/src/model.rs b/rust/crates/control-plane/src/model.rs index ac705520..e1ad8910 100644 --- a/rust/crates/control-plane/src/model.rs +++ b/rust/crates/control-plane/src/model.rs @@ -243,6 +243,41 @@ pub struct UpsertUserUiPreferenceInput { pub value_json: String, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AiAgentProfileRecord { + pub id: EntityId, + pub agent_id: String, + pub profile_kind: String, + pub owner_user_id: Option, + pub base_profile_name: String, + pub isolated_profile_name: String, + pub display_name: String, + pub status: String, + pub created_at: Timestamp, + pub updated_at: Timestamp, + pub revision: i64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AiAgentProfileGrantRecord { + pub id: EntityId, + pub profile_id: EntityId, + pub user_id: Option, + pub role: String, + pub can_run: bool, + pub can_manage_skills: bool, + pub can_manage_config: bool, + pub created_at: Timestamp, + pub updated_at: Timestamp, + pub revision: i64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AiAgentProfileAccessRecord { + pub profile: AiAgentProfileRecord, + pub grant: AiAgentProfileGrantRecord, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct NavigationRecentRecord { pub id: EntityId, @@ -393,6 +428,42 @@ pub struct AppendAiRuntimeEventInput { pub payload_json: String, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AiExternalConversationBindingRecord { + pub id: EntityId, + pub user_id: EntityId, + pub workspace_id: Option, + pub mnote_session_id: EntityId, + pub acp_session_id: Option, + pub agent_id: String, + pub profile: String, + pub provider: String, + pub remote_conversation_id: String, + pub remote_url: Option, + pub status: String, + pub metadata_json: String, + pub created_at: Timestamp, + pub updated_at: Timestamp, + pub deleted_at: Option, + pub revision: i64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct UpsertAiExternalConversationBindingInput { + pub id: Option, + pub user_id: EntityId, + pub workspace_id: Option, + pub mnote_session_id: EntityId, + pub acp_session_id: Option, + pub agent_id: String, + pub profile: String, + pub provider: String, + pub remote_conversation_id: String, + pub remote_url: Option, + pub status: String, + pub metadata_json: String, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ShareLinkRecord { pub id: EntityId, diff --git a/rust/crates/control-plane/src/sqlite.rs b/rust/crates/control-plane/src/sqlite.rs index 49c99226..b6b896f9 100644 --- a/rust/crates/control-plane/src/sqlite.rs +++ b/rust/crates/control-plane/src/sqlite.rs @@ -1,5 +1,6 @@ //! SQLite-backed control-plane store. +use std::collections::BTreeMap; use std::sync::Mutex; use rusqlite::{params, Connection, OptionalExtension}; @@ -7,15 +8,17 @@ use rusqlite::{params, Connection, OptionalExtension}; use crate::error::ControlPlaneError; use crate::migrations; use crate::model::{ - password_hash_v1, share_token_hash_v1, AiPolicyRecord, AiRuntimeEventRecord, - AiRuntimeRunRecord, AppendAiRuntimeEventInput, AppendAuditInput, AuditLogRecord, - AuthSessionRecord, AuthenticatePasswordInput, CreatePasswordIdentityInput, CreateSessionInput, - CreateShareLinkInput, CreatedShareLink, DirectoryGrantInput, DirectoryGrantLookup, - DirectoryGrantRecord, NavigationRecentRecord, OutboxEventInput, OutboxEventRecord, - ResolvedAccess, ResolvedAuthSession, ShareLinkRecord, SidebarShortcutRecord, SyncStateRecord, - UpsertAiPolicyInput, UpsertAiRuntimeRunInput, UpsertNavigationRecentInput, - UpsertSidebarShortcutInput, UpsertSyncStateInput, UpsertUserInput, UpsertUserUiPreferenceInput, - UserRecord, UserUiPreferenceRecord, WorkspaceRecord, + password_hash_v1, share_token_hash_v1, AiAgentProfileAccessRecord, AiAgentProfileGrantRecord, + AiAgentProfileRecord, AiExternalConversationBindingRecord, AiPolicyRecord, + AiRuntimeEventRecord, AiRuntimeRunRecord, AppendAiRuntimeEventInput, AppendAuditInput, + AuditLogRecord, AuthSessionRecord, AuthenticatePasswordInput, CreatePasswordIdentityInput, + CreateSessionInput, CreateShareLinkInput, CreatedShareLink, DirectoryGrantInput, + DirectoryGrantLookup, DirectoryGrantRecord, NavigationRecentRecord, OutboxEventInput, + OutboxEventRecord, ResolvedAccess, ResolvedAuthSession, ShareLinkRecord, SidebarShortcutRecord, + SyncStateRecord, UpsertAiExternalConversationBindingInput, UpsertAiPolicyInput, + UpsertAiRuntimeRunInput, UpsertNavigationRecentInput, UpsertSidebarShortcutInput, + UpsertSyncStateInput, UpsertUserInput, UpsertUserUiPreferenceInput, UserRecord, + UserUiPreferenceRecord, WorkspaceRecord, }; use crate::store::ControlPlaneStore; @@ -214,6 +217,66 @@ fn option_to_stored_text(value: Option) -> String { .unwrap_or_default() } +fn sanitize_profile_component(value: &str) -> String { + let mut sanitized = value + .trim() + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() { + ch.to_ascii_lowercase() + } else { + '-' + } + }) + .collect::(); + while sanitized.contains("--") { + sanitized = sanitized.replace("--", "-"); + } + sanitized = sanitized.trim_matches('-').to_string(); + if sanitized.is_empty() { + "user".to_string() + } else { + sanitized + } +} + +fn row_to_ai_agent_profile(row: &rusqlite::Row<'_>) -> rusqlite::Result { + let owner_user_id: String = row.get(3)?; + Ok(AiAgentProfileRecord { + id: row.get(0)?, + agent_id: row.get(1)?, + profile_kind: row.get(2)?, + owner_user_id: empty_string_to_option(owner_user_id), + base_profile_name: row.get(4)?, + isolated_profile_name: row.get(5)?, + display_name: row.get(6)?, + status: row.get(7)?, + created_at: row.get(8)?, + updated_at: row.get(9)?, + revision: row.get(10)?, + }) +} + +fn row_to_ai_agent_profile_access( + row: &rusqlite::Row<'_>, +) -> rusqlite::Result { + Ok(AiAgentProfileAccessRecord { + profile: row_to_ai_agent_profile(row)?, + grant: AiAgentProfileGrantRecord { + id: row.get(11)?, + profile_id: row.get(12)?, + user_id: empty_string_to_option(row.get(13)?), + role: row.get(14)?, + can_run: row.get::<_, i64>(15)? != 0, + can_manage_skills: row.get::<_, i64>(16)? != 0, + can_manage_config: row.get::<_, i64>(17)? != 0, + created_at: row.get(18)?, + updated_at: row.get(19)?, + revision: row.get(20)?, + }, + }) +} + fn row_to_user_ui_preference(row: &rusqlite::Row<'_>) -> rusqlite::Result { Ok(UserUiPreferenceRecord { id: row.get(0)?, @@ -281,6 +344,19 @@ fn navigation_recent_target_key( } } +fn validate_ai_external_conversation_status(status: &str) -> Result<(), ControlPlaneError> { + match status { + "active" + | "local_deleted" + | "remote_deleted" + | "remote_delete_failed" + | "remote_missing" => Ok(()), + _ => Err(ControlPlaneError::InvalidInput( + "external conversation status 不合法".to_string(), + )), + } +} + fn row_to_ai_policy(row: &rusqlite::Row<'_>) -> rusqlite::Result { Ok(AiPolicyRecord { id: row.get(0)?, @@ -348,6 +424,29 @@ fn row_to_ai_runtime_event(row: &rusqlite::Row<'_>) -> rusqlite::Result, +) -> rusqlite::Result { + Ok(AiExternalConversationBindingRecord { + id: row.get(0)?, + user_id: row.get(1)?, + workspace_id: row.get(2)?, + mnote_session_id: row.get(3)?, + acp_session_id: row.get(4)?, + agent_id: row.get(5)?, + profile: row.get(6)?, + provider: row.get(7)?, + remote_conversation_id: row.get(8)?, + remote_url: row.get(9)?, + status: row.get(10)?, + metadata_json: row.get(11)?, + created_at: row.get(12)?, + updated_at: row.get(13)?, + deleted_at: row.get(14)?, + revision: row.get(15)?, + }) +} + fn derive_ai_runtime_title(payload_json: &str, fallback: &str) -> String { let title = serde_json::from_str::(payload_json) .ok() @@ -391,6 +490,50 @@ fn prune_navigation_recent_for_kind( Ok(()) } +fn list_ai_agent_profile_access_rows( + conn: &Connection, + user_id: &str, +) -> Result, ControlPlaneError> { + let mut stmt = conn.prepare( + "SELECT + p.id, p.agent_id, p.profile_kind, p.owner_user_id, p.base_profile_name, + p.isolated_profile_name, p.display_name, p.status, p.created_at, p.updated_at, + p.revision, + g.id, g.profile_id, g.user_id, g.role, g.can_run, g.can_manage_skills, + g.can_manage_config, g.created_at, g.updated_at, g.revision + FROM ai_agent_profiles p + JOIN ai_agent_profile_grants g ON g.profile_id = p.id + WHERE p.agent_id = 'hermes' + AND p.status = 'active' + AND g.can_run = 1 + AND ( + (p.profile_kind = 'personal' AND p.owner_user_id = ?1 AND g.user_id = ?1) + OR (p.profile_kind = 'shared' AND (g.user_id = '' OR g.user_id = ?1)) + ) + ORDER BY + CASE p.profile_kind WHEN 'personal' THEN 0 ELSE 1 END, + p.display_name ASC, + g.can_manage_skills DESC, + g.can_manage_config DESC", + )?; + let rows = stmt + .query_map(params![user_id], row_to_ai_agent_profile_access)? + .collect::, _>>() + .map_err(ControlPlaneError::from)?; + let mut by_profile = BTreeMap::::new(); + for row in rows { + by_profile + .entry(row.profile.id.clone()) + .and_modify(|existing| { + if row.grant.can_manage_skills && !existing.grant.can_manage_skills { + *existing = row.clone(); + } + }) + .or_insert(row); + } + Ok(by_profile.into_values().collect()) +} + impl ControlPlaneStore for SqliteControlPlaneStore { fn upsert_user(&self, input: UpsertUserInput) -> Result { if input.username.trim().is_empty() { @@ -1461,6 +1604,168 @@ impl ControlPlaneStore for SqliteControlPlaneStore { Ok(rows) } + fn ensure_ai_agent_profile_policy( + &self, + user_id: &str, + is_admin: bool, + ) -> Result, ControlPlaneError> { + let user_id = user_id.trim(); + if user_id.is_empty() { + return Err(ControlPlaneError::InvalidInput( + "ai agent profile user_id 不能为空".to_string(), + )); + } + let conn = self.conn.lock().unwrap(); + let now = now_text(); + conn.execute( + "INSERT INTO ai_agent_profiles ( + id, agent_id, profile_kind, owner_user_id, base_profile_name, + isolated_profile_name, display_name, status, created_at, updated_at, revision + ) + VALUES ('shared_lite', 'hermes', 'shared', '', 'lite', 'lite', 'Lite', 'active', ?1, ?2, 1) + ON CONFLICT(agent_id, profile_kind, owner_user_id, base_profile_name) + DO UPDATE SET status = 'active', updated_at = excluded.updated_at", + params![now, now], + )?; + conn.execute( + "INSERT INTO ai_agent_profile_grants ( + id, profile_id, user_id, role, can_run, can_manage_skills, + can_manage_config, created_at, updated_at, revision + ) + VALUES ('grant_shared_lite_all', 'shared_lite', '', 'user', 1, 0, 0, ?1, ?2, 1) + ON CONFLICT(profile_id, user_id, role) + DO UPDATE SET can_run = 1, can_manage_skills = 0, can_manage_config = 0, + updated_at = excluded.updated_at", + params![now, now], + )?; + + // 旧的泛化 OpenClaw 网页问答入口已拆成明确的三类 chat agent。 + conn.execute( + "UPDATE ai_agent_profiles + SET status = 'retired', updated_at = ?1 + WHERE id = 'shared_openclaw_webqa'", + params![now], + )?; + let shared_chat_profiles = [ + ( + "shared_deepseek_chat", + "deepseek-chat", + "openclaw-deepseek-chat", + "DeepSeek Chat", + ), + ( + "shared_gemini_chat", + "gemini-chat", + "openclaw-gemini-chat", + "Gemini Chat", + ), + ( + "shared_doubao_chat", + "doubao-chat", + "openclaw-doubao-chat", + "豆包 Chat", + ), + ]; + for (profile_id, base_profile, isolated_profile, display_name) in shared_chat_profiles { + conn.execute( + "INSERT INTO ai_agent_profiles ( + id, agent_id, profile_kind, owner_user_id, base_profile_name, + isolated_profile_name, display_name, status, created_at, updated_at, revision + ) + VALUES (?1, 'hermes', 'shared', '', ?2, ?3, ?4, 'active', ?5, ?6, 1) + ON CONFLICT(agent_id, profile_kind, owner_user_id, base_profile_name) + DO UPDATE SET status = 'active', display_name = excluded.display_name, + isolated_profile_name = excluded.isolated_profile_name, updated_at = excluded.updated_at", + params![profile_id, base_profile, isolated_profile, display_name, now, now], + )?; + let grant_id = format!("grant_{profile_id}_all"); + conn.execute( + "INSERT INTO ai_agent_profile_grants ( + id, profile_id, user_id, role, can_run, can_manage_skills, + can_manage_config, created_at, updated_at, revision + ) + VALUES (?1, ?2, '', 'user', 1, 0, 0, ?3, ?4, 1) + ON CONFLICT(profile_id, user_id, role) + DO UPDATE SET can_run = 1, can_manage_skills = 0, can_manage_config = 0, + updated_at = excluded.updated_at", + params![grant_id, profile_id, now, now], + )?; + } + + let user_part = sanitize_profile_component(user_id); + let personal_profile_id = format!("usr_{user_part}_default"); + let personal_profile_name = format!("mnote-u-{user_part}-default"); + conn.execute( + "INSERT INTO ai_agent_profiles ( + id, agent_id, profile_kind, owner_user_id, base_profile_name, + isolated_profile_name, display_name, status, created_at, updated_at, revision + ) + VALUES (?1, 'hermes', 'personal', ?2, 'default', ?3, '我的 Hermes', 'active', ?4, ?5, 1) + ON CONFLICT(agent_id, profile_kind, owner_user_id, base_profile_name) + DO UPDATE SET status = 'active', updated_at = excluded.updated_at", + params![personal_profile_id, user_id, personal_profile_name, now, now], + )?; + let personal_grant_id = format!("grant_{personal_profile_id}_owner"); + conn.execute( + "INSERT INTO ai_agent_profile_grants ( + id, profile_id, user_id, role, can_run, can_manage_skills, + can_manage_config, created_at, updated_at, revision + ) + VALUES (?1, ?2, ?3, 'owner', 1, 1, 1, ?4, ?5, 1) + ON CONFLICT(profile_id, user_id, role) + DO UPDATE SET can_run = 1, can_manage_skills = 1, can_manage_config = 1, + updated_at = excluded.updated_at", + params![personal_grant_id, personal_profile_id, user_id, now, now], + )?; + + if is_admin { + let admin_grant_id = format!("grant_shared_lite_admin_{user_part}"); + conn.execute( + "INSERT INTO ai_agent_profile_grants ( + id, profile_id, user_id, role, can_run, can_manage_skills, + can_manage_config, created_at, updated_at, revision + ) + VALUES (?1, 'shared_lite', ?2, 'admin', 1, 1, 1, ?3, ?4, 1) + ON CONFLICT(profile_id, user_id, role) + DO UPDATE SET can_run = 1, can_manage_skills = 1, can_manage_config = 1, + updated_at = excluded.updated_at", + params![admin_grant_id, user_id, now, now], + )?; + for (profile_id, _, _, _) in shared_chat_profiles { + let admin_chat_grant_id = format!("grant_{profile_id}_admin_{user_part}"); + conn.execute( + "INSERT INTO ai_agent_profile_grants ( + id, profile_id, user_id, role, can_run, can_manage_skills, + can_manage_config, created_at, updated_at, revision + ) + VALUES (?1, ?2, ?3, 'admin', 1, 0, 0, ?4, ?5, 1) + ON CONFLICT(profile_id, user_id, role) + DO UPDATE SET can_run = 1, can_manage_skills = 0, can_manage_config = 0, + updated_at = excluded.updated_at", + params![admin_chat_grant_id, profile_id, user_id, now, now], + )?; + } + } + + list_ai_agent_profile_access_rows(&conn, user_id) + } + + fn resolve_ai_agent_profile( + &self, + user_id: &str, + is_admin: bool, + profile_id: &str, + ) -> Result, ControlPlaneError> { + let profile_id = profile_id.trim(); + if profile_id.is_empty() { + return Ok(None); + } + let access = self.ensure_ai_agent_profile_policy(user_id, is_admin)?; + Ok(access + .into_iter() + .find(|item| item.profile.id == profile_id && item.grant.can_run)) + } + fn upsert_navigation_recent( &self, input: UpsertNavigationRecentInput, @@ -2047,6 +2352,207 @@ impl ControlPlaneStore for SqliteControlPlaneStore { Ok(rows) } + fn upsert_ai_external_conversation_binding( + &self, + input: UpsertAiExternalConversationBindingInput, + ) -> Result { + let user_id = input.user_id.trim(); + let mnote_session_id = input.mnote_session_id.trim(); + let provider = input.provider.trim(); + let remote_conversation_id = input.remote_conversation_id.trim(); + if user_id.is_empty() + || mnote_session_id.is_empty() + || provider.is_empty() + || remote_conversation_id.is_empty() + { + return Err(ControlPlaneError::InvalidInput( + "external conversation binding user_id/session_id/provider/remote_id 不能为空" + .to_string(), + )); + } + serde_json::from_str::(&input.metadata_json)?; + validate_ai_external_conversation_status(&input.status)?; + + let conn = self.conn.lock().unwrap(); + let now = now_text(); + let existing = conn + .query_row( + "SELECT id, user_id, workspace_id, mnote_session_id, acp_session_id, agent_id, profile, provider, remote_conversation_id, remote_url, status, metadata_json, created_at, updated_at, deleted_at, revision + FROM ai_external_conversation_bindings + WHERE user_id = ?1 AND mnote_session_id = ?2 AND provider = ?3 + LIMIT 1", + params![user_id, mnote_session_id, provider], + row_to_ai_external_conversation_binding, + ) + .optional()?; + if let Some(existing) = existing { + let revision = existing.revision + 1; + let deleted_at = if input.status == "active" { + None + } else { + existing.deleted_at.or_else(|| Some(now.clone())) + }; + conn.execute( + "UPDATE ai_external_conversation_bindings + SET workspace_id = ?1, acp_session_id = ?2, agent_id = ?3, profile = ?4, remote_conversation_id = ?5, remote_url = ?6, status = ?7, metadata_json = ?8, updated_at = ?9, deleted_at = ?10, revision = ?11 + WHERE id = ?12", + params![ + input.workspace_id, + input.acp_session_id, + input.agent_id, + input.profile, + remote_conversation_id, + input.remote_url, + input.status, + input.metadata_json, + now, + deleted_at, + revision, + existing.id + ], + )?; + return Ok(AiExternalConversationBindingRecord { + id: existing.id, + user_id: existing.user_id, + workspace_id: input.workspace_id, + mnote_session_id: existing.mnote_session_id, + acp_session_id: input.acp_session_id, + agent_id: input.agent_id, + profile: input.profile, + provider: existing.provider, + remote_conversation_id: remote_conversation_id.to_string(), + remote_url: input.remote_url, + status: input.status, + metadata_json: input.metadata_json, + created_at: existing.created_at, + updated_at: now, + deleted_at, + revision, + }); + } + + let deleted_at = if input.status == "active" { + None + } else { + Some(now.clone()) + }; + let record = AiExternalConversationBindingRecord { + id: input.id.unwrap_or_else(|| new_id("aecb")), + user_id: user_id.to_string(), + workspace_id: input.workspace_id, + mnote_session_id: mnote_session_id.to_string(), + acp_session_id: input.acp_session_id, + agent_id: input.agent_id, + profile: input.profile, + provider: provider.to_string(), + remote_conversation_id: remote_conversation_id.to_string(), + remote_url: input.remote_url, + status: input.status, + metadata_json: input.metadata_json, + created_at: now.clone(), + updated_at: now, + deleted_at, + revision: 1, + }; + conn.execute( + "INSERT INTO ai_external_conversation_bindings (id, user_id, workspace_id, mnote_session_id, acp_session_id, agent_id, profile, provider, remote_conversation_id, remote_url, status, metadata_json, created_at, updated_at, deleted_at, revision) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, 1)", + params![ + record.id, + record.user_id, + record.workspace_id, + record.mnote_session_id, + record.acp_session_id, + record.agent_id, + record.profile, + record.provider, + record.remote_conversation_id, + record.remote_url, + record.status, + record.metadata_json, + record.created_at, + record.updated_at, + record.deleted_at + ], + )?; + Ok(record) + } + + fn find_ai_external_conversation_binding( + &self, + user_id: &str, + workspace_id: Option<&str>, + mnote_session_id: &str, + provider: &str, + ) -> Result, ControlPlaneError> { + let user_id = user_id.trim(); + let mnote_session_id = mnote_session_id.trim(); + let provider = provider.trim(); + if user_id.is_empty() || mnote_session_id.is_empty() || provider.is_empty() { + return Ok(None); + } + let conn = self.conn.lock().unwrap(); + conn.query_row( + "SELECT id, user_id, workspace_id, mnote_session_id, acp_session_id, agent_id, profile, provider, remote_conversation_id, remote_url, status, metadata_json, created_at, updated_at, deleted_at, revision + FROM ai_external_conversation_bindings + WHERE user_id = ?1 + AND (?2 IS NULL OR workspace_id = ?2) + AND mnote_session_id = ?3 + AND provider = ?4 + LIMIT 1", + params![user_id, workspace_id, mnote_session_id, provider], + row_to_ai_external_conversation_binding, + ) + .optional() + .map_err(ControlPlaneError::from) + } + + fn mark_ai_external_conversation_binding_status( + &self, + user_id: &str, + workspace_id: Option<&str>, + mnote_session_id: &str, + provider: &str, + status: &str, + metadata_json: Option<&str>, + ) -> Result { + let user_id = user_id.trim(); + let mnote_session_id = mnote_session_id.trim(); + let provider = provider.trim(); + validate_ai_external_conversation_status(status)?; + let metadata_json = metadata_json.unwrap_or("{}"); + serde_json::from_str::(metadata_json)?; + if user_id.is_empty() || mnote_session_id.is_empty() || provider.is_empty() { + return Ok(0); + } + let conn = self.conn.lock().unwrap(); + let now = now_text(); + let deleted_at = if status == "active" { + None + } else { + Some(now.clone()) + }; + let changed = conn.execute( + "UPDATE ai_external_conversation_bindings + SET status = ?1, metadata_json = ?2, updated_at = ?3, deleted_at = COALESCE(?4, deleted_at), revision = revision + 1 + WHERE user_id = ?5 + AND (?6 IS NULL OR workspace_id = ?6) + AND mnote_session_id = ?7 + AND provider = ?8", + params![ + status, + metadata_json, + now, + deleted_at, + user_id, + workspace_id, + mnote_session_id, + provider + ], + )?; + Ok(changed) + } + fn rename_ai_runtime_session( &self, user_id: &str, @@ -2195,9 +2701,9 @@ mod tests { use super::*; use crate::model::{ password_hash_v1, session_token_hash, AppendAiRuntimeEventInput, AuthenticatePasswordInput, - CreatePasswordIdentityInput, UpsertAiPolicyInput, UpsertAiRuntimeRunInput, - UpsertNavigationRecentInput, UpsertSidebarShortcutInput, UpsertSyncStateInput, - UpsertUserUiPreferenceInput, + CreatePasswordIdentityInput, UpsertAiExternalConversationBindingInput, UpsertAiPolicyInput, + UpsertAiRuntimeRunInput, UpsertNavigationRecentInput, UpsertSidebarShortcutInput, + UpsertSyncStateInput, UpsertUserUiPreferenceInput, }; fn store() -> SqliteControlPlaneStore { @@ -2716,6 +3222,95 @@ mod tests { assert!(bob_sidebar_preferences.is_empty()); } + #[test] + fn ai_agent_profile_policy_provisions_personal_and_shared_boundaries() { + let store = store(); + create_user(&store, "alice"); + create_user(&store, "bob"); + store + .upsert_user(UpsertUserInput { + id: Some("admin".to_string()), + email: Some("admin@example.com".to_string()), + username: "admin".to_string(), + display_name: "admin".to_string(), + role: Some("admin".to_string()), + password_hash: None, + }) + .expect("admin user"); + + let alice_profiles = store + .ensure_ai_agent_profile_policy("alice", false) + .expect("alice profiles"); + assert_eq!(alice_profiles.len(), 5); + let alice_personal = alice_profiles + .iter() + .find(|item| item.profile.profile_kind == "personal") + .expect("alice personal profile"); + assert_eq!( + alice_personal.profile.owner_user_id.as_deref(), + Some("alice") + ); + assert!(alice_personal.grant.can_manage_skills); + let alice_shared = alice_profiles + .iter() + .find(|item| item.profile.id == "shared_lite") + .expect("alice shared profile"); + assert!(alice_shared.grant.can_run); + assert!(!alice_shared.grant.can_manage_skills); + for (profile_id, isolated_profile, display_name) in [ + ( + "shared_deepseek_chat", + "openclaw-deepseek-chat", + "DeepSeek Chat", + ), + ("shared_gemini_chat", "openclaw-gemini-chat", "Gemini Chat"), + ("shared_doubao_chat", "openclaw-doubao-chat", "豆包 Chat"), + ] { + let alice_chat = alice_profiles + .iter() + .find(|item| item.profile.id == profile_id) + .expect("alice shared chat profile"); + assert_eq!(alice_chat.profile.isolated_profile_name, isolated_profile); + assert_eq!(alice_chat.profile.display_name, display_name); + assert!(alice_chat.grant.can_run); + assert!(!alice_chat.grant.can_manage_skills); + assert!(!alice_chat.grant.can_manage_config); + } + + let bob_profiles = store + .ensure_ai_agent_profile_policy("bob", false) + .expect("bob profiles"); + let bob_personal = bob_profiles + .iter() + .find(|item| item.profile.profile_kind == "personal") + .expect("bob personal profile"); + assert_ne!(bob_personal.profile.id, alice_personal.profile.id); + assert!(store + .resolve_ai_agent_profile("bob", false, &alice_personal.profile.id) + .expect("resolve cross user") + .is_none()); + + let admin_shared = store + .resolve_ai_agent_profile("admin", true, "shared_lite") + .expect("admin shared") + .expect("admin can see shared"); + assert!(admin_shared.grant.can_manage_skills); + assert!(admin_shared.grant.can_manage_config); + for profile_id in [ + "shared_deepseek_chat", + "shared_gemini_chat", + "shared_doubao_chat", + ] { + let admin_chat = store + .resolve_ai_agent_profile("admin", true, profile_id) + .expect("admin chat") + .expect("admin can see shared chat profile"); + assert!(admin_chat.grant.can_run); + assert!(!admin_chat.grant.can_manage_skills); + assert!(!admin_chat.grant.can_manage_config); + } + } + #[test] fn navigation_recent_is_user_scoped_upserted_and_limited_by_kind() { let store = store(); @@ -3025,6 +3620,79 @@ mod tests { assert!(runs.is_empty()); } + #[test] + fn ai_external_conversation_binding_is_user_scoped_and_statused() { + let store = store(); + create_user(&store, "doubao_user_a"); + create_user(&store, "doubao_user_b"); + + let binding = store + .upsert_ai_external_conversation_binding(UpsertAiExternalConversationBindingInput { + id: None, + user_id: "doubao_user_a".to_string(), + workspace_id: Some("ws_1".to_string()), + mnote_session_id: "chatonly_session_1".to_string(), + acp_session_id: Some("acp_session_1".to_string()), + agent_id: "chat_only".to_string(), + profile: "openclaw-doubao-chat".to_string(), + provider: "doubao-web".to_string(), + remote_conversation_id: "38428454119180290".to_string(), + remote_url: Some("https://www.doubao.com/chat/38428454119180290".to_string()), + status: "active".to_string(), + metadata_json: "{\"source\":\"provider.conversation.bound\"}".to_string(), + }) + .expect("upsert external conversation binding"); + + assert_eq!(binding.status, "active"); + assert_eq!(binding.remote_conversation_id, "38428454119180290"); + + let found = store + .find_ai_external_conversation_binding( + "doubao_user_a", + Some("ws_1"), + "chatonly_session_1", + "doubao-web", + ) + .expect("find binding") + .expect("binding exists"); + assert_eq!(found.id, binding.id); + assert_eq!(found.acp_session_id.as_deref(), Some("acp_session_1")); + + let other_user = store + .find_ai_external_conversation_binding( + "doubao_user_b", + Some("ws_1"), + "chatonly_session_1", + "doubao-web", + ) + .expect("find other user binding"); + assert!(other_user.is_none()); + + let changed = store + .mark_ai_external_conversation_binding_status( + "doubao_user_a", + Some("ws_1"), + "chatonly_session_1", + "doubao-web", + "local_deleted", + Some("{\"reason\":\"mnote_session_deleted\"}"), + ) + .expect("mark local deleted"); + assert_eq!(changed, 1); + + let deleted = store + .find_ai_external_conversation_binding( + "doubao_user_a", + Some("ws_1"), + "chatonly_session_1", + "doubao-web", + ) + .expect("find deleted binding") + .expect("binding remains auditable"); + assert_eq!(deleted.status, "local_deleted"); + assert!(deleted.deleted_at.is_some()); + } + #[test] fn session_lookup_resolves_active_user_by_token_hash() { let store = store(); diff --git a/rust/crates/control-plane/src/store.rs b/rust/crates/control-plane/src/store.rs index 9ddd8775..19656427 100644 --- a/rust/crates/control-plane/src/store.rs +++ b/rust/crates/control-plane/src/store.rs @@ -2,14 +2,16 @@ use crate::error::ControlPlaneError; use crate::model::{ - AiPolicyRecord, AiRuntimeEventRecord, AiRuntimeRunRecord, AppendAiRuntimeEventInput, - AppendAuditInput, AuditLogRecord, AuthSessionRecord, AuthenticatePasswordInput, - CreatePasswordIdentityInput, CreateSessionInput, CreateShareLinkInput, CreatedShareLink, - DirectoryGrantInput, DirectoryGrantLookup, DirectoryGrantRecord, NavigationRecentRecord, - OutboxEventInput, OutboxEventRecord, ResolvedAccess, ResolvedAuthSession, ShareLinkRecord, - SidebarShortcutRecord, SyncStateRecord, UpsertAiPolicyInput, UpsertAiRuntimeRunInput, - UpsertNavigationRecentInput, UpsertSidebarShortcutInput, UpsertSyncStateInput, UpsertUserInput, - UpsertUserUiPreferenceInput, UserRecord, UserUiPreferenceRecord, WorkspaceRecord, + AiAgentProfileAccessRecord, AiExternalConversationBindingRecord, AiPolicyRecord, + AiRuntimeEventRecord, AiRuntimeRunRecord, AppendAiRuntimeEventInput, AppendAuditInput, + AuditLogRecord, AuthSessionRecord, AuthenticatePasswordInput, CreatePasswordIdentityInput, + CreateSessionInput, CreateShareLinkInput, CreatedShareLink, DirectoryGrantInput, + DirectoryGrantLookup, DirectoryGrantRecord, NavigationRecentRecord, OutboxEventInput, + OutboxEventRecord, ResolvedAccess, ResolvedAuthSession, ShareLinkRecord, SidebarShortcutRecord, + SyncStateRecord, UpsertAiExternalConversationBindingInput, UpsertAiPolicyInput, + UpsertAiRuntimeRunInput, UpsertNavigationRecentInput, UpsertSidebarShortcutInput, + UpsertSyncStateInput, UpsertUserInput, UpsertUserUiPreferenceInput, UserRecord, + UserUiPreferenceRecord, WorkspaceRecord, }; pub trait ControlPlaneStore: Send + Sync { @@ -136,6 +138,19 @@ pub trait ControlPlaneStore: Send + Sync { source_kind: Option<&str>, ) -> Result, ControlPlaneError>; + fn ensure_ai_agent_profile_policy( + &self, + user_id: &str, + is_admin: bool, + ) -> Result, ControlPlaneError>; + + fn resolve_ai_agent_profile( + &self, + user_id: &str, + is_admin: bool, + profile_id: &str, + ) -> Result, ControlPlaneError>; + fn upsert_navigation_recent( &self, input: UpsertNavigationRecentInput, @@ -196,6 +211,29 @@ pub trait ControlPlaneStore: Send + Sync { limit: usize, ) -> Result, ControlPlaneError>; + fn upsert_ai_external_conversation_binding( + &self, + input: UpsertAiExternalConversationBindingInput, + ) -> Result; + + fn find_ai_external_conversation_binding( + &self, + user_id: &str, + workspace_id: Option<&str>, + mnote_session_id: &str, + provider: &str, + ) -> Result, ControlPlaneError>; + + fn mark_ai_external_conversation_binding_status( + &self, + user_id: &str, + workspace_id: Option<&str>, + mnote_session_id: &str, + provider: &str, + status: &str, + metadata_json: Option<&str>, + ) -> Result; + fn rename_ai_runtime_session( &self, user_id: &str, diff --git a/rust/crates/core-protocol/src/kernel.rs b/rust/crates/core-protocol/src/kernel.rs index fc3e68f6..3b1487c6 100644 --- a/rust/crates/core-protocol/src/kernel.rs +++ b/rust/crates/core-protocol/src/kernel.rs @@ -264,10 +264,18 @@ impl DocumentBuffer { self.base_content_hash = Some(content_hash); self.current_content_hash = None; self.dirty_state = DocBufferDirtyState::Clean; - if write_intent_id.as_deref().map(str::trim).is_some_and(|value| !value.is_empty()) { + if write_intent_id + .as_deref() + .map(str::trim) + .is_some_and(|value| !value.is_empty()) + { self.last_write_intent_id = write_intent_id; } - if save_operation_id.as_deref().map(str::trim).is_some_and(|value| !value.is_empty()) { + if save_operation_id + .as_deref() + .map(str::trim) + .is_some_and(|value| !value.is_empty()) + { self.last_save_operation_id = save_operation_id; } self.last_saved_at = Some( @@ -922,7 +930,10 @@ mod tests { request.expected_file_version.as_deref(), Some("local-md:local-md:README.md:1:2:hash") ); - assert_eq!(request.write_intent_id.as_deref(), Some("intent:editor:abc")); + assert_eq!( + request.write_intent_id.as_deref(), + Some("intent:editor:abc") + ); assert_eq!(request.save_operation_id.as_deref(), Some("save:op:abc")); assert_eq!(request.content_format, "editorBlocks"); assert_eq!(request.editor_source.as_deref(), Some("tiptap")); diff --git a/rust/crates/mnote-web/Cargo.toml b/rust/crates/mnote-web/Cargo.toml index 61e95e88..e88a8fd5 100644 --- a/rust/crates/mnote-web/Cargo.toml +++ b/rust/crates/mnote-web/Cargo.toml @@ -31,3 +31,4 @@ comrak = { version = "0.52", default-features = false } notify = "8.2.0" time = { version = "0.3", features = ["formatting", "local-offset"] } uuid = { version = "1", features = ["v4"] } +zip = "2" diff --git a/rust/crates/mnote-web/browser/document-resource-tab-runtime.js b/rust/crates/mnote-web/browser/document-resource-tab-runtime.js index 7e9feafa..751360f3 100644 --- a/rust/crates/mnote-web/browser/document-resource-tab-runtime.js +++ b/rust/crates/mnote-web/browser/document-resource-tab-runtime.js @@ -64,6 +64,7 @@ export const createResourceTabRuntime = (dependencies = {}) => { const resourceTabMru = { primary: [], secondary: [] }; const resourceTabMruMax = 20; const resourceTabCloseGuardAttribute = 'data-resource-tab-close-guarded'; + let onlyofficeBridgeReadyListenerBound = false; const normalizePaneRole = (paneRole) => String(paneRole || '').trim() === 'secondary' ? 'secondary' : 'primary'; @@ -127,6 +128,30 @@ export const createResourceTabRuntime = (dependencies = {}) => { } }; + const currentFileTreeWorkspacePath = () => { + try { + const row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path]'); + const reader = window.__mnoteFileTreeRuntime?.readWorkspacePathFromRow; + if (row instanceof HTMLElement && typeof reader === 'function') { + const workspacePath = reader(row); + if (workspacePath && typeof workspacePath === 'object') { + return { + ...workspacePath, + sourceKind: String(workspacePath.sourceKind || row.getAttribute('data-source-kind') || '').trim(), + rootUri: String(workspacePath.rootUri || row.getAttribute('data-root-uri') || '').trim(), + }; + } + } + if (row instanceof HTMLElement) { + return { + sourceKind: String(row.getAttribute('data-source-kind') || '').trim(), + rootUri: String(row.getAttribute('data-root-uri') || '').trim(), + }; + } + } catch (_) {} + return null; + }; + const currentWebShellDocumentId = () => { const explicit = typeof currentDocumentId === 'function' ? String(currentDocumentId() || '').trim() : ''; if (explicit) return explicit; @@ -157,17 +182,23 @@ export const createResourceTabRuntime = (dependencies = {}) => { const currentWebShellSourceKind = () => { try { - return currentUrl().searchParams.get('sourceKind') || ''; + return currentUrl().searchParams.get('sourceKind') + || document.body?.dataset?.mnoteSourceKind + || currentFileTreeWorkspacePath()?.sourceKind + || ''; } catch (_) { - return ''; + return document.body?.dataset?.mnoteSourceKind || currentFileTreeWorkspacePath()?.sourceKind || ''; } }; const currentWebShellRootUri = () => { try { - return currentUrl().searchParams.get('rootUri') || ''; + return currentUrl().searchParams.get('rootUri') + || document.body?.dataset?.mnoteRootUri + || currentFileTreeWorkspacePath()?.rootUri + || ''; } catch (_) { - return ''; + return document.body?.dataset?.mnoteRootUri || currentFileTreeWorkspacePath()?.rootUri || ''; } }; @@ -196,7 +227,9 @@ export const createResourceTabRuntime = (dependencies = {}) => { resourceKind: String(fromInput.resourceKind || fromInput.objectKind || resourceKind || '').trim(), }; } - const id = String(objectIdentity || documentId || assetId || relativePath || '').trim(); + const structuredObjectIdentity = objectIdentity && typeof objectIdentity === 'object' ? objectIdentity : null; + const objectIdentityText = structuredObjectIdentity ? '' : String(objectIdentity || '').trim(); + const id = String(objectIdentityText || documentId || assetId || relativePath || '').trim(); if (!id) return null; return { schema: 'mnote.workspace_path.v1', @@ -205,7 +238,7 @@ export const createResourceTabRuntime = (dependencies = {}) => { rootUri: String(rootUri || '').trim(), relativePath: String(relativePath || '').trim(), documentId: String(documentId || '').trim(), - objectIdentity: String(objectIdentity || '').trim(), + objectIdentity: structuredObjectIdentity || objectIdentityText, assetId: String(assetId || '').trim(), resourceKind: String(resourceKind || '').trim(), }; @@ -371,6 +404,18 @@ export const createResourceTabRuntime = (dependencies = {}) => { }); }; + const officeBridgeDebugForEntry = (entry) => { + if (!entry?.panel || !(entry.panel instanceof HTMLElement)) return null; + const frame = entry.panel.querySelector('iframe.mnote-resource-tab-frame'); + if (!(frame instanceof HTMLIFrameElement)) return null; + try { + const debug = frame.contentWindow?.__MNOTE_ONLYOFFICE_DEBUG__; + return debug && typeof debug === 'object' ? debug : null; + } catch (_) { + return null; + } + }; + const openEditorsSnapshotEntry = (entry, key, generatedAt = Date.now()) => { const active = entry?.tab instanceof HTMLElement ? entry.tab.getAttribute('aria-selected') === 'true' @@ -384,6 +429,13 @@ export const createResourceTabRuntime = (dependencies = {}) => { const assetId = String(entry?.assetId || entry?.session?.assetId || '').trim(); const kind = normalizeResourceTabKind(entry); const dirtyState = resourceTabCloseGuardReason(entry?.session); + const officeBridgeDebug = kind === 'office' ? officeBridgeDebugForEntry(entry) : null; + const onlyofficeSessionId = String( + officeBridgeDebug?.bridgeSessionId + || entry?.onlyofficeSessionId + || entry?.bridgeSessionId + || '', + ).trim(); return { objectIdentity, workspacePath: buildWorkspacePath({ @@ -411,6 +463,11 @@ export const createResourceTabRuntime = (dependencies = {}) => { dirtyGuard: dirtyState, assetId, path: relativePath, + onlyofficeSessionId, + bridgeSessionId: onlyofficeSessionId, + bridgeSessionReady: Boolean(onlyofficeSessionId), + bridgeDocumentId: String(officeBridgeDebug?.documentId || '').trim(), + bridgeAssetId: String(officeBridgeDebug?.assetId || '').trim(), lastActiveAt: Number(entry?.session?.lastActiveAt || entry?.lastActiveAt || 0) || (active ? generatedAt : 0), preview: false, pinned: false, @@ -427,6 +484,12 @@ export const createResourceTabRuntime = (dependencies = {}) => { const rootUri = currentWebShellRootUri(); const relativePath = sourceKind === 'local_folder' ? localMarkdownRelativePathFromDocumentId(documentId) : ''; const objectIdentity = `page:${paneRole}`; + const workspaceObjectIdentity = { + objectKind: 'page', + documentId, + blockId: null, + assetId: null, + }; const active = nodes.pageTab instanceof HTMLElement ? nodes.pageTab.getAttribute('aria-selected') === 'true' : false; @@ -443,7 +506,7 @@ export const createResourceTabRuntime = (dependencies = {}) => { rootUri, relativePath, documentId, - objectIdentity, + objectIdentity: workspaceObjectIdentity, assetId: '', resourceKind: 'page', }), @@ -1035,6 +1098,17 @@ export const createResourceTabRuntime = (dependencies = {}) => { markIntendedSlashRoot(entry); }; + const ensureOnlyofficeBridgeReadyListener = () => { + if (onlyofficeBridgeReadyListenerBound) return; + onlyofficeBridgeReadyListenerBound = true; + window.addEventListener('message', (event) => { + if (event.origin !== window.location.origin) return; + const detail = event.data && typeof event.data === 'object' ? event.data : null; + if (!detail || detail.type !== 'mnote:onlyoffice-bridge-ready') return; + syncOpenEditorsSnapshot(); + }); + }; + const openPassiveResourceTab = (entry, input) => { const href = String(input.officeUrl || input.href || '').trim(); if (entry.kind === 'image') { @@ -1051,6 +1125,12 @@ export const createResourceTabRuntime = (dependencies = {}) => { const frame = entry.panel.querySelector('iframe'); if (frame instanceof HTMLIFrameElement) { frame.title = entry.title; + if (entry.kind === 'office') { + ensureOnlyofficeBridgeReadyListener(); + frame.addEventListener('load', () => { + syncOpenEditorsSnapshot(); + }, { once: true }); + } frame.src = href; } installPassiveResourceWatch(entry); diff --git a/rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js b/rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js index 20abd4a5..23fd4dd7 100644 --- a/rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js +++ b/rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js @@ -15,6 +15,18 @@ export const firstNonEmptyText = (...values) => { return ''; }; +export const normalizeMindmapDimension = (value, kind) => { + const raw = typeof value === 'number' + ? value + : typeof value === 'string' + ? Number(value.trim().replace(/px$/i, '')) + : NaN; + if (!Number.isFinite(raw)) return null; + const rounded = Math.round(raw); + const min = kind === 'height' ? 240 : 900; + return rounded >= min ? rounded : null; +}; + // 过渡适配(TODO step-4):legacy→Tiptap inline marks 转换函数组。 // AST/block 迁移 complete 后,前端应直接消费 block document 中的 // tiptap 格式 marks(已由 Rust 侧 local_markdown_parser 输出), @@ -125,19 +137,26 @@ export const legacyBlockToTiptap = (block, index = 0) => { } if (type === 'divider' || type === 'horizontalRule' || type === 'horizontal_rule') return { type: 'horizontalRule', attrs: { blockId } }; if (type === 'mindmap') { + const attrs = block?.attrs && typeof block.attrs === 'object' ? block.attrs : null; const data = block?.props?.data && typeof block.props.data === 'object' ? block.props.data : null; const mindmapId = firstNonEmptyText( block?.props?.mindmapId, block?.props?.mindmap_id, block?.props?.sourcePath, block?.props?.source_path, + attrs?.mindmapId, + attrs?.mindmap_id, + attrs?.sourcePath, + attrs?.source_path, block?.mindmapId, block?.mindmap_id, data?.mindmapId, data?.mindmap_id, data?.id ); - const rootNodeId = firstNonEmptyText(block?.props?.rootNodeId, block?.props?.root_node_id, data?.rootNodeId, data?.root_node_id) || 'root'; + const rootNodeId = firstNonEmptyText(block?.props?.rootNodeId, block?.props?.root_node_id, attrs?.rootNodeId, attrs?.root_node_id, data?.rootNodeId, data?.root_node_id) || 'root'; + const mindmapWidth = normalizeMindmapDimension(block?.props?.mindmapWidth ?? block?.props?.mindmap_width ?? attrs?.mindmapWidth ?? attrs?.mindmap_width ?? data?.mindmapWidth ?? data?.mindmap_width, 'width'); + const mindmapHeight = normalizeMindmapDimension(block?.props?.mindmapHeight ?? block?.props?.mindmap_height ?? attrs?.mindmapHeight ?? attrs?.mindmap_height ?? data?.mindmapHeight ?? data?.mindmap_height, 'height'); return { type: 'paragraph', attrs: withTextAlign({ @@ -145,6 +164,8 @@ export const legacyBlockToTiptap = (block, index = 0) => { mnoteBlockType: 'mindmap', mindmapId, rootNodeId, + ...(mindmapWidth !== null ? { mindmapWidth } : {}), + ...(mindmapHeight !== null ? { mindmapHeight } : {}), }), }; } @@ -225,7 +246,9 @@ export const mindmapDomDescriptors = (root) => { const rootNodeId = typeof node.dataset.mnoteRootNodeId === 'string' && node.dataset.mnoteRootNodeId.trim() ? node.dataset.mnoteRootNodeId.trim() : 'root'; - return [{ mindmapId, rootNodeId }]; + const mindmapWidth = normalizeMindmapDimension(node.dataset.mnoteMindmapWidth, 'width'); + const mindmapHeight = normalizeMindmapDimension(node.dataset.mnoteMindmapHeight, 'height'); + return [{ mindmapId, rootNodeId, mindmapWidth, mindmapHeight }]; }); }; @@ -246,6 +269,12 @@ export const hydrateMindmapAttrsFromDom = (tiptapDocument, root) => { if (typeof node.attrs.rootNodeId !== 'string' || !node.attrs.rootNodeId.trim()) { node.attrs.rootNodeId = descriptor.rootNodeId; } + if (normalizeMindmapDimension(node.attrs.mindmapWidth, 'width') === null && descriptor.mindmapWidth !== null) { + node.attrs.mindmapWidth = descriptor.mindmapWidth; + } + if (normalizeMindmapDimension(node.attrs.mindmapHeight, 'height') === null && descriptor.mindmapHeight !== null) { + node.attrs.mindmapHeight = descriptor.mindmapHeight; + } } return tiptapDocument; }; @@ -445,20 +474,17 @@ export const localizeTiptapAssetUrls = (node, context) => { }; export const pageBodyTiptapDocumentSource = (body, fallbackText = '') => { + const blockDocument = body?.blockDocument || body?.block_document || body?.editorDocument || body?.editor_document || null; + if (blockDocument) return 'page_aggregate.block_document'; if (String(body?.projectionSource || body?.projection_source || '') === 'local_markdown.content' && body?.content) { return 'local_markdown.content'; } - const blockDocument = body?.blockDocument || body?.block_document || body?.editorDocument || body?.editor_document || null; - if (blockDocument) return 'page_aggregate.block_document'; if (body?.content) return 'compat.legacy_content'; return fallbackText ? 'degraded.fallback_text' : 'empty'; }; export const pageBodyTiptapDocument = (body, fallbackText = '', context = null) => { const projectionContext = { ...(context || {}), body }; - if (pageBodyTiptapDocumentSource(body, fallbackText) === 'local_markdown.content') { - return localizeTiptapAssetUrls(toTiptapDocument(body.content, fallbackText), projectionContext); - } const blockDocument = body?.blockDocument || body?.block_document || body?.editorDocument || body?.editor_document || null; if (blockDocument) return localizeTiptapAssetUrls(toTiptapDocument(blockDocument, fallbackText), projectionContext); return localizeTiptapAssetUrls(toTiptapDocument(body?.content, fallbackText), projectionContext); @@ -533,10 +559,14 @@ export const mindmapPropsFromAttrs = (attrs, fallbackMindmapId) => { ); const rootNodeId = firstNonEmptyText(attrs?.rootNodeId, attrs?.root_node_id, data?.rootNodeId, data?.root_node_id) || 'root'; const projectionVersion = Number(attrs?.projectionVersion ?? attrs?.projection_version); + const mindmapWidth = normalizeMindmapDimension(attrs?.mindmapWidth ?? attrs?.mindmap_width ?? data?.mindmapWidth ?? data?.mindmap_width, 'width'); + const mindmapHeight = normalizeMindmapDimension(attrs?.mindmapHeight ?? attrs?.mindmap_height ?? data?.mindmapHeight ?? data?.mindmap_height, 'height'); return { mindmapId, rootNodeId, ...(Number.isFinite(projectionVersion) ? { projectionVersion } : {}), + ...(mindmapWidth !== null ? { mindmapWidth } : {}), + ...(mindmapHeight !== null ? { mindmapHeight } : {}), }; }; diff --git a/rust/crates/mnote-web/browser/filetree-runtime.js b/rust/crates/mnote-web/browser/filetree-runtime.js index 97b229b8..14b2ad61 100644 --- a/rust/crates/mnote-web/browser/filetree-runtime.js +++ b/rust/crates/mnote-web/browser/filetree-runtime.js @@ -9,7 +9,12 @@ function fileTreeRowKind(row) { function fileTreeRowDocumentId(row) { if (!(row instanceof HTMLElement)) return ''; - return String(row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || '').trim(); + return String( + row.getAttribute('data-document-id') + || row.getAttribute('data-doc-id') + || row.getAttribute('data-owner-document-id') + || '', + ).trim(); } function fileTreeRowAssetId(row, deps) { @@ -98,6 +103,11 @@ function readWorkspacePathFromRow(row, deps) { var documentId = fileTreeRowDocumentId(row); var rowId = String(row.getAttribute('data-row-id') || '').trim(); var rowKind = fileTreeRowKind(row); + if (!documentId && (rowKind === 'folder' || objectKind === 'index') && relativePath) { + documentId = 'local-dir:' + relativePath.replace(/^\/+/, '').split('/').map(function(segment) { + return encodeURIComponent(segment).replace(/%20/g, '~20'); + }).join('~2F'); + } var sourceKind = String(row.getAttribute('data-source-kind') || currentSourceKind() || '').trim(); var rootUri = String(row.getAttribute('data-root-uri') || currentRootUri() || '').trim(); return { diff --git a/rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js b/rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js index e31330e7..c956c54a 100644 --- a/rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-filetree-open-runtime.js @@ -410,7 +410,7 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => { navigateToMindmapObject(String(detail && detail.documentId || currentDocumentId() || '').trim(), assetId, String(detail.workspaceId || '').trim()); return; } - var requestedOfficeMode = forceNewWindow || forceEditMode ? 'edit' : 'view'; + var requestedOfficeMode = forceEditMode ? 'edit' : 'view'; var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail && detail.documentId || currentDocumentId() || '').trim(), assetId, requestedOfficeMode); if (localOfficeUrl) { if (!forceNewWindow && await openLocalResourceInActiveTab({ diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-markdown-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-markdown-runtime.js new file mode 100644 index 00000000..15e275bf --- /dev/null +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-markdown-runtime.js @@ -0,0 +1,107 @@ +export function createSidebarPageAiMarkdownRuntime(context) { + const { escapeHtml } = context; + + function textFromUnknown(value) { + if (value == null) return ''; + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return String(value); + if (Array.isArray(value)) return value.map(textFromUnknown).filter(Boolean).join(' '); + if (typeof value !== 'object') return ''; + var parts = []; + ['text', 'title', 'content', 'children', 'blocks', 'body'].forEach(function(key) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + var text = textFromUnknown(value[key]); + if (text) parts.push(text); + } + }); + return parts.join(' '); + } + + function renderPageAiMarkdownInline(text) { + var html = escapeHtml(String(text || '')); + var codeSpans = []; + html = html.replace(/`([^`\n]+)`/g, function(_, code) { + var key = '\u0000CODE' + codeSpans.length + '\u0000'; + codeSpans.push('' + code + ''); + return key; + }); + html = html.replace(/\*\*([^*\n]+)\*\*/g, '$1'); + html = html.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1$2'); + codeSpans.forEach(function(value, index) { + html = html.replace('\u0000CODE' + index + '\u0000', value); + }); + return html; + } + + function renderPageAiMarkdown(content) { + var lines = String(content || '').replace(/\r\n?/g, '\n').split('\n'); + var blocks = []; + var index = 0; + function isBlockBoundary(line) { + return !line.trim() || + /^```/.test(line.trim()) || + /^#{1,6}\s+/.test(line) || + /^\s*[-*]\s+/.test(line) || + /^\s*\d+[.)]\s+/.test(line); + } + while (index < lines.length) { + var line = lines[index]; + if (!line.trim()) { + index += 1; + continue; + } + if (/^```/.test(line.trim())) { + index += 1; + var codeLines = []; + while (index < lines.length && !/^```/.test(lines[index].trim())) { + codeLines.push(lines[index]); + index += 1; + } + if (index < lines.length) index += 1; + blocks.push('
' + escapeHtml(codeLines.join('\n')) + '
'); + continue; + } + var heading = line.match(/^(#{1,6})\s+(.+)$/); + if (heading) { + var level = Math.min(6, heading[1].length); + blocks.push('' + renderPageAiMarkdownInline(heading[2]) + ''); + index += 1; + continue; + } + if (/^\s*[-*]\s+/.test(line)) { + var unordered = []; + while (index < lines.length && /^\s*[-*]\s+/.test(lines[index])) { + unordered.push('
  • ' + renderPageAiMarkdownInline(lines[index].replace(/^\s*[-*]\s+/, '')) + '
  • '); + index += 1; + } + blocks.push('
      ' + unordered.join('') + '
    '); + continue; + } + if (/^\s*\d+[.)]\s+/.test(line)) { + var ordered = []; + while (index < lines.length && /^\s*\d+[.)]\s+/.test(lines[index])) { + ordered.push('
  • ' + renderPageAiMarkdownInline(lines[index].replace(/^\s*\d+[.)]\s+/, '')) + '
  • '); + index += 1; + } + blocks.push('
      ' + ordered.join('') + '
    '); + continue; + } + var paragraph = []; + while (index < lines.length && !isBlockBoundary(lines[index])) { + paragraph.push(renderPageAiMarkdownInline(lines[index])); + index += 1; + } + if (paragraph.length) { + blocks.push('

    ' + paragraph.join('
    ') + '

    '); + } else { + index += 1; + } + } + return blocks.join('') || escapeHtml(String(content || '')); + } + + return { + textFromUnknown, + renderPageAiMarkdown, + renderPageAiMarkdownInline + }; +} diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-permission-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-permission-runtime.js new file mode 100644 index 00000000..6e6c0745 --- /dev/null +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-permission-runtime.js @@ -0,0 +1,132 @@ +export function createSidebarPageAiPermissionRuntime(context) { + const { + documentRef, + pageAiPreviewValue, + pageUiState, + renderPageAiConversation, + } = context; + const doc = documentRef || document; + + function pageAiPermissionMessage(payload, eventType) { + payload = payload && typeof payload === 'object' ? payload : {}; + var permissionId = String(payload.permissionId || payload.permission_id || payload.id || ('perm_' + Date.now())).trim(); + var toolName = String(payload.toolName || payload.tool || payload.name || payload.method || 'session/request_permission').trim(); + var args = payload.arguments || payload.args || payload.input || payload.params || payload; + var decision = String(payload.decision || payload.result || '').trim(); + if (!decision && eventType === 'permission.denied') decision = 'denied'; + if (!decision && eventType === 'permission.allowed') decision = 'allowed'; + return { + role: 'tool', + kind: 'permission', + permissionId: permissionId, + toolName: toolName, + argsSummary: pageAiPreviewValue(args), + content: decision === 'denied' ? '已自动拒绝权限请求' : (decision === 'allowed' ? '已自动允许权限请求' : '等待权限确认'), + resolved: decision === 'denied' || decision === 'allowed', + decision: decision + }; + } + + function pageAiApplyPermissionEvent(eventName, payloadText) { + var payload = null; + try { + payload = JSON.parse(payloadText || 'null'); + } catch (_) { + payload = {}; + } + var message = pageAiPermissionMessage(payload, eventName); + var existing = pageUiState.pageAiMessages.find(function(item) { + return item.kind === 'permission' && item.permissionId === message.permissionId; + }); + if (existing) { + Object.assign(existing, message); + } else { + pageUiState.pageAiMessages.push(message); + } + pageUiState.pageAiPermissionRequests = pageUiState.pageAiPermissionRequests.filter(function(item) { + return item.permissionId !== message.permissionId; + }).concat([message]).slice(-20); + if (!message.resolved) { + pageAiShowPermissionDialog(message); + } else { + pageAiHidePermissionDialog(); + } + } + + function pageAiResolvePermission(permissionId, decision) { + permissionId = String(permissionId || '').trim(); + if (!permissionId) return; + var runId = pageUiState.pageAiCurrentRunId; + if (runId) { + fetch('/api/hermes/client/runs/' + encodeURIComponent(runId) + '/resolve-permission', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ permissionId: permissionId, decision: decision }) + }).then(function(response) { + if (!response.ok) console.warn('resolve-permission 后端返回异常', response.status); + }).catch(function(err) { + console.warn('resolve-permission 请求失败', err); + }); + } else { + console.warn('resolve-permission: 无活跃 runId,只能本地更新'); + } + pageUiState.pageAiMessages.forEach(function(item) { + if (item.kind === 'permission' && item.permissionId === permissionId) { + item.resolved = true; + item.decision = decision; + item.content = decision === 'allow' ? '已允许权限请求' : '已拒绝权限请求'; + } + }); + var dialog = doc.querySelector('[data-page-ai-permission-dialog]'); + if (dialog instanceof HTMLElement) dialog.hidden = true; + renderPageAiConversation(); + } + + function pageAiHidePermissionDialog() { + var dialog = doc.querySelector('[data-page-ai-permission-dialog]'); + if (dialog instanceof HTMLElement) dialog.hidden = true; + } + + function pageAiShowPermissionDialog(message) { + if (!message || message.kind !== 'permission') return; + if (message.resolved) { + pageAiHidePermissionDialog(); + return; + } + var dialog = doc.querySelector('[data-page-ai-permission-dialog]'); + if (!(dialog instanceof HTMLElement)) { + dialog = doc.createElement('div'); + dialog.className = 'wolai-page-ai-permission-dialog'; + dialog.setAttribute('data-page-ai-permission-dialog', 'true'); + dialog.innerHTML = '' + + ''; + doc.body.appendChild(dialog); + } + var tool = dialog.querySelector('[data-page-ai-permission-tool]'); + if (tool instanceof HTMLElement) tool.textContent = message.toolName || 'session/request_permission'; + var args = dialog.querySelector('[data-page-ai-permission-args]'); + if (args instanceof HTMLElement) args.textContent = message.argsSummary || message.content || ''; + dialog.querySelectorAll('[data-page-ai-permission-dialog-action]').forEach(function(button) { + if (button instanceof HTMLButtonElement) { + button.setAttribute('data-page-ai-permission-id', message.permissionId || ''); + button.disabled = Boolean(message.resolved); + } + }); + dialog.hidden = false; + } + + return { + pageAiPermissionMessage, + pageAiApplyPermissionEvent, + pageAiResolvePermission, + pageAiHidePermissionDialog, + pageAiShowPermissionDialog + }; +} diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-profile-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-profile-runtime.js new file mode 100644 index 00000000..b92f7431 --- /dev/null +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-profile-runtime.js @@ -0,0 +1,220 @@ +export function createSidebarPageAiProfileRuntime(context) { + const { + chatOnlyProfileRegistry, + documentRef, + pageAiAgentRecord, + pageAiCurrentAgentId, + pageAiNormalizeAgentId, + pageUiState, + } = context; + const PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY = Array.isArray(chatOnlyProfileRegistry) ? chatOnlyProfileRegistry : []; + + function pageAiProviderLabel(provider) { + if (provider === 'codex') return 'Codex'; + if (provider === 'claudecode') return 'ClaudeCode'; + return 'Hermes'; + } + + function pageAiNormalizeArray(value) { + return Array.isArray(value) ? value : []; + } + + function pageAiDefaultAcpRuntimes() { + return [ + { + name: 'reasonix', + title: 'ACP · Reasonix', + description: '通过 ACP 协议直连 Reasonix(DeepSeek 缓存优先)', + model: 'deepseek-chat', + preset: 'auto' + }, + { + name: 'hermes', + title: 'ACP · Hermes', + description: '通过 ACP 协议直连 Hermes agent runtime' + } + ]; + } + + function pageAiNormalizeAcpRuntimes(runtimes) { + var byName = {}; + pageAiDefaultAcpRuntimes().forEach(function(runtime) { + byName[runtime.name] = Object.assign({}, runtime); + }); + pageAiNormalizeArray(runtimes).forEach(function(runtime) { + var name = String(runtime && runtime.name || '').trim(); + if (name !== 'reasonix' && name !== 'hermes') return; + byName[name] = Object.assign({}, byName[name] || {}, runtime, { name: name }); + }); + return ['reasonix', 'hermes'].map(function(name) { return byName[name]; }).filter(Boolean); + } + + function pageAiUnwrapUpstream(payload) { + if (payload && typeof payload === 'object' && payload.upstream) return payload.upstream; + return payload || null; + } + + function pageAiProfileValue(profile) { + if (profile && typeof profile === 'object') { + return String(profile.profileId || profile.name || profile.profile || profile.id || '').trim(); + } + return String(profile || '').trim(); + } + + function pageAiCurrentProfile() { + var active = String(pageUiState.pageAiActiveProfileName || '').trim(); + if (active) return active; + var selected = pageUiState.pageAiProfiles.find(function(profile) { + return profile && profile.active; + }); + return pageAiProfileValue(selected) || 'mnoteai'; + } + + function pageAiRunProfile() { + if (String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix') return 'reasonix'; + if (pageAiCurrentAgentId() === 'chat_only') return pageAiNormalizeChatOnlyProfileId(pageAiCurrentProfile()); + return pageAiCurrentProfile(); + } + + function pageAiMnoteToolModel() { + var doc = documentRef || document; + return String(doc.documentElement.getAttribute('data-mnote-page-ai-tool-model') || 'deepseek-v4-flash').trim() || 'deepseek-v4-flash'; + } + + function pageAiCurrentProfileRecord() { + var active = pageAiCurrentProfile(); + return pageUiState.pageAiProfiles.find(function(profile) { + return pageAiProfileValue(profile) === active; + }) || null; + } + + function pageAiChatOnlyProfileSpec(profile) { + var profileId = pageAiProfileValue(profile); + var baseProfile = String(profile && profile.baseProfile || '').trim(); + var isolatedProfile = String(profile && profile.isolatedProfile || '').trim(); + return PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY.find(function(spec) { + return spec.profileId === profileId || spec.baseProfile === baseProfile || isolatedProfile.indexOf(spec.baseProfile) >= 0; + }) || null; + } + + function pageAiDefaultChatOnlyProfileSpec() { + return PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY[0] || { profileId: 'shared_deepseek_chat', baseProfile: 'deepseek-chat', label: 'DeepSeek' }; + } + + function pageAiNormalizeChatOnlyProfileId(profileId) { + var profile = pageAiProfileRecordById(profileId) || { profileId: String(profileId || '').trim(), name: String(profileId || '').trim() }; + var spec = pageAiChatOnlyProfileSpec(profile); + return (spec || pageAiDefaultChatOnlyProfileSpec()).profileId; + } + + function pageAiProfileDisplayLabel(profile, fallback) { + var alias = String(profile && profile.alias || '').trim(); + var displayName = String(profile && (profile.displayName || profile.label || '') || '').trim(); + var name = pageAiProfileValue(profile); + return alias || displayName || fallback || name || 'default'; + } + + function pageAiProfileRecordById(profileId) { + var normalized = String(profileId || '').trim(); + if (!normalized) return null; + return pageAiNormalizeArray(pageUiState.pageAiProfiles).find(function(profile) { + return pageAiProfileValue(profile) === normalized; + }) || null; + } + + function pageAiSessionAgentFilterValue(session) { + var agentId = pageAiNormalizeAgentId(session && session.agentId); + if (agentId === 'reasonix') return 'reasonix'; + var profileId = String(session && (session.profileId || session.profile) || '').trim(); + if (agentId === 'chat_only') profileId = pageAiNormalizeChatOnlyProfileId(profileId); + return agentId + ':' + profileId; + } + + function pageAiSessionAgentLabel(session) { + var agentId = pageAiNormalizeAgentId(session && session.agentId); + if (agentId === 'reasonix') return 'Reasonix'; + var profileId = String(session && (session.profileId || session.profile) || '').trim(); + var profile = pageAiProfileRecordById(profileId) || { profileId: profileId, name: profileId }; + var chatOnlySpec = pageAiChatOnlyProfileSpec(profile); + if (agentId === 'chat_only') { + return 'ChatOnly / ' + (chatOnlySpec || pageAiDefaultChatOnlyProfileSpec()).label; + } + if (agentId === 'hermes') return 'Hermes / ' + pageAiProfileDisplayLabel(profile, profileId); + return pageAiAgentRecord(agentId).label; + } + + function pageAiSessionPreviewText(session) { + var preview = Array.isArray(session && session.messages) && session.messages.length + ? String(session.messages.slice(-1)[0].content || '') + : String(session && (session.snippet || session.preview || '暂无消息') || '暂无消息'); + preview = preview.replace(/\s+/g, ' ').trim(); + var limit = 96; + return preview.length > limit ? preview.slice(0, limit) + '…' : preview; + } + + function pageAiSessionAgentFilterOptions(rows) { + var byValue = { all: '全部 agent' }; + pageAiNormalizeArray(rows).forEach(function(session) { + var value = pageAiSessionAgentFilterValue(session); + byValue[value] = pageAiSessionAgentLabel(session); + }); + return Object.keys(byValue).map(function(value) { + return { value: value, label: byValue[value] }; + }); + } + + function pageAiFilteredHistoryRows(rows) { + var filterValue = String(pageUiState.pageAiSessionAgentFilter || 'all').trim() || 'all'; + var normalized = pageAiNormalizeArray(rows); + if (filterValue === 'all') return normalized; + return normalized.filter(function(session) { + return pageAiSessionAgentFilterValue(session) === filterValue; + }); + } + + function pageAiTimestamp(value) { + if (typeof value === 'number' && Number.isFinite(value)) return value; + if (typeof value === 'string' && value.trim()) { + var parsed = Date.parse(value); + if (Number.isFinite(parsed)) return parsed; + } + return Date.now(); + } + + function pageAiUsageSummary(usage) { + if (!usage || typeof usage !== 'object') return ''; + var used = usage.used ?? usage.contextUsed ?? usage.input_tokens ?? usage.inputTokens ?? usage.prompt_tokens ?? usage.promptTokens; + var size = usage.size ?? usage.contextSize ?? usage.total_tokens ?? usage.totalTokens; + var output = usage.output_tokens ?? usage.outputTokens ?? usage.completion_tokens ?? usage.completionTokens; + var parts = []; + if (Number.isFinite(Number(used))) parts.push('ctx ' + Number(used)); + if (Number.isFinite(Number(size)) && Number(size) > 0) parts.push('/ ' + Number(size)); + if (Number.isFinite(Number(output)) && Number(output) > 0) parts.push('out ' + Number(output)); + return parts.join(' ') || ''; + } + + return { + pageAiProviderLabel, + pageAiNormalizeArray, + pageAiDefaultAcpRuntimes, + pageAiNormalizeAcpRuntimes, + pageAiUnwrapUpstream, + pageAiProfileValue, + pageAiCurrentProfile, + pageAiRunProfile, + pageAiMnoteToolModel, + pageAiCurrentProfileRecord, + pageAiChatOnlyProfileSpec, + pageAiDefaultChatOnlyProfileSpec, + pageAiNormalizeChatOnlyProfileId, + pageAiProfileDisplayLabel, + pageAiProfileRecordById, + pageAiSessionAgentFilterValue, + pageAiSessionAgentLabel, + pageAiSessionPreviewText, + pageAiSessionAgentFilterOptions, + pageAiFilteredHistoryRows, + pageAiTimestamp, + pageAiUsageSummary + }; +} diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js new file mode 100644 index 00000000..492f612f --- /dev/null +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js @@ -0,0 +1,1128 @@ +export function createSidebarPageAiRenderRuntime(context) { + const { + contextRefRegistry, + cssEscape, + currentDocumentId, + documentRef, + escapeHtml, + localMarkdownRelativePathFromPageAiDocumentId, + pageAiAgentRecord, + pageAiAllSkillEntries, + pageAiBuildAllowedRoots, + pageAiChatOnlyProfileEntries, + pageAiChatOnlyProfileSpec, + pageAiCurrentAgentId, + pageAiCurrentProfile, + pageAiCurrentProfileRecord, + pageAiCurrentSession, + pageAiCurrentSkillSource, + pageAiCurrentSkillSourceLabel, + pageAiDefaultChatOnlyProfileSpec, + pageAiEditorTargetCandidates, + pageAiEnsureContextRefState, + pageAiFilteredHistoryRows, + pageAiHideHermesBuiltinSkills, + pageAiHermesProfileEntries, + pageAiHermesSettingsUrl, + pageAiMnoteToolModel, + pageAiNormalizeAcpRuntimes, + pageAiNormalizeArray, + pageAiNormalizeSkillSource, + pageAiProfileDisplayLabel, + pageAiProfileValue, + pageAiProviderLabel, + pageAiReasonixMemoryEnabled, + pageAiRunProfile, + pageAiSessionAgentFilterOptions, + pageAiSessionAgentLabel, + pageAiSessionAgentFilterValue, + pageAiSessionPreviewText, + pageAiSessionStorageLabel, + pageAiSkillEnabled, + pageAiSkillGroupCollapsed, + pageAiSkillListEntries, + pageAiSkillOriginLabel, + pageAiSkillSourceOptions, + pageAiSkillSourceParts, + pageAiToggleableSkillEntries, + pageAiUsageSummary, + pageUiState, + renderPageAiMarkdown, + searchText, + currentPageAiEditorTarget, + currentPageAiSelectedText, + } = context; + const PAGE_AI_CONTEXT_REF_REGISTRY = Array.isArray(contextRefRegistry) ? contextRefRegistry : []; + + function pageAiTargetLabel(target) { + var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {}; + var kind = String(target && (target.resourceKind || target.editorKind) || workspacePath.resourceKind || '').trim(); + var title = String(target && target.title || '').trim(); + var relativePath = String(workspacePath.relativePath || target && target.path || '').trim(); + if (title) return title; + if (relativePath) return relativePath.split('/').filter(Boolean).pop() || relativePath; + if (kind === 'mindmap') return '思维导图'; + if (kind === 'only_office') return 'Office 资源'; + return '当前页'; + } + + function pageAiTargetDetail(target) { + var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {}; + var kind = String(target && (target.resourceKind || target.editorKind) || workspacePath.resourceKind || '').trim(); + var pane = String(target && target.paneRole || '').trim(); + var relativePath = String(workspacePath.relativePath || target && target.path || '').trim(); + return [kind || 'page', pane, relativePath].filter(Boolean).join(' · '); + } + + function pageAiContextRefLabel(kind) { + var record = PAGE_AI_CONTEXT_REF_REGISTRY.find(function(ref) { return ref.id === kind; }); + return record ? record.label : kind; + } + + function pageAiContextButtonSummary() { + var selected = pageAiEnsureContextRefState(); + var labels = PAGE_AI_CONTEXT_REF_REGISTRY + .filter(function(ref) { return selected[ref.id] === true; }) + .map(function(ref) { return ref.label; }); + if (!labels.length) return '选择上下文'; + var head = labels.slice(0, 2).join(' + '); + return labels.length > 2 ? head + ' +' + String(labels.length - 2) : head; + } + + function pageAiContextRefDetail(kind) { + var documentId = currentDocumentId(); + var relativePath = localMarkdownRelativePathFromPageAiDocumentId(documentId); + if (kind === 'current_page') return relativePath || documentId || '当前页面'; + if (kind === 'selection') return currentPageAiSelectedText() ? '当前选区可用' : '当前没有选区'; + if (kind === 'active_editor') { + var target = currentPageAiEditorTarget(); + return String(target && (target.title || target.documentId) || relativePath || '当前打开资源'); + } + if (kind === 'file') return relativePath || '当前文件'; + if (kind === 'folder') { + var roots = pageAiBuildAllowedRoots(); + if (!roots.length) return '需要授权后可用'; + return roots.map(function(root) { return root.rootUri.replace(/^file:\/\//, ''); }).filter(Boolean)[0] || '已授权文件夹'; + } + if (kind === 'changed_files') return '本次 run 后可用于追问'; + return ''; + } + + function pageAiContextRefDisabled(kind) { + if (kind === 'selection') return !currentPageAiSelectedText(); + return false; + } + + function pageAiSuggestions() { + var title = searchText(documentRef.querySelector('[data-page-title-current="true"]')?.textContent) || '当前页面'; + return [ + '帮我总结《' + title + '》当前内容', + '把当前页面改写得更简洁一些', + '提炼当前页的关键待办和行动项', + '基于当前页内容生成一个三段式摘要' + ]; + } + + function pageAiCurrentAgentSelectionLabel() { + var agentId = pageAiCurrentAgentId(); + if (agentId === 'reasonix') return 'Reasonix'; + var profile = pageAiCurrentProfileRecord(); + var chatOnlySpec = pageAiChatOnlyProfileSpec(profile || pageAiCurrentProfile()); + if (agentId === 'chat_only') { + return 'ChatOnly / ' + (chatOnlySpec || pageAiDefaultChatOnlyProfileSpec()).label; + } + if (chatOnlySpec) { + return 'ChatOnly / ' + chatOnlySpec.label; + } + if (agentId === 'hermes') return 'Hermes / ' + pageAiProfileDisplayLabel(profile, pageAiCurrentProfile()); + return pageAiAgentRecord(agentId).label; + } + + function pageAiRenderAgentProfileOption(agentId, profile, label, detail) { + var profileId = pageAiProfileValue(profile); + var active = pageAiCurrentAgentId() === agentId && profileId === pageAiCurrentProfile(); + return '' + + ''; + } + + function pageAiCurrentModelLabel() { + var profile = pageAiCurrentProfileRecord(); + var toolModel = pageAiMnoteToolModel(); + if (!profile) return 'tool: ' + toolModel; + var model = String(profile.model || '').trim(); + var gateway = String(profile.gateway || '').trim(); + var profileLabel = [model, gateway].filter(Boolean).join(' · ') || '由 Hermes 决定'; + return 'tool: ' + toolModel + ' · profile: ' + profileLabel; + } + + function pageAiFormatChangedFiles(files) { + return pageAiNormalizeArray(files).map(function(file) { + var path = String(file && file.path || '').trim(); + var changeType = String(file && file.changeType || file.change_type || 'modified').trim(); + var summary = String(file && file.summary || '').trim(); + var version = String(file && (file.version || file.revision || '') || '').trim(); + var hashBefore = String(file && file.hashBefore || '').trim(); + var hashAfter = String(file && file.hashAfter || '').trim(); + if (!version && (hashBefore || hashAfter)) version = 'hash ' + [hashBefore || '0', hashAfter || '0'].join('→'); + var modifiedBefore = Number(file && file.modifiedBeforeMs || 0) || 0; + var modifiedAfter = Number(file && file.modifiedAfterMs || 0) || 0; + if (!version && (modifiedBefore || modifiedAfter)) version = 'mtime ' + [String(modifiedBefore), String(modifiedAfter)].join('→'); + var actor = [file && file.agentKind, file && file.actorType, file && file.actorId].map(function(value) { + return String(value || '').trim(); + }).filter(Boolean).join('/'); + return [changeType, path, version, summary, actor].filter(Boolean).join(' · '); + }).filter(Boolean).join('\n'); + } + + function pageAiRunStatusLabel(status) { + if (status === 'queued') return '排队中'; + if (status === 'running') return '运行中'; + if (status === 'tool_calling') return '调用工具'; + if (status === 'completed') return '已完成'; + if (status === 'failed') return '失败'; + if (status === 'aborted') return '已停止'; + return '空闲'; + } + + function pageAiMemoryFileLabel(section) { + if (section === 'soul') return 'SOUL.md'; + if (section === 'user') return 'USER.md'; + return 'MEMORY.md'; + } + + function pageAiContextScopeLabel(scope) { + if (scope === 'selection') return '当前选区'; + if (scope === 'block') return '当前块'; + if (scope === 'options') return '页面设置'; + return '当前页'; + } + + function pageAiFilteredSkillEntries() { + var query = String(pageUiState.pageAiSkillQuery || '').trim().toLowerCase(); + var parts = pageAiSkillSourceParts(pageAiCurrentSkillSource()); + var entries = pageAiToggleableSkillEntries(parts.group, parts.profile); + if (!entries.length) { + entries = pageAiSkillListEntries().map(function(skill) { + return Object.assign({}, skill, { + group: parts.group, + profile: parts.profile || '' + }); + }); + } + return entries.filter(function(skill) { + if (parts.group === 'hermes' && pageAiHideHermesBuiltinSkills(parts.profile) && skill.builtin === true) return false; + if (!query) return true; + return String(skill.name || skill.id || '').toLowerCase().indexOf(query) >= 0 + || String(skill.title || '').toLowerCase().indexOf(query) >= 0 + || String(skill.description || '').toLowerCase().indexOf(query) >= 0 + || String(skill.category || '').toLowerCase().indexOf(query) >= 0 + || pageAiSkillOriginLabel(skill).toLowerCase().indexOf(query) >= 0; + }); + } + + function renderPageAiProviderButtons() { + var drawer = ensurePageAiDrawer(); + var isAcp = pageUiState.pageAiAcpRuntime !== ''; + drawer.querySelectorAll('[data-page-ai-provider]').forEach(function(button) { + var provider = button.getAttribute('data-page-ai-provider') || 'hermes'; + var active = provider === pageUiState.pageAiProvider; + button.classList.toggle('is-active', active); + button.setAttribute('aria-pressed', active ? 'true' : 'false'); + }); + var providerNode = drawer.querySelector('.wolai-page-ai-subtitle span:first-child'); + if (providerNode instanceof HTMLElement) { + providerNode.textContent = pageAiAgentRecord(pageAiCurrentAgentId()).label; + } + } + + function renderPageAiControls() { + var drawer = ensurePageAiDrawer(); + var isAcp = pageUiState.pageAiAcpRuntime !== ''; + var activeAgentId = pageAiCurrentAgentId(); + var activeProfile = pageAiCurrentProfile(); + drawer.setAttribute('data-page-ai-page', pageUiState.pageAiPage || 'chat'); + drawer.setAttribute('data-page-ai-active-agent-id', activeAgentId); + drawer.setAttribute('data-mnote-acp-runtime', pageUiState.pageAiAcpRuntime || 'reasonix'); + documentRef.documentElement.setAttribute('data-mnote-page-ai-agent-id', activeAgentId); + var agentButton = drawer.querySelector('[data-page-ai-agent-button]'); + var agentPopoverId = 'mnote-page-ai-agent-popover'; + var activeAgentLabel = pageAiCurrentAgentSelectionLabel(); + if (agentButton instanceof HTMLElement) { + agentButton.textContent = 'AI'; + agentButton.setAttribute('aria-expanded', pageUiState.pageAiAgentPopoverOpen ? 'true' : 'false'); + agentButton.setAttribute('aria-controls', agentPopoverId); + agentButton.setAttribute('data-page-ai-agent-summary', activeAgentLabel); + agentButton.setAttribute('aria-label', 'Agent:' + activeAgentLabel); + agentButton.setAttribute('title', 'Agent:' + activeAgentLabel); + } + var agentPopover = drawer.querySelector('[data-page-ai-agent-popover]'); + if (agentPopover instanceof HTMLElement) { + agentPopover.id = agentPopoverId; + agentPopover.hidden = !pageUiState.pageAiAgentPopoverOpen; + var chatOnlyOptions = pageAiChatOnlyProfileEntries().map(function(profile) { + var spec = pageAiChatOnlyProfileSpec(profile); + var label = profile.menuLabel || (spec && spec.label) || pageAiProfileDisplayLabel(profile, ''); + return pageAiRenderAgentProfileOption('chat_only', profile, label, '网页问答 · 不申请文件写权限'); + }).join(''); + var hermesOptions = pageAiHermesProfileEntries().map(function(profile) { + var profileId = pageAiProfileValue(profile); + var scope = profile.kind === 'shared' + ? (profile.readonly ? '共享 · 只读配置' : '共享') + : (profile.kind === 'personal' ? '个人 profile' : 'Hermes profile'); + return pageAiRenderAgentProfileOption('hermes', profile, pageAiProfileDisplayLabel(profile, profileId), scope); + }).join(''); + var reasonixActive = activeAgentId === 'reasonix'; + agentPopover.innerHTML = '' + + '
    ' + + '选择 Agent' + + '' + + '
    ' + + '
    ' + + '
    ChatOnly
    ' + + '
    ' + chatOnlyOptions + '
    ' + + '
    ' + + '
    ' + + '
    Hermes
    ' + + '
    ' + hermesOptions + '
    ' + + '
    ' + + '
    ' + + '
    Reasonix
    ' + + '
    ' + + '' + + '
    ' + + '
    '; + } + var agentChip = drawer.querySelector('[data-page-ai-agent-chip]'); + if (agentChip instanceof HTMLElement) { + agentChip.textContent = activeAgentLabel; + agentChip.setAttribute('title', '当前 Agent:' + activeAgentLabel); + } + var contextButton = drawer.querySelector('[data-page-ai-context-button]'); + var contextPopoverId = 'mnote-page-ai-context-popover'; + if (contextButton instanceof HTMLElement) { + var summary = pageAiContextButtonSummary(); + contextButton.textContent = '⇅'; + contextButton.setAttribute('aria-expanded', pageUiState.pageAiContextPopoverOpen ? 'true' : 'false'); + contextButton.setAttribute('aria-controls', contextPopoverId); + contextButton.setAttribute('data-page-ai-context-summary', summary); + contextButton.setAttribute('aria-label', '上下文:' + summary); + contextButton.setAttribute('title', '上下文:' + summary); + } + var contextPopover = drawer.querySelector('[data-page-ai-context-popover]'); + if (contextPopover instanceof HTMLElement) { + var selectedRefs = pageAiEnsureContextRefState(); + contextPopover.id = contextPopoverId; + contextPopover.hidden = !pageUiState.pageAiContextPopoverOpen; + contextPopover.innerHTML = '' + + '
    ' + + '发送给 AI 的上下文' + + '' + + '
    ' + + '
    ' + + PAGE_AI_CONTEXT_REF_REGISTRY.map(function(ref) { + var checked = selectedRefs[ref.id] === true; + var disabled = pageAiContextRefDisabled(ref.id); + return '' + + ''; + }).join('') + + '
    ' + + '
    ' + + '授权区域' + + '' + escapeHtml(pageAiBuildAllowedRoots().length ? pageAiBuildAllowedRoots().map(function(root) { return root.permission + ' · ' + root.rootUri.replace(/^file:\/\//, ''); }).join(' / ') : '未选择授权区域') + '' + + '
    '; + } + var targetButton = drawer.querySelector('[data-page-ai-target-button]'); + var targetPopover = drawer.querySelector('[data-page-ai-target-popover]'); + var targetChip = drawer.querySelector('[data-page-ai-target-chip]'); + var targetPopoverId = 'mnote-page-ai-target-popover'; + var targetCandidates = pageAiEditorTargetCandidates(); + var activeTarget = currentPageAiEditorTarget(); + var activeTargetLabel = pageAiTargetLabel(activeTarget); + if (targetButton instanceof HTMLElement) { + targetButton.textContent = '◎'; + targetButton.setAttribute('aria-expanded', pageUiState.pageAiTargetPopoverOpen ? 'true' : 'false'); + targetButton.setAttribute('aria-controls', targetPopoverId); + targetButton.setAttribute('aria-label', '目标:' + activeTargetLabel); + targetButton.setAttribute('title', '目标:' + activeTargetLabel); + } + if (targetChip instanceof HTMLElement) { + targetChip.textContent = activeTargetLabel; + targetChip.setAttribute('title', '当前目标:' + activeTargetLabel + ' · ' + pageAiTargetDetail(activeTarget)); + } + if (targetPopover instanceof HTMLElement) { + targetPopover.id = targetPopoverId; + targetPopover.hidden = !pageUiState.pageAiTargetPopoverOpen; + targetPopover.innerHTML = '' + + '
    ' + + '选择写入目标' + + '' + + '
    ' + + '
    ' + + targetCandidates.map(function(target) { + var targetId = String(target && target.targetId || '').trim(); + var active = targetId && targetId === String(activeTarget && activeTarget.targetId || '').trim(); + return '' + + ''; + }).join('') + + '
    '; + } + var allowedRootsNode = drawer.querySelector('[data-page-ai-allowed-roots]'); + if (allowedRootsNode instanceof HTMLElement) { + var allowedRoots = pageAiBuildAllowedRoots(); + if (!allowedRoots.length) { + var errorText = String(pageUiState.pageAiAllowedRootsError || '').trim(); + allowedRootsNode.innerHTML = '' + escapeHtml(errorText || '未选择授权区域') + ''; + } else { + allowedRootsNode.innerHTML = allowedRoots.map(function(root) { + var label = root.rootUri.replace(/^file:\/\//, '') || root.rootUri; + return '' + escapeHtml(root.permission + ' · ' + label) + ''; + }).join(''); + } + } + // Populate ACP runtime dropdown + var acpSelect = drawer.querySelector('[data-page-ai-acp-runtime]'); + if (acpSelect instanceof HTMLSelectElement) { + var runtimes = pageUiState.pageAiAcpRuntimes.length ? pageUiState.pageAiAcpRuntimes : pageAiNormalizeAcpRuntimes([]); + acpSelect.innerHTML = runtimes.map(function(rt) { + return ''; + }).join(''); + acpSelect.value = pageUiState.pageAiAcpRuntime || 'reasonix'; + } + // Show/hide Hermes-specific profile select + var profileLabel = drawer.querySelector('[data-page-ai-hermes-profile]'); + if (profileLabel instanceof HTMLElement) { + profileLabel.style.display = pageUiState.pageAiAcpRuntime === 'reasonix' ? 'none' : ''; + } + // When ACP is selected, populate agent panel with ACP runtime info + var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]'); + if (agentPanel instanceof HTMLElement) { + agentPanel.innerHTML = '' + + '
    ' + + '
    授权区域
    ' + escapeHtml(pageAiBuildAllowedRoots().length ? 'SQLite directory_grants' : '需要授权') + '
    ' + + '
    ' + + '
    ' + + '
    默认上下文
    ContextRefs
    ' + + '
    ' + + '
    ' + + '
    审计
    changed files 默认摘要
    ' + + '
    '; + } + var profileSummary = drawer.querySelector('[data-page-ai-profile-summary]'); + if (profileSummary instanceof HTMLElement) profileSummary.textContent = '上下文可选'; + var profileSummary = drawer.querySelector('[data-page-ai-profile-summary]'); + if (profileSummary instanceof HTMLElement) profileSummary.textContent = '上下文可选'; + drawer.querySelectorAll('[data-page-ai-profile-select]').forEach(function(profileSelect) { + if (!(profileSelect instanceof HTMLSelectElement)) return; + var profiles = pageUiState.pageAiProfiles.length ? pageUiState.pageAiProfiles : [{ name: activeProfile, active: true }]; + profileSelect.innerHTML = profiles.map(function(profile) { + var name = pageAiProfileValue(profile) || 'default'; + var model = [profile.model, profile.gateway].filter(Boolean).join(' / '); + var scope = profile.kind === 'shared' + ? (profile.readonly ? 'shared · readonly' : 'shared · admin') + : (profile.kind === 'personal' ? 'personal · 可配置' : ''); + var label = [profile.alias || name, scope, model].filter(Boolean).join(' · '); + return ''; + }).join(''); + profileSelect.value = activeProfile; + }); + var runStatus = drawer.querySelector('[data-page-ai-run-status]'); + if (runStatus instanceof HTMLElement) { + var queueSuffix = pageUiState.pageAiQueueLength > 0 ? ' · 队列 ' + pageUiState.pageAiQueueLength : ''; + runStatus.textContent = pageAiRunStatusLabel(pageUiState.pageAiRunStatus) + queueSuffix; + } + var stopButton = drawer.querySelector('[data-page-ai-action="stop-run"]'); + if (stopButton instanceof HTMLButtonElement) { + var canStop = ['queued', 'running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0 && pageUiState.pageAiCurrentRunId; + stopButton.disabled = !canStop; + stopButton.setAttribute('aria-disabled', canStop ? 'false' : 'true'); + } + var settingsLink = drawer.querySelector('[data-page-ai-action="open-hermes-settings"]'); + if (settingsLink instanceof HTMLButtonElement) { + settingsLink.disabled = !pageAiHermesSettingsUrl(); + settingsLink.title = pageAiHermesSettingsUrl() ? '打开 Hermes 设置' : '未配置 MNOTE_WEB_HERMES_UPSTREAM_URL'; + } + var queueList = drawer.querySelector('[data-page-ai-queue-list]'); + if (queueList instanceof HTMLElement) { + if (!pageUiState.pageAiQueuedItems.length) { + queueList.innerHTML = '
    暂无排队项。
    '; + } else { + queueList.innerHTML = pageUiState.pageAiQueuedItems.map(function(item, index) { + var label = '队列 ' + (index + 1); + return '' + + '
    ' + + '
    ' + escapeHtml(label) + '
    ' + escapeHtml(item.queueId) + '
    ' + + '' + + '
    '; + }).join(''); + } + } + var sessionNode = drawer.querySelector('[data-page-ai-session-status]'); + if (sessionNode instanceof HTMLElement) { + var session = pageAiCurrentSession(); + var usageText = session && session.usage ? pageAiUsageSummary(session.usage) : ''; + sessionNode.textContent = session && session.id + ? [String(session.title || '当前页问答'), pageAiSessionStorageLabel(session), usageText].filter(Boolean).join(' · ') + : '等待 AI session'; + } + var modelNode = drawer.querySelector('[data-page-ai-model-status]'); + if (modelNode instanceof HTMLElement) modelNode.textContent = pageAiBuildAllowedRoots().length ? '已授权' : '需授权'; + var scopeSelect = drawer.querySelector('[data-page-ai-context-scope]'); + if (scopeSelect instanceof HTMLSelectElement) scopeSelect.value = pageUiState.pageAiContextScope; + var scopeLabel = drawer.querySelector('[data-page-ai-context-scope-label]'); + if (scopeLabel instanceof HTMLElement) scopeLabel.textContent = pageAiContextScopeLabel(pageUiState.pageAiContextScope); + drawer.querySelectorAll('[data-page-ai-tab]').forEach(function(button) { + var target = button.getAttribute('data-page-ai-tab') || 'chat'; + var active = target === pageUiState.pageAiPage; + button.classList.toggle('is-active', active); + button.setAttribute('aria-selected', active ? 'true' : 'false'); + }); + drawer.querySelectorAll('[data-page-ai-panel]').forEach(function(panel) { + if (panel instanceof HTMLElement) { + panel.hidden = panel.getAttribute('data-page-ai-panel') !== pageUiState.pageAiPage; + } + }); + var profileError = drawer.querySelector('[data-page-ai-profile-error]'); + if (profileError instanceof HTMLElement) { + profileError.textContent = pageUiState.pageAiProfileError || ''; + profileError.hidden = !pageUiState.pageAiProfileError; + } + var memoryError = drawer.querySelector('[data-page-ai-memory-error]'); + if (memoryError instanceof HTMLElement) { + var memoryErrorText = pageUiState.pageAiProfileMemoryError || ''; + if (memoryErrorText && memoryErrorText === pageUiState.pageAiProfileError) memoryErrorText = ''; + memoryError.textContent = memoryErrorText; + memoryError.hidden = !memoryErrorText; + } + var hermesMemoryPanel = drawer.querySelector('[data-page-ai-hermes-panel]'); + if (hermesMemoryPanel instanceof HTMLElement) { + hermesMemoryPanel.innerHTML = ['soul', 'user', 'memory'].map(function(section) { + var label = pageAiMemoryFileLabel(section); + var value = pageUiState.pageAiProfileMemoryDrafts[section] || ''; + return '' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + escapeHtml(label) + '
    ' + + '
    保存到 Hermes profile: ' + escapeHtml(activeProfile) + '
    ' + + '
    ' + + '' + + '
    ' + + '' + + '
    '; + }).join(''); + } + var skillError = drawer.querySelector('[data-page-ai-skill-error]'); + if (skillError instanceof HTMLElement) { + skillError.textContent = pageUiState.pageAiSkillError || ''; + skillError.hidden = !pageUiState.pageAiSkillError; + } + var sessionError = drawer.querySelector('[data-page-ai-session-error]'); + if (sessionError instanceof HTMLElement) { + sessionError.textContent = pageUiState.pageAiSessionError || ''; + sessionError.hidden = !pageUiState.pageAiSessionError; + } + var sessionSearch = drawer.querySelector('[data-page-ai-session-search]'); + if (sessionSearch instanceof HTMLInputElement && documentRef.activeElement !== sessionSearch) { + sessionSearch.value = pageUiState.pageAiSessionSearchQuery || ''; + } + var sessionAgentFilter = drawer.querySelector('[data-page-ai-session-agent-filter]'); + if (sessionAgentFilter instanceof HTMLSelectElement) { + var historyRowsForFilter = pageUiState.pageAiSessionSearchResults.length + ? pageUiState.pageAiSessionSearchResults + : pageUiState.pageAiSessions; + var options = pageAiSessionAgentFilterOptions(historyRowsForFilter); + var activeFilter = String(pageUiState.pageAiSessionAgentFilter || 'all').trim() || 'all'; + if (!options.some(function(option) { return option.value === activeFilter; })) activeFilter = 'all'; + sessionAgentFilter.innerHTML = options.map(function(option) { + return ''; + }).join(''); + sessionAgentFilter.value = activeFilter; + pageUiState.pageAiSessionAgentFilter = activeFilter; + } + var skillSearch = drawer.querySelector('[data-page-ai-skill-search]'); + if (skillSearch instanceof HTMLInputElement && documentRef.activeElement !== skillSearch) { + skillSearch.value = pageUiState.pageAiSkillQuery; + } + var skillSourceSelect = drawer.querySelector('[data-page-ai-skill-source-select]'); + if (skillSourceSelect instanceof HTMLSelectElement) { + var activeSkillSource = pageAiCurrentSkillSource(); + skillSourceSelect.innerHTML = pageAiSkillSourceOptions().map(function(option) { + return ''; + }).join(''); + skillSourceSelect.value = activeSkillSource; + } + var hermesBuiltinToggle = drawer.querySelector('[data-page-ai-hide-hermes-builtin]'); + if (hermesBuiltinToggle instanceof HTMLInputElement) { + var skillSourceParts = pageAiSkillSourceParts(pageAiCurrentSkillSource()); + var showHermesFilter = skillSourceParts.group === 'hermes'; + var hermesFilterLabel = hermesBuiltinToggle.closest('.wolai-page-ai-skill-filter-toggle'); + if (hermesFilterLabel instanceof HTMLElement) hermesFilterLabel.hidden = !showHermesFilter; + hermesBuiltinToggle.checked = showHermesFilter && pageAiHideHermesBuiltinSkills(skillSourceParts.profile); + } + var skillList = drawer.querySelector('[data-page-ai-skill-list]'); + if (skillList instanceof HTMLElement) { + var skills = pageAiFilteredSkillEntries(); + if (!skills.length) { + skillList.innerHTML = '
    没有匹配的技能。
    '; + } else { + var sourceParts = pageAiSkillSourceParts(pageAiCurrentSkillSource()); + var group = sourceParts.group; + var headingExtra = group === 'hermes' ? ' · ' + sourceParts.profile : ''; + var collapsed = pageAiSkillGroupCollapsed(pageAiCurrentSkillSource()); + skillList.innerHTML = '' + + '
    ' + + '' + + (collapsed ? '' : skills.map(function(skill) { + var sourceText = pageAiSkillOriginLabel(skill) + + (skill.configScope ? ' · ' + skill.configScope : '') + + (skill.readOnly ? ' · 只读' : '') + + (skill.modified ? ' · modified' : ''); + var description = String(skill.description || '').trim(); + var hasDescription = description && description !== '---' && description !== '无描述'; + var displayName = String(skill.title || skill.name || skill.id || '').trim(); + var disabled = skill.toggleable === false || skill.readOnly === true; + return '' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + escapeHtml(displayName) + '
    ' + + '
    ' + escapeHtml(sourceText) + '
    ' + + '
    ' + + (hasDescription ? '
    ' + escapeHtml(description) + '
    ' : '') + + '
    ' + + '' + + '
    '; + }).join('')) + + '
    '; + } + } + var toolsList = drawer.querySelector('[data-page-ai-tool-list]'); + if (toolsList instanceof HTMLElement) { + var tools = pageAiNormalizeArray(pageUiState.pageAiTools); + if (!tools.length) { + toolsList.innerHTML = '
    尚未读取到 mnote tool manifest。
    '; + } else { + toolsList.innerHTML = tools.map(function(tool) { + return '' + + '
    ' + + '
    ' + + '
    ' + escapeHtml(tool.name) + '
    ' + + '
    ' + escapeHtml(tool.scope || tool.kind || 'mnote') + ' · ' + escapeHtml(tool.status || 'available') + '
    ' + + (tool.unavailableReason ? '
    ' + escapeHtml(tool.unavailableReason) + '
    ' : '') + + '
    ' + + '' + + '
    '; + }).join(''); + } + } + var gatewayError = drawer.querySelector('[data-page-ai-gateway-error]'); + if (gatewayError instanceof HTMLElement) { + gatewayError.textContent = pageUiState.pageAiGatewayHealthError || ''; + gatewayError.hidden = !pageUiState.pageAiGatewayHealthError; + } + var gatewayStatusNode = drawer.querySelector('[data-page-ai-gateway-status]'); + var gatewayDetail = drawer.querySelector('[data-page-ai-gateway-detail]'); + var gatewayHealth = pageUiState.pageAiGatewayHealth; + if (gatewayStatusNode instanceof HTMLElement) { + if (!gatewayHealth) { + gatewayStatusNode.textContent = '未检查'; + } else { + var gateway = gatewayHealth.gateway || {}; + var profile = gatewayHealth.profile || {}; + gatewayStatusNode.textContent = gatewayHealth.ok ? '可用' : '需要设置'; + if (gateway.status) gatewayStatusNode.textContent += ' · ' + gateway.status; + if (profile.name) gatewayStatusNode.textContent += ' · ' + profile.name; + } + } + if (gatewayDetail instanceof HTMLElement) { + if (!gatewayHealth) { + gatewayDetail.innerHTML = '
    打开高级后会检查 agent runtime 与当前 profile。
    '; + } else { + var gatewayInfo = gatewayHealth.gateway || {}; + var profileInfo = gatewayHealth.profile || {}; + var suggestionText = pageAiNormalizeArray(gatewayHealth.suggestions).join(';'); + gatewayDetail.innerHTML = '' + + '
    ' + + '
    ' + escapeHtml(profileInfo.name || activeProfile) + '
    ' + + '
    model.default: ' + escapeHtml(profileInfo.modelDefault || '未设置') + '
    ' + + '
    provider: ' + escapeHtml(profileInfo.provider || '未设置') + ' · API key: ' + escapeHtml(profileInfo.apiKeyConfigured ? '已配置' : '未检测到') + '
    ' + + '
    ' + + '
    ' + + '
    ' + escapeHtml(gatewayInfo.upstream || '未配置 upstream') + '
    ' + + '
    gateway: ' + escapeHtml(gatewayInfo.status || 'unknown') + (gatewayInfo.httpStatus ? ' · HTTP ' + escapeHtml(gatewayInfo.httpStatus) : '') + '
    ' + + (suggestionText ? '
    ' + escapeHtml(suggestionText) + '
    ' : '') + + '
    '; + } + } + var toolError = drawer.querySelector('[data-page-ai-tool-error]'); + if (toolError instanceof HTMLElement) { + toolError.textContent = pageUiState.pageAiToolsError || ''; + toolError.hidden = !pageUiState.pageAiToolsError; + } + var lastTool = drawer.querySelector('[data-page-ai-last-tool]'); + if (lastTool instanceof HTMLElement) { + var call = pageUiState.pageAiLastToolCall; + lastTool.textContent = call + ? [call.event, call.name, call.runId, call.traceId || call.auditId].filter(Boolean).join(' · ') + : '暂无 tool call'; + } + var reasonixPanel = drawer.querySelector('[data-page-ai-reasonix-panel]'); + if (reasonixPanel instanceof HTMLElement) { + reasonixPanel.innerHTML = '' + + '
    ' + + '
    ' + + '
    Reasonix 专属设置
    ACP runtime / memory
    ' + + '' + + '
    ' + + '
    ' + escapeHtml(pageAiReasonixMemoryEnabled() ? '当前用户已开启 Reasonix global/project memory。' : '默认关闭 Reasonix memory,避免跨用户偏好混入。') + '
    ' + + '
    '; + } + var chatOnlyPanel = drawer.querySelector('[data-page-ai-chat-only-panel]'); + if (chatOnlyPanel instanceof HTMLElement) { + chatOnlyPanel.innerHTML = '
    Chat-only
    默认不申请文件写权限
    '; + } + } + + function humanizePageAiResponse(rawText, promptText) { + var providerLabel = pageAiProviderLabel(pageUiState.pageAiProvider); + var text = String(rawText || '').trim(); + if (!text) { + return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”,但这次没有返回可读内容。'; + } + if (text.startsWith('{')) { + try { + var payload = JSON.parse(text); + var operation = payload && payload.operation ? payload.operation : {}; + var normalized = operation && operation.normalized_input ? operation.normalized_input : {}; + var args = normalized && normalized.args ? normalized.args : {}; + var documentId = searchText(args.documentId || args.pageId || currentDocumentId()); + var toolName = searchText(operation.tool_name || normalized.toolName || 'doc_get'); + return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”。当前已通过 Hermes 调用 mnote plugin 工具,目标页面是 ' + (documentId || '当前页面') + ',本次选择的工具是 ' + toolName + '。如果你继续追问,我会沿当前页面上下文继续回复。'; + } catch (_) { + return providerLabel + ' 已返回结果,但当前结果是结构化文本,已为你保留原始内容。'; + } + } + return text; + } + + function ensurePageAiDrawer() { + var existing = documentRef.querySelector('[data-testid="wolai-page-ai-drawer"]'); + if (existing instanceof HTMLElement) return existing; + var drawer = documentRef.createElement('aside'); + drawer.className = 'wolai-page-ai-drawer'; + drawer.setAttribute('data-testid', 'wolai-page-ai-drawer'); + drawer.setAttribute('data-mnote-surface', 'page-ai'); + drawer.hidden = true; + drawer.innerHTML = '' + + ''; + documentRef.body.appendChild(drawer); + return drawer; + } + + function renderPageAiSuggestions() { + var drawer = ensurePageAiDrawer(); + var list = drawer.querySelector('[data-page-ai-panel="chat"] [data-page-ai-suggestion-list]'); + if (!(list instanceof HTMLElement)) return; + var container = list.closest('.wolai-page-ai-suggestions'); + if (container instanceof HTMLElement) { + container.hidden = pageUiState.pageAiPage !== 'chat' || pageUiState.pageAiMessages.length > 0; + } + var suggestions = pageAiSuggestions(); + var offset = pageUiState.pageAiSuggestionIndex % suggestions.length; + var ordered = suggestions.slice(offset).concat(suggestions.slice(0, offset)).slice(0, 3); + list.innerHTML = ordered.map(function(text) { + return ''; + }).join(''); + } + + function renderPageAiConversation() { + var drawer = ensurePageAiDrawer(); + renderPageAiSuggestions(); + var conversation = drawer.querySelector('[data-page-ai-panel="' + cssEscape(pageUiState.pageAiPage || 'chat') + '"] [data-page-ai-conversation]'); + if (!(conversation instanceof HTMLElement)) return; + if (pageUiState.pageAiPage === 'history') { + var sourceRows = pageUiState.pageAiSessionSearchResults.length + ? pageUiState.pageAiSessionSearchResults + : pageUiState.pageAiSessions; + var historyRows = pageAiFilteredHistoryRows(sourceRows); + if (!historyRows.length) { + conversation.innerHTML = '
    没有匹配的历史会话。
    '; + return; + } + conversation.innerHTML = historyRows.map(function(session) { + var preview = pageAiSessionPreviewText(session); + var active = session.id === pageUiState.pageAiActiveSessionId; + var usage = pageAiUsageSummary(session.usage); + var agentLabel = pageAiSessionAgentLabel(session); + var meta = [agentLabel, pageAiSessionStorageLabel(session), session.permissionLevel, session.shareId, session.status, usage].filter(Boolean).join(' · '); + return '' + + '
    ' + + '' + + '
    ' + + '' + + '' + + '' + + '
    ' + + '
    '; + }).join(''); + return; + } + if (!pageUiState.pageAiMessages.length) { + conversation.innerHTML = '
    围绕当前页面提问,我会优先使用当前页内容、页面结构和已保存设置。
    '; + return; + } + conversation.innerHTML = pageUiState.pageAiMessages.map(function(item) { + var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI'); + if (item.role === 'tool') { + var statusLabel = item.status === 'completed' ? '完成' : (item.status === 'failed' ? '失败' : '运行中'); + var locationRows = Array.isArray(item.locations) && item.locations.length + ? '
    位置:' + item.locations.map(function(loc, idx) { + return '' + + '' + escapeHtml(loc) + '' + + '' + + ''; + }).join('') + '
    ' + : ''; + var detailRows = [ + locationRows, + item.argsSummary ? '
    参数 ' + escapeHtml(item.argsSummary) + '
    ' : '', + item.resultSummary ? '
    结果 ' + escapeHtml(item.resultSummary) + '
    ' : '', + item.changedFiles && item.changedFiles.length ? '
    ' + escapeHtml(pageAiFormatChangedFiles(item.changedFiles)) + '
    ' : '', + (item.traceId || item.auditId) ? '
    ' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '
    ' : '' + ].filter(Boolean).join(''); + return '' + + '
    ' + + '
    ' + escapeHtml(roleLabel) + '
    ' + + '
    ' + + '
    ' + + '' + + '' + escapeHtml(item.toolName || item.content || 'tool') + '' + + '' + escapeHtml([statusLabel, item.toolKind, item.toolCallId].filter(Boolean).join(' · ')) + '' + + '' + + (detailRows || '
    暂无参数或结果详情。
    ') + + '
    ' + + '
    ' + + '
    '; + } + if (item.kind === 'thought') { + return '' + + '
    ' + + '思考过程' + + '
    ' + escapeHtml(item.content || '') + '
    ' + + '
    '; + } + if (item.kind === 'permission') { + var permissionActions = item.resolved ? '' : ( + '
    ' + + '' + + '' + + '
    ' + ); + return '' + + '
    ' + + '
    权限
    ' + + '
    ' + + '' + escapeHtml(item.toolName || 'session/request_permission') + '
    ' + + '' + escapeHtml(item.argsSummary || item.content || '') + '' + + permissionActions + + '
    ' + + '
    '; + } + if (item.kind === 'plan') { + var planEntries = Array.isArray(item.entries) ? item.entries : []; + var listHtml = planEntries.map(function(entry, idx) { + return '
  • ' + escapeHtml(String(entry || '')) + '
  • '; + }).join(''); + return '' + + '
    ' + + '
    ' + + '执行计划 · ' + planEntries.length + ' 步' + + '
    ' + + '
      ' + listHtml + '
    ' + + '
    ' + + '
    ' + + '
    '; + } + var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : ''; + return '' + + '
    ' + + '
    ' + escapeHtml(roleLabel) + '
    ' + + '
    ' + (item.role === 'assistant' ? renderPageAiMarkdown(item.content || '') : escapeHtml(item.content || '')) + '
    ' + + '
    '; + }).join(''); + conversation.scrollTop = conversation.scrollHeight; + } + + return { + ensurePageAiDrawer, + humanizePageAiResponse, + pageAiContextButtonSummary, + pageAiContextRefDetail, + pageAiContextRefDisabled, + pageAiContextRefLabel, + pageAiCurrentAgentSelectionLabel, + pageAiCurrentModelLabel, + pageAiFilteredSkillEntries, + pageAiFormatChangedFiles, + pageAiMemoryFileLabel, + pageAiRenderAgentProfileOption, + pageAiRunStatusLabel, + pageAiSuggestions, + pageAiTargetDetail, + pageAiTargetLabel, + pageAiContextScopeLabel, + renderPageAiControls, + renderPageAiConversation, + renderPageAiProviderButtons, + renderPageAiSuggestions, + }; +} diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js index f673ddf7..6f6aa13c 100644 --- a/rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js @@ -1,3 +1,11 @@ +import { createSidebarPageAiMarkdownRuntime } from './sidebar-page-ai-markdown-runtime.js'; +import { createSidebarPageAiPermissionRuntime } from './sidebar-page-ai-permission-runtime.js'; +import { createSidebarPageAiProfileRuntime } from './sidebar-page-ai-profile-runtime.js'; +import { createSidebarPageAiRenderRuntime } from './sidebar-page-ai-render-runtime.js'; +import { createSidebarPageAiSessionRuntime } from './sidebar-page-ai-session-runtime.js'; +import { createSidebarPageAiSkillRuntime } from './sidebar-page-ai-skill-runtime.js'; +import { createSidebarPageAiTargetRuntime } from './sidebar-page-ai-target-runtime.js'; + export function createSidebarPageAiRuntime(context) { const { buildLocalFileOpenUrl, @@ -22,6 +30,11 @@ export function createSidebarPageAiRuntime(context) { { id: 'reasonix', label: 'Reasonix', acpRuntime: 'reasonix', canWriteFiles: true }, { id: 'chat_only', label: 'Chat-only', acpRuntime: 'hermes', canWriteFiles: false } ]; + var PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY = [ + { profileId: 'shared_deepseek_chat', baseProfile: 'deepseek-chat', label: 'DeepSeek' }, + { profileId: 'shared_doubao_chat', baseProfile: 'doubao-chat', label: '豆包' }, + { profileId: 'shared_gemini_chat', baseProfile: 'gemini-chat', label: 'Gemini' } + ]; var PAGE_AI_CONTEXT_REF_REGISTRY = [ { id: 'current_page', label: '当前页' }, { id: 'selection', label: '选区' }, @@ -30,22 +43,131 @@ export function createSidebarPageAiRuntime(context) { { id: 'folder', label: '文件夹' }, { id: 'changed_files', label: '最近修改' } ]; + var pageAiDelegatesInstalled = false; - function textFromUnknown(value) { - if (value == null) return ''; - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return String(value); - if (Array.isArray(value)) return value.map(textFromUnknown).filter(Boolean).join(' '); - if (typeof value !== 'object') return ''; - var parts = []; - ['text', 'title', 'content', 'children', 'blocks', 'body'].forEach(function(key) { - if (Object.prototype.hasOwnProperty.call(value, key)) { - var text = textFromUnknown(value[key]); - if (text) parts.push(text); + function clonePageAiDefaultValue(value) { + if (Array.isArray(value)) return value.slice(); + if (value && typeof value === 'object') return Object.assign({}, value); + return value; + } + + function ensurePageAiStateFacade(state) { + var defaults = { + pageAiOpen: false, + pageAiBusy: false, + pageAiMessages: [], + pageAiSuggestionIndex: 0, + pageAiProvider: 'hermes', + pageAiPage: 'chat', + pageAiRunStatus: 'idle', + pageAiCurrentRunId: '', + pageAiAcpRuntime: 'reasonix', + pageAiAcpRuntimes: [], + pageAiQueueLength: 0, + pageAiQueuedItems: [], + pageAiStoppedRunIds: {}, + pageAiAbortController: null, + pageAiContextScope: 'page', + pageAiContextPopoverOpen: false, + pageAiTargetPopoverOpen: false, + pageAiSelectedTargetId: '', + pageAiSelectedContextRefs: null, + pageAiCurrentRunTargetSnapshot: null, + pageAiAllowedRoots: [], + pageAiAllowedRootsError: '', + pageAiAgentId: 'reasonix', + pageAiAgentPopoverOpen: false, + pageAiTools: [], + pageAiToolsError: '', + pageAiGatewayHealth: null, + pageAiGatewayHealthError: '', + pageAiLastToolCall: null, + pageAiProfiles: [], + pageAiActiveProfileName: 'mnoteai', + pageAiProfileError: '', + pageAiProfileMemory: { memory: '', user: '', soul: '' }, + pageAiProfileMemoryDrafts: { memory: '', user: '', soul: '' }, + pageAiProfileMemoryError: '', + pageAiSkills: { categories: [], archived: [] }, + pageAiSkillCatalogs: { + mnote: { categories: [], archived: [] }, + reasonix: { categories: [], archived: [] }, + hermes: { categories: [], archived: [] } + }, + pageAiSkillPreferences: {}, + pageAiCollapsedSkillGroups: {}, + pageAiSkillQuery: '', + pageAiSkillError: '', + pageAiSkillLoadSeq: 0, + pageAiActiveSkillSource: 'mnote', + pageAiSessions: [], + pageAiActiveSessionId: '', + pageAiSessionSearchQuery: '', + pageAiSessionSearchResults: [], + pageAiSessionSearchTimer: 0, + pageAiSessionAgentFilter: 'all', + pageAiSessionError: '', + pageAiPermissionRequests: [] + }; + Object.keys(defaults).forEach(function(key) { + if (typeof state[key] === 'undefined') { + state[key] = clonePageAiDefaultValue(defaults[key]); } }); - return parts.join(' '); + if (!state.pageAiSkillCatalogs || typeof state.pageAiSkillCatalogs !== 'object') { + state.pageAiSkillCatalogs = clonePageAiDefaultValue(defaults.pageAiSkillCatalogs); + } + if (!state.pageAiProfileMemory || typeof state.pageAiProfileMemory !== 'object') { + state.pageAiProfileMemory = clonePageAiDefaultValue(defaults.pageAiProfileMemory); + } + if (!state.pageAiProfileMemoryDrafts || typeof state.pageAiProfileMemoryDrafts !== 'object') { + state.pageAiProfileMemoryDrafts = clonePageAiDefaultValue(defaults.pageAiProfileMemoryDrafts); + } + if (!state.pageAiSkills || typeof state.pageAiSkills !== 'object') { + state.pageAiSkills = clonePageAiDefaultValue(defaults.pageAiSkills); + } + return state; } + ensurePageAiStateFacade(pageUiState); + + function closestAction(target, selector) { + var node = target && target.nodeType === Node.TEXT_NODE ? target.parentElement : target; + return node && typeof node.closest === 'function' ? node.closest(selector) : null; + } + + const pageAiMarkdown = createSidebarPageAiMarkdownRuntime({ escapeHtml }); + const textFromUnknown = (...args) => pageAiMarkdown.textFromUnknown(...args); + const renderPageAiMarkdown = (...args) => pageAiMarkdown.renderPageAiMarkdown(...args); + let pageAiRenderRuntime = null; + + function pageAiRenderApi() { + if (!pageAiRenderRuntime) throw new Error('page_ai_render_runtime_not_ready'); + return pageAiRenderRuntime; + } + + function ensurePageAiDrawer(...args) { return pageAiRenderApi().ensurePageAiDrawer(...args); } + function humanizePageAiResponse(...args) { return pageAiRenderApi().humanizePageAiResponse(...args); } + function pageAiContextButtonSummary(...args) { return pageAiRenderApi().pageAiContextButtonSummary(...args); } + function pageAiContextRefDetail(...args) { return pageAiRenderApi().pageAiContextRefDetail(...args); } + function pageAiContextRefDisabled(...args) { return pageAiRenderApi().pageAiContextRefDisabled(...args); } + function pageAiContextRefLabel(...args) { return pageAiRenderApi().pageAiContextRefLabel(...args); } + function pageAiContextScopeLabel(...args) { return pageAiRenderApi().pageAiContextScopeLabel(...args); } + function pageAiCurrentAgentSelectionLabel(...args) { return pageAiRenderApi().pageAiCurrentAgentSelectionLabel(...args); } + function pageAiCurrentModelLabel(...args) { return pageAiRenderApi().pageAiCurrentModelLabel(...args); } + function pageAiFilteredSkillEntries(...args) { return pageAiRenderApi().pageAiFilteredSkillEntries(...args); } + function pageAiFormatChangedFiles(...args) { return pageAiRenderApi().pageAiFormatChangedFiles(...args); } + function pageAiMemoryFileLabel(...args) { return pageAiRenderApi().pageAiMemoryFileLabel(...args); } + function pageAiRenderAgentProfileOption(...args) { return pageAiRenderApi().pageAiRenderAgentProfileOption(...args); } + function pageAiRunStatusLabel(...args) { return pageAiRenderApi().pageAiRunStatusLabel(...args); } + function pageAiSuggestions(...args) { return pageAiRenderApi().pageAiSuggestions(...args); } + function pageAiTargetDetail(...args) { return pageAiRenderApi().pageAiTargetDetail(...args); } + function pageAiTargetLabel(...args) { return pageAiRenderApi().pageAiTargetLabel(...args); } + function renderPageAiControls(...args) { return pageAiRenderApi().renderPageAiControls(...args); } + function renderPageAiConversation(...args) { return pageAiRenderApi().renderPageAiConversation(...args); } + function renderPageAiProviderButtons(...args) { return pageAiRenderApi().renderPageAiProviderButtons(...args); } + function renderPageAiSuggestions(...args) { return pageAiRenderApi().renderPageAiSuggestions(...args); } + function readLocalEditorBlocks() { var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror'); if (!(editor instanceof HTMLElement)) return []; @@ -158,131 +280,6 @@ export function createSidebarPageAiRuntime(context) { }; } - function normalizePageAiOpenEditorEntry(entry) { - if (!entry || typeof entry !== 'object') return null; - return { - objectIdentity: String(entry.objectIdentity || '').trim(), - workspacePath: entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : null, - paneRole: String(entry.paneRole || 'primary').trim() === 'secondary' ? 'secondary' : 'primary', - documentId: String(entry.documentId || '').trim(), - workspaceId: String(entry.workspaceId || '').trim(), - title: String(entry.title || '').trim(), - kind: String(entry.kind || entry.editorKind || '').trim(), - editorKind: String(entry.editorKind || entry.kind || '').trim(), - active: entry.active === true, - dirtyState: String(entry.dirtyState || entry.dirtyGuard || '').trim(), - preview: entry.preview === true, - pinned: entry.pinned === true, - lastActiveAt: Number(entry.lastActiveAt || 0) || 0, - assetId: String(entry.assetId || '').trim(), - path: String(entry.path || '').trim() - }; - } - - function currentPageAiOpenEditorsSnapshot() { - var snapshot = null; - try { - if (window.__mnoteDocumentPaneRuntime && typeof window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot === 'function') { - snapshot = window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot(); - } - } catch (_) {} - if (!snapshot || typeof snapshot !== 'object') snapshot = window.__mnoteOpenEditorsSnapshot || null; - if (!snapshot || typeof snapshot !== 'object') return null; - var editors = Array.isArray(snapshot.editors) - ? snapshot.editors.map(normalizePageAiOpenEditorEntry).filter(Boolean) - : []; - var resources = Array.isArray(snapshot.resourceEditors) - ? snapshot.resourceEditors.map(normalizePageAiOpenEditorEntry).filter(Boolean) - : editors.filter(function(entry) { return entry.kind !== 'page'; }); - var normalizeGroup = function(group, paneRole) { - var groupEditors = group && Array.isArray(group.editors) - ? group.editors.map(normalizePageAiOpenEditorEntry).filter(Boolean) - : editors.filter(function(entry) { return entry.paneRole === paneRole; }); - var groupResources = group && Array.isArray(group.resourceEditors) - ? group.resourceEditors.map(normalizePageAiOpenEditorEntry).filter(Boolean) - : resources.filter(function(entry) { return entry.paneRole === paneRole; }); - var groupActive = groupEditors.find(function(entry) { return entry.active; }) || null; - return { - paneRole: paneRole, - activeObjectIdentity: String(group && group.activeObjectIdentity || (groupActive ? groupActive.objectIdentity : '') || '').trim(), - editors: groupEditors, - resourceEditors: groupResources - }; - }; - var groups = { - primary: normalizeGroup(snapshot.groups && snapshot.groups.primary, 'primary'), - secondary: normalizeGroup(snapshot.groups && snapshot.groups.secondary, 'secondary') - }; - var activeObjectIdentity = String(snapshot.activeObjectIdentity || '').trim(); - var activeEditor = editors.find(function(entry) { - return entry.active && (!activeObjectIdentity || entry.objectIdentity === activeObjectIdentity); - }) || groups.primary.editors.find(function(entry) { - return entry.active; - }) || groups.secondary.editors.find(function(entry) { - return entry.active; - }) || null; - return { - schema: String(snapshot.schema || 'mnote.open_editors_snapshot.v1'), - generatedAt: Number(snapshot.generatedAt || 0) || Date.now(), - activeObjectIdentity: activeObjectIdentity || (activeEditor ? activeEditor.objectIdentity : ''), - activeEditor: activeEditor, - editors: editors, - resourceEditors: resources, - groups: groups - }; - } - - function currentPageAiEditorTarget() { - var snapshot = currentPageAiOpenEditorsSnapshot(); - var activeEditor = snapshot && snapshot.activeEditor ? snapshot.activeEditor : null; - if (!activeEditor) { - var fallbackDocumentId = currentDocumentId(); - return { - schema: 'mnote.ai_editor_target.v1', - source: 'fallback_current_document', - objectIdentity: fallbackDocumentId ? 'page:primary' : '', - workspacePath: null, - paneRole: 'primary', - documentId: fallbackDocumentId, - workspaceId: resolveWorkspaceId(document.body), - editorKind: 'page', - active: true, - dirtyState: '', - preview: false, - pinned: true, - lastActiveAt: Date.now(), - assetId: '', - path: '' - }; - } - return { - schema: 'mnote.ai_editor_target.v1', - source: 'open_editors_snapshot', - objectIdentity: activeEditor.objectIdentity, - workspacePath: activeEditor.workspacePath || null, - paneRole: activeEditor.paneRole, - documentId: activeEditor.documentId, - workspaceId: activeEditor.workspaceId || resolveWorkspaceId(document.body), - editorKind: activeEditor.editorKind || activeEditor.kind, - active: activeEditor.active, - dirtyState: activeEditor.dirtyState || '', - preview: activeEditor.preview === true, - pinned: activeEditor.pinned === true, - lastActiveAt: activeEditor.lastActiveAt || 0, - assetId: activeEditor.assetId || '', - path: activeEditor.path || '' - }; - } - - function pageAiCloneJson(value) { - if (value == null) return null; - try { - return JSON.parse(JSON.stringify(value)); - } catch (_) { - return null; - } - } - function pageAiNormalizeAgentId(agentId) { var value = String(agentId || '').trim(); return PAGE_AI_AGENT_REGISTRY.some(function(agent) { return agent.id === value; }) ? value : 'reasonix'; @@ -301,6 +298,128 @@ export function createSidebarPageAiRuntime(context) { return 'reasonix'; } + const pageAiProfileRuntime = createSidebarPageAiProfileRuntime({ + chatOnlyProfileRegistry: PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY, + documentRef: document, + pageAiAgentRecord, + pageAiCurrentAgentId, + pageAiNormalizeAgentId, + pageUiState, + }); + const pageAiProviderLabel = (...args) => pageAiProfileRuntime.pageAiProviderLabel(...args); + const pageAiNormalizeArray = (...args) => pageAiProfileRuntime.pageAiNormalizeArray(...args); + const pageAiDefaultAcpRuntimes = (...args) => pageAiProfileRuntime.pageAiDefaultAcpRuntimes(...args); + const pageAiNormalizeAcpRuntimes = (...args) => pageAiProfileRuntime.pageAiNormalizeAcpRuntimes(...args); + const pageAiUnwrapUpstream = (...args) => pageAiProfileRuntime.pageAiUnwrapUpstream(...args); + const pageAiProfileValue = (...args) => pageAiProfileRuntime.pageAiProfileValue(...args); + const pageAiCurrentProfile = (...args) => pageAiProfileRuntime.pageAiCurrentProfile(...args); + const pageAiRunProfile = (...args) => pageAiProfileRuntime.pageAiRunProfile(...args); + const pageAiMnoteToolModel = (...args) => pageAiProfileRuntime.pageAiMnoteToolModel(...args); + const pageAiCurrentProfileRecord = (...args) => pageAiProfileRuntime.pageAiCurrentProfileRecord(...args); + const pageAiChatOnlyProfileSpec = (...args) => pageAiProfileRuntime.pageAiChatOnlyProfileSpec(...args); + const pageAiDefaultChatOnlyProfileSpec = (...args) => pageAiProfileRuntime.pageAiDefaultChatOnlyProfileSpec(...args); + const pageAiNormalizeChatOnlyProfileId = (...args) => pageAiProfileRuntime.pageAiNormalizeChatOnlyProfileId(...args); + const pageAiProfileDisplayLabel = (...args) => pageAiProfileRuntime.pageAiProfileDisplayLabel(...args); + const pageAiProfileRecordById = (...args) => pageAiProfileRuntime.pageAiProfileRecordById(...args); + const pageAiSessionAgentFilterValue = (...args) => pageAiProfileRuntime.pageAiSessionAgentFilterValue(...args); + const pageAiSessionAgentLabel = (...args) => pageAiProfileRuntime.pageAiSessionAgentLabel(...args); + const pageAiSessionPreviewText = (...args) => pageAiProfileRuntime.pageAiSessionPreviewText(...args); + const pageAiSessionAgentFilterOptions = (...args) => pageAiProfileRuntime.pageAiSessionAgentFilterOptions(...args); + const pageAiFilteredHistoryRows = (...args) => pageAiProfileRuntime.pageAiFilteredHistoryRows(...args); + const pageAiTimestamp = (...args) => pageAiProfileRuntime.pageAiTimestamp(...args); + const pageAiUsageSummary = (...args) => pageAiProfileRuntime.pageAiUsageSummary(...args); + const pageAiPermissionRuntime = createSidebarPageAiPermissionRuntime({ + documentRef: document, + pageAiPreviewValue, + pageUiState, + renderPageAiConversation, + }); + const pageAiPermissionMessage = (...args) => pageAiPermissionRuntime.pageAiPermissionMessage(...args); + const pageAiApplyPermissionEvent = (...args) => pageAiPermissionRuntime.pageAiApplyPermissionEvent(...args); + const pageAiResolvePermission = (...args) => pageAiPermissionRuntime.pageAiResolvePermission(...args); + const pageAiHidePermissionDialog = (...args) => pageAiPermissionRuntime.pageAiHidePermissionDialog(...args); + const pageAiShowPermissionDialog = (...args) => pageAiPermissionRuntime.pageAiShowPermissionDialog(...args); + const pageAiSessionRuntime = createSidebarPageAiSessionRuntime({ + currentDocumentId, + currentRootUri, + currentSourceKind, + documentRef: document, + pageAiApplyRuntimeState, + pageAiCurrentAgentId, + pageAiCurrentProfile, + pageAiErrorMessage, + pageAiNormalizeAgentId, + pageAiNormalizeArray, + pageAiNormalizeChatOnlyProfileId, + pageAiPermissionMessage, + pageAiPreviewValue, + pageAiRunProfile, + pageAiSetActiveProfile, + pageAiTimestamp, + pageUiState, + renderPageAiControls, + renderPageAiConversation, + resolveWorkspaceId, + sessionStorageVersion: PAGE_AI_SESSION_STORAGE_VERSION, + windowRef: window, + }); + const pageAiStorageKey = (...args) => pageAiSessionRuntime.pageAiStorageKey(...args); + const pageAiBackendSessionQuery = (...args) => pageAiSessionRuntime.pageAiBackendSessionQuery(...args); + const pageAiNewSession = (...args) => pageAiSessionRuntime.pageAiNewSession(...args); + const pageAiNormalizeSessions = (...args) => pageAiSessionRuntime.pageAiNormalizeSessions(...args); + const pageAiNormalizeBackendSessionRow = (...args) => pageAiSessionRuntime.pageAiNormalizeBackendSessionRow(...args); + const pageAiMergeSessions = (...args) => pageAiSessionRuntime.pageAiMergeSessions(...args); + const pageAiSessionStorageLabel = (...args) => pageAiSessionRuntime.pageAiSessionStorageLabel(...args); + const pageAiLoadSessions = (...args) => pageAiSessionRuntime.pageAiLoadSessions(...args); + const pageAiLoadBackendSessions = (...args) => pageAiSessionRuntime.pageAiLoadBackendSessions(...args); + const pageAiMessageFromRuntimeEvent = (...args) => pageAiSessionRuntime.pageAiMessageFromRuntimeEvent(...args); + const pageAiApplyBackendSessionDetail = (...args) => pageAiSessionRuntime.pageAiApplyBackendSessionDetail(...args); + const pageAiLoadBackendSessionDetail = (...args) => pageAiSessionRuntime.pageAiLoadBackendSessionDetail(...args); + const pageAiSearchBackendSessions = (...args) => pageAiSessionRuntime.pageAiSearchBackendSessions(...args); + const pageAiPersistSessions = (...args) => pageAiSessionRuntime.pageAiPersistSessions(...args); + const pageAiEnsureHermesSession = (...args) => pageAiSessionRuntime.pageAiEnsureHermesSession(...args); + const pageAiRestoreHermesSession = (...args) => pageAiSessionRuntime.pageAiRestoreHermesSession(...args); + const pageAiCurrentSession = (...args) => pageAiSessionRuntime.pageAiCurrentSession(...args); + const pageAiSyncCurrentSessionMessages = (...args) => pageAiSessionRuntime.pageAiSyncCurrentSessionMessages(...args); + const pageAiSetActiveSession = (...args) => pageAiSessionRuntime.pageAiSetActiveSession(...args); + const pageAiStartNewSession = (...args) => pageAiSessionRuntime.pageAiStartNewSession(...args); + const pageAiRenameBackendSession = (...args) => pageAiSessionRuntime.pageAiRenameBackendSession(...args); + const pageAiDeleteBackendSession = (...args) => pageAiSessionRuntime.pageAiDeleteBackendSession(...args); + const pageAiResumeBackendSession = (...args) => pageAiSessionRuntime.pageAiResumeBackendSession(...args); + const pageAiSkillRuntime = createSidebarPageAiSkillRuntime({ + pageAiCurrentAgentId, + pageAiCurrentProfile, + pageAiLoadSkills, + pageAiNormalizeArray, + pageAiPersistAiPreference, + pageAiPersistRawAiPreference, + pageAiProfileValue, + pageUiState, + renderPageAiControls, + }); + const pageAiSkillSourceOptions = (...args) => pageAiSkillRuntime.pageAiSkillSourceOptions(...args); + const pageAiDefaultSkillSource = (...args) => pageAiSkillRuntime.pageAiDefaultSkillSource(...args); + const pageAiNormalizeSkillSource = (...args) => pageAiSkillRuntime.pageAiNormalizeSkillSource(...args); + const pageAiCurrentSkillSource = (...args) => pageAiSkillRuntime.pageAiCurrentSkillSource(...args); + const pageAiSetSkillSource = (...args) => pageAiSkillRuntime.pageAiSetSkillSource(...args); + const pageAiSkillSourceParts = (...args) => pageAiSkillRuntime.pageAiSkillSourceParts(...args); + const pageAiCurrentSkillSourceLabel = (...args) => pageAiSkillRuntime.pageAiCurrentSkillSourceLabel(...args); + const pageAiSkillOriginLabel = (...args) => pageAiSkillRuntime.pageAiSkillOriginLabel(...args); + const pageAiSkillPreferenceKey = (...args) => pageAiSkillRuntime.pageAiSkillPreferenceKey(...args); + const pageAiSkillPreferenceTable = (...args) => pageAiSkillRuntime.pageAiSkillPreferenceTable(...args); + const pageAiHermesHideBuiltinPreferenceKey = (...args) => pageAiSkillRuntime.pageAiHermesHideBuiltinPreferenceKey(...args); + const pageAiHideHermesBuiltinSkills = (...args) => pageAiSkillRuntime.pageAiHideHermesBuiltinSkills(...args); + const pageAiReasonixMemoryEnabled = (...args) => pageAiSkillRuntime.pageAiReasonixMemoryEnabled(...args); + const pageAiSetReasonixMemoryEnabled = (...args) => pageAiSkillRuntime.pageAiSetReasonixMemoryEnabled(...args); + const pageAiSetHideHermesBuiltinSkills = (...args) => pageAiSkillRuntime.pageAiSetHideHermesBuiltinSkills(...args); + const pageAiSkillIsBuiltin = (...args) => pageAiSkillRuntime.pageAiSkillIsBuiltin(...args); + const pageAiToggleableSkillEntries = (...args) => pageAiSkillRuntime.pageAiToggleableSkillEntries(...args); + const pageAiAllSkillEntries = (...args) => pageAiSkillRuntime.pageAiAllSkillEntries(...args); + const pageAiSkillGroupCollapsed = (...args) => pageAiSkillRuntime.pageAiSkillGroupCollapsed(...args); + const pageAiToggleSkillGroup = (...args) => pageAiSkillRuntime.pageAiToggleSkillGroup(...args); + const pageAiSetSkillPreference = (...args) => pageAiSkillRuntime.pageAiSetSkillPreference(...args); + const pageAiSkillEnabled = (...args) => pageAiSkillRuntime.pageAiSkillEnabled(...args); + function pageAiEnsureContextRefState() { if (!pageUiState.pageAiSelectedContextRefs || typeof pageUiState.pageAiSelectedContextRefs !== 'object') { pageUiState.pageAiSelectedContextRefs = { @@ -315,6 +434,104 @@ export function createSidebarPageAiRuntime(context) { return pageUiState.pageAiSelectedContextRefs; } + const pageAiTargetRuntime = createSidebarPageAiTargetRuntime({ + currentDocumentId, + currentPageOptions, + currentRootUri, + currentSourceKind, + documentRef: document, + escapeHtml, + pageAiEnsureContextRefState, + pageAiNormalizeArray, + pageUiState, + resolveWorkspaceId, + searchText, + }); + const currentPageAiOpenEditorsSnapshot = (...args) => pageAiTargetRuntime.currentPageAiOpenEditorsSnapshot(...args); + const currentPageAiEditorTarget = (...args) => pageAiTargetRuntime.currentPageAiEditorTarget(...args); + const currentPageAiPageEditorTarget = (...args) => pageAiTargetRuntime.currentPageAiPageEditorTarget(...args); + const currentPageAiScopedEditorTarget = (...args) => pageAiTargetRuntime.currentPageAiScopedEditorTarget(...args); + const pageAiCloneJson = (...args) => pageAiTargetRuntime.pageAiCloneJson(...args); + const pageAiEditorTargetCandidates = (...args) => pageAiTargetRuntime.pageAiEditorTargetCandidates(...args); + const pageAiFallbackEditorTarget = (...args) => pageAiTargetRuntime.pageAiFallbackEditorTarget(...args); + const pageAiResourceKindForTarget = (...args) => pageAiTargetRuntime.pageAiResourceKindForTarget(...args); + const pageAiTargetFromOpenEditor = (...args) => pageAiTargetRuntime.pageAiTargetFromOpenEditor(...args); + const pageAiTargetId = (...args) => pageAiTargetRuntime.pageAiTargetId(...args); + const localMarkdownRelativePathFromPageAiDocumentId = (...args) => pageAiTargetRuntime.localMarkdownRelativePathFromPageAiDocumentId(...args); + const localMarkdownDocumentIdFromPageAiRelativePath = (...args) => pageAiTargetRuntime.localMarkdownDocumentIdFromPageAiRelativePath(...args); + const pageAiWorkspacePathForDocument = (...args) => pageAiTargetRuntime.pageAiWorkspacePathForDocument(...args); + const pageAiWorkspacePathForTarget = (...args) => pageAiTargetRuntime.pageAiWorkspacePathForTarget(...args); + const pageAiBuildAllowedRoots = (...args) => pageAiTargetRuntime.pageAiBuildAllowedRoots(...args); + const pageAiBuildContextRefs = (...args) => pageAiTargetRuntime.pageAiBuildContextRefs(...args); + const pageAiBuildRunTargetSnapshot = (...args) => pageAiTargetRuntime.pageAiBuildRunTargetSnapshot(...args); + const pageAiPageContextForRefs = (...args) => pageAiTargetRuntime.pageAiPageContextForRefs(...args); + const pageAiSetRunTargetSnapshot = (...args) => pageAiTargetRuntime.pageAiSetRunTargetSnapshot(...args); + const assertPageAiTargetInCurrentWorkspace = (...args) => pageAiTargetRuntime.assertPageAiTargetInCurrentWorkspace(...args); + const pageAiBuildAgentTargetPackage = (...args) => pageAiTargetRuntime.pageAiBuildAgentTargetPackage(...args); + const pageAiBlockingDirtyState = (...args) => pageAiTargetRuntime.pageAiBlockingDirtyState(...args); + const fetchPageAiTargetBufferState = (...args) => pageAiTargetRuntime.fetchPageAiTargetBufferState(...args); + const assertPageAiTargetWritable = (...args) => pageAiTargetRuntime.assertPageAiTargetWritable(...args); + const currentPageAiSelectedText = (...args) => pageAiTargetRuntime.currentPageAiSelectedText(...args); + const pageAiProjectionBlocks = (...args) => pageAiTargetRuntime.pageAiProjectionBlocks(...args); + const pageAiBlockText = (...args) => pageAiTargetRuntime.pageAiBlockText(...args); + const pageAiSelectedBlockIdsFromSelection = (...args) => pageAiTargetRuntime.pageAiSelectedBlockIdsFromSelection(...args); + const pageAiBlocksToPageXml = (...args) => pageAiTargetRuntime.pageAiBlocksToPageXml(...args); + const buildPageAiContext = (...args) => pageAiTargetRuntime.buildPageAiContext(...args); + const pageAiScopedPageContext = (...args) => pageAiTargetRuntime.pageAiScopedPageContext(...args); + pageAiRenderRuntime = createSidebarPageAiRenderRuntime({ + contextRefRegistry: PAGE_AI_CONTEXT_REF_REGISTRY, + cssEscape, + currentDocumentId, + currentPageAiEditorTarget, + currentPageAiSelectedText, + documentRef: document, + escapeHtml, + localMarkdownRelativePathFromPageAiDocumentId, + pageAiAgentRecord, + pageAiAllSkillEntries, + pageAiBuildAllowedRoots, + pageAiChatOnlyProfileEntries, + pageAiChatOnlyProfileSpec, + pageAiCurrentAgentId, + pageAiCurrentProfile, + pageAiCurrentProfileRecord, + pageAiCurrentSession, + pageAiCurrentSkillSource, + pageAiCurrentSkillSourceLabel, + pageAiDefaultChatOnlyProfileSpec, + pageAiEditorTargetCandidates, + pageAiEnsureContextRefState, + pageAiFilteredHistoryRows, + pageAiHermesProfileEntries, + pageAiHermesSettingsUrl, + pageAiHideHermesBuiltinSkills, + pageAiMnoteToolModel, + pageAiNormalizeAcpRuntimes, + pageAiNormalizeArray, + pageAiNormalizeSkillSource, + pageAiProfileDisplayLabel, + pageAiProfileValue, + pageAiProviderLabel, + pageAiReasonixMemoryEnabled, + pageAiRunProfile, + pageAiSessionAgentFilterOptions, + pageAiSessionAgentFilterValue, + pageAiSessionAgentLabel, + pageAiSessionPreviewText, + pageAiSessionStorageLabel, + pageAiSkillEnabled, + pageAiSkillGroupCollapsed, + pageAiSkillListEntries, + pageAiSkillOriginLabel, + pageAiSkillSourceOptions, + pageAiSkillSourceParts, + pageAiToggleableSkillEntries, + pageAiUsageSummary, + pageUiState, + renderPageAiMarkdown, + searchText, + }); + function pageAiSetAgentId(agentId) { var next = pageAiNormalizeAgentId(agentId); var record = pageAiAgentRecord(next); @@ -322,6 +539,7 @@ export function createSidebarPageAiRuntime(context) { pageUiState.pageAiAcpRuntime = record.acpRuntime || 'reasonix'; pageUiState.pageAiAgentPopoverOpen = next === 'hermes'; if (next === 'hermes') pageAiSetActiveProfile(pageAiCurrentProfile()); + if (next === 'chat_only') pageAiSetActiveProfile(pageAiNormalizeChatOnlyProfileId(pageAiCurrentProfile())); document.documentElement.setAttribute('data-mnote-page-ai-agent-id', next); pageAiPersistAiPreference('default_agent_id', next); void pageAiLoadSkills(); @@ -341,55 +559,42 @@ export function createSidebarPageAiRuntime(context) { function pageAiSetContextPopoverOpen(open) { pageUiState.pageAiContextPopoverOpen = Boolean(open); - if (open) pageUiState.pageAiAgentPopoverOpen = false; + if (open) { + pageUiState.pageAiAgentPopoverOpen = false; + pageUiState.pageAiTargetPopoverOpen = false; + } renderPageAiControls(); } function pageAiSetAgentPopoverOpen(open) { pageUiState.pageAiAgentPopoverOpen = Boolean(open); - if (open) pageUiState.pageAiContextPopoverOpen = false; + if (open) { + pageUiState.pageAiContextPopoverOpen = false; + pageUiState.pageAiTargetPopoverOpen = false; + } renderPageAiControls(); } - function pageAiContextRefLabel(kind) { - var record = PAGE_AI_CONTEXT_REF_REGISTRY.find(function(ref) { return ref.id === kind; }); - return record ? record.label : kind; - } - - function pageAiContextButtonSummary() { - var selected = pageAiEnsureContextRefState(); - var labels = PAGE_AI_CONTEXT_REF_REGISTRY - .filter(function(ref) { return selected[ref.id] === true; }) - .map(function(ref) { return ref.label; }); - if (!labels.length) return '选择上下文'; - var head = labels.slice(0, 2).join(' + '); - return labels.length > 2 ? head + ' +' + String(labels.length - 2) : head; - } - - function pageAiContextRefDetail(kind) { - var documentId = currentDocumentId(); - var relativePath = localMarkdownRelativePathFromPageAiDocumentId(documentId); - if (kind === 'current_page') return relativePath || documentId || '当前页面'; - if (kind === 'selection') return currentPageAiSelectedText() ? '当前选区可用' : '当前没有选区'; - if (kind === 'active_editor') { - var target = currentPageAiEditorTarget(); - return String(target && (target.title || target.documentId) || relativePath || '当前打开资源'); + function pageAiSetTargetPopoverOpen(open) { + pageUiState.pageAiTargetPopoverOpen = Boolean(open); + if (open) { + pageUiState.pageAiAgentPopoverOpen = false; + pageUiState.pageAiContextPopoverOpen = false; } - if (kind === 'file') return relativePath || '当前文件'; - if (kind === 'folder') { - var roots = pageAiBuildAllowedRoots(); - if (!roots.length) return '需要授权后可用'; - return roots.map(function(root) { return root.rootUri.replace(/^file:\/\//, ''); }).filter(Boolean)[0] || '已授权文件夹'; - } - if (kind === 'changed_files') return '本次 run 后可用于追问'; - return ''; + renderPageAiControls(); } - function pageAiContextRefDisabled(kind) { - if (kind === 'selection') return !currentPageAiSelectedText(); - return false; + function pageAiSelectTarget(targetId) { + var normalized = String(targetId || '').trim(); + var candidates = pageAiEditorTargetCandidates(); + var target = candidates.find(function(candidate) { return candidate.targetId === normalized; }) || null; + if (!target) return; + pageUiState.pageAiSelectedTargetId = target.targetId; + pageUiState.pageAiTargetPopoverOpen = false; + renderPageAiControls(); } + function pageAiPersistAiPreference(key, value) { pageAiPersistRawAiPreference('ai.common.' + key, value); } @@ -435,6 +640,8 @@ export function createSidebarPageAiRuntime(context) { } var hermesProfile = String(preferences['ai.agent.hermes.profile_id'] || '').trim(); if (hermesProfile) pageAiSetActiveProfile(hermesProfile); + var skillSource = String(preferences['ai.common.skills.active_source'] || '').trim(); + if (skillSource) pageUiState.pageAiActiveSkillSource = skillSource; var selected = preferences['ai.common.context_refs.default_selected']; if (selected && typeof selected === 'object' && !Array.isArray(selected)) { pageUiState.pageAiSelectedContextRefs = Object.assign({}, pageAiEnsureContextRefState(), selected); @@ -490,340 +697,6 @@ export function createSidebarPageAiRuntime(context) { return pageUiState.pageAiAllowedRoots; } - function pageAiBuildContextRefs(scopedContext, runTargetSnapshot) { - var selected = pageAiEnsureContextRefState(); - var refs = []; - var documentId = currentDocumentId(); - var rootUri = currentRootUri(); - var workspaceId = resolveWorkspaceId(document.body); - var editorTarget = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget(); - if (selected.current_page) { - refs.push({ - kind: 'current_page', - documentId: documentId, - rootUri: rootUri, - workspaceId: workspaceId - }); - } - if (selected.selection && scopedContext && scopedContext.selectedText) { - refs.push({ - kind: 'selection', - documentId: documentId, - rootUri: rootUri, - selectedBlockId: scopedContext.selectedBlockId || '' - }); - } - if (selected.active_editor && editorTarget) { - refs.push({ - kind: 'active_editor', - documentId: editorTarget.documentId || documentId, - rootUri: editorTarget.workspacePath && editorTarget.workspacePath.rootUri || rootUri, - relativePath: editorTarget.workspacePath && editorTarget.workspacePath.relativePath || '', - editorKind: editorTarget.editorKind || '' - }); - } - if (selected.file) { - refs.push({ - kind: 'file', - documentId: documentId, - rootUri: rootUri, - relativePath: localMarkdownRelativePathFromPageAiDocumentId(documentId) - }); - } - if (selected.folder) { - refs.push({ - kind: 'folder', - rootUri: rootUri, - relativePath: '' - }); - } - if (selected.changed_files) { - refs.push({ - kind: 'changed_files', - rootUri: rootUri, - sinceRunTargetSnapshot: runTargetSnapshot && runTargetSnapshot.frozenAt || null - }); - } - return refs.filter(function(ref) { - return ref && String(ref.kind || '').trim(); - }); - } - - function pageAiBuildAllowedRoots() { - return pageAiNormalizeArray(pageUiState.pageAiAllowedRoots).map(function(root) { - return { - rootUri: root.rootUri, - permission: root.permission === 'write' || root.permission === 'read_write' ? 'write' : 'read', - recursive: root.recursive !== false, - source: root.source === 'auto' || root.source === 'user' || root.source === 'admin' - ? 'sqlite_directory_grant' - : (root.source || 'sqlite_directory_grant'), - grantId: root.id || '' - }; - }); - } - - function pageAiBuildRunTargetSnapshot(scopedContext, prompt) { - var aiContext = scopedContext && scopedContext.pageContext && scopedContext.pageContext.aiContext - ? scopedContext.pageContext.aiContext - : {}; - var editorTarget = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget(); - return { - schema: 'mnote.page_ai_run_target_snapshot.v1', - source: 'open_editors_snapshot', - frozenAt: Date.now(), - workspaceId: resolveWorkspaceId(document.body), - documentId: currentDocumentId(), - sourceKind: currentSourceKind(), - rootUri: currentRootUri(), - contextScope: pageUiState.pageAiContextScope || 'page', - promptPreview: searchText(prompt || '').slice(0, 160), - editorTarget: pageAiCloneJson(editorTarget), - activeEditorTarget: pageAiCloneJson(aiContext.activeEditorTarget || editorTarget), - openEditorsSnapshot: pageAiCloneJson(aiContext.openEditorsSnapshot || currentPageAiOpenEditorsSnapshot()) - }; - } - - function pageAiContextKindsFromRefs(contextRefs) { - var kinds = {}; - pageAiNormalizeArray(contextRefs).forEach(function(ref) { - var kind = String(ref && ref.kind || '').trim(); - if (kind) kinds[kind] = true; - }); - return kinds; - } - - function pageAiPageContextForRefs(pageContext, contextRefs) { - var cloned = pageAiCloneJson(pageContext) || {}; - var aiContext = cloned.aiContext && typeof cloned.aiContext === 'object' ? cloned.aiContext : {}; - delete cloned.documentBlocks; - delete cloned.evidence; - delete aiContext.contextBlocks; - delete aiContext.pageText; - delete aiContext.pageXml; - delete aiContext.truncated; - delete aiContext.warnings; - delete aiContext.selectedText; - delete aiContext.selectedBlockIds; - delete aiContext.selectedBlocks; - delete aiContext.allowedTargetBlockIds; - cloned.aiContext = aiContext; - return cloned; - } - - function pageAiSetRunTargetSnapshot(snapshot) { - pageUiState.pageAiCurrentRunTargetSnapshot = snapshot || null; - var target = snapshot && snapshot.editorTarget ? snapshot.editorTarget : null; - var documentId = String(target && target.documentId || snapshot && snapshot.documentId || '').trim(); - var workspaceId = String(target && target.workspaceId || snapshot && snapshot.workspaceId || '').trim(); - var rootUri = String(target && target.workspacePath && target.workspacePath.rootUri || snapshot && snapshot.rootUri || '').trim(); - if (documentId) document.documentElement.setAttribute('data-mnote-page-ai-run-target-document-id', documentId); - if (workspaceId) document.documentElement.setAttribute('data-mnote-page-ai-run-target-workspace-id', workspaceId); - if (rootUri) document.documentElement.setAttribute('data-mnote-page-ai-run-target-root-uri', rootUri); - } - - function assertPageAiTargetInCurrentWorkspace(editorTarget) { - var target = editorTarget && typeof editorTarget === 'object' ? editorTarget : {}; - var workspacePath = target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {}; - var targetSourceKind = String(workspacePath.sourceKind || '').trim(); - var currentKind = String(currentSourceKind() || '').trim(); - if (targetSourceKind && currentKind && targetSourceKind !== currentKind) { - var sourceError = new Error('AI target 与当前页面 sourceKind 不一致,请重新选择当前工作区内的目标。'); - sourceError.code = 'page_ai_target_workspace_mismatch'; - throw sourceError; - } - var targetRootUri = String(workspacePath.rootUri || '').trim(); - var currentRoot = String(currentRootUri() || '').trim(); - if (targetRootUri && currentRoot && targetRootUri !== currentRoot) { - var rootError = new Error('AI target 与当前本地工作区 rootUri 不一致,请重新选择当前工作区内的目标。'); - rootError.code = 'page_ai_target_workspace_mismatch'; - throw rootError; - } - var targetWorkspaceId = String(target.workspaceId || workspacePath.workspaceId || '').trim(); - var currentWorkspaceId = String(resolveWorkspaceId(document.body) || '').trim(); - if (targetWorkspaceId && currentWorkspaceId && targetWorkspaceId !== currentWorkspaceId) { - var workspaceError = new Error('AI target 与当前 workspaceId 不一致,请重新选择当前工作区内的目标。'); - workspaceError.code = 'page_ai_target_workspace_mismatch'; - throw workspaceError; - } - } - - function localMarkdownRelativePathFromPageAiDocumentId(documentId) { - var value = String(documentId || '').trim(); - if (!value.startsWith('local-md:')) return ''; - return value.slice('local-md:'.length).replace(/~2F/g, '/'); - } - - function pageAiBlockingDirtyState(dirtyState) { - var state = String(dirtyState || '').trim(); - var normalized = state.toLowerCase(); - if (normalized === 'dirty') return 'Dirty'; - if (normalized === 'stale') return 'Stale'; - if (normalized === 'deleted') return 'Deleted'; - if (normalized === 'externalmodified' || normalized === 'external-change-conflict' || normalized === 'hasexternalconflict') return 'ExternalModified'; - return ''; - } - - async function fetchPageAiTargetBufferState(editorTarget) { - if (currentSourceKind() !== 'local_folder') return null; - var target = editorTarget && typeof editorTarget === 'object' ? editorTarget : {}; - var documentId = String(target.documentId || currentDocumentId() || '').trim(); - var rootUri = String(target.workspacePath && target.workspacePath.rootUri || currentRootUri() || '').trim(); - if (!documentId || !rootUri) return null; - var relativePath = String(target.workspacePath && target.workspacePath.relativePath || '').trim() - || localMarkdownRelativePathFromPageAiDocumentId(documentId); - var url = new URL('/api/documents/buffer-state', window.location.origin); - url.searchParams.set('documentId', documentId); - url.searchParams.set('sourceKind', 'local_folder'); - url.searchParams.set('rootUri', rootUri); - var workspaceId = String(target.workspaceId || resolveWorkspaceId(document.body) || '').trim(); - if (workspaceId) url.searchParams.set('workspaceId', workspaceId); - if (relativePath) url.searchParams.set('relativePath', relativePath); - try { - var response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } }); - var payload = await response.json().catch(function() { return null; }); - if (!response.ok || !payload || payload.ok !== true) return null; - return payload.result || null; - } catch (_) { - return null; - } - } - - async function assertPageAiTargetWritable(editorTarget) { - var snapshotState = pageAiBlockingDirtyState(editorTarget && editorTarget.dirtyState); - var bufferState = await fetchPageAiTargetBufferState(editorTarget); - var bufferDirtyState = pageAiBlockingDirtyState(bufferState && bufferState.dirtyState); - var blockedState = bufferDirtyState || snapshotState; - if (!blockedState) return bufferState; - var documentId = String(editorTarget && editorTarget.documentId || currentDocumentId() || '').trim(); - var error = new Error('目标文档存在未保存或外部变更状态(' + blockedState + '),请先保存、解决冲突或刷新后再让 AI 写入。'); - error.code = 'page_ai_target_buffer_not_writable'; - error.documentId = documentId; - error.dirtyState = blockedState; - throw error; - } - - function currentPageAiSelectedText() { - try { - var selection = window.getSelection ? window.getSelection() : null; - return selection ? searchText(selection.toString() || '') : ''; - } catch (_) { - return ''; - } - } - - function pageAiProjectionBlocks(aggregate) { - var blocks = aggregate && aggregate.body && aggregate.body.blockDocument && aggregate.body.blockDocument.blocks; - return Array.isArray(blocks) ? blocks : []; - } - - function pageAiBlockText(block) { - return searchText(block && (block.text || block.title || block.content) || ''); - } - - function pageAiSelectedBlockIdsFromSelection() { - try { - var selection = window.getSelection ? window.getSelection() : null; - if (!selection || selection.rangeCount === 0 || searchText(selection.toString() || '') === '') return []; - var range = selection.getRangeAt(0); - var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror'); - if (!(editor instanceof HTMLElement)) return []; - return Array.from(editor.children).filter(function(node) { - if (!(node instanceof HTMLElement)) return false; - try { - return range.intersectsNode(node); - } catch (_) { - return false; - } - }).map(function(node) { - return searchText(node.getAttribute('data-id') || node.id || ''); - }).filter(Boolean); - } catch (_) { - return []; - } - } - - function pageAiBlocksToPageXml(blocks, aggregate) { - var revision = aggregate && aggregate.body ? String(aggregate.body.revision || '') : ''; - var pageId = currentDocumentId() || 'current-page'; - var lines = ['']; - blocks.forEach(function(block) { - var blockId = String(block && (block.blockId || block.id) || ''); - var type = String(block && block.type || 'paragraph'); - var revisionRef = String(block && block.revisionRef || ''); - var level = block && block.attrs && block.attrs.level ? ' level="' + escapeHtml(block.attrs.level) + '"' : ''; - lines.push(' ' + escapeHtml(pageAiBlockText(block)) + ''); - }); - lines.push(''); - return lines.join('\n'); - } - - function buildPageAiContext(contextSnapshot, scope, selectedText) { - var aggregate = contextSnapshot.aggregate || {}; - var body = aggregate.body || {}; - var allBlocks = pageAiProjectionBlocks(aggregate); - var selectedBlockIds = scope === 'selection' ? pageAiSelectedBlockIdsFromSelection() : []; - var selectedSet = {}; - selectedBlockIds.forEach(function(id) { selectedSet[id] = true; }); - var selectedBlocks = selectedBlockIds.length - ? allBlocks.filter(function(block) { return selectedSet[String(block.blockId || block.id || '')]; }) - : []; - var contextBlocks = selectedBlocks.length ? selectedBlocks : allBlocks.slice(0, 120); - var truncated = !selectedBlocks.length && allBlocks.length > contextBlocks.length; - return { - schema: 'mnote.page_ai_context.v1', - workspaceId: resolveWorkspaceId(document.body), - documentId: currentDocumentId(), - activeEditorTarget: currentPageAiEditorTarget(), - openEditorsSnapshot: currentPageAiOpenEditorsSnapshot(), - scope: scope, - revision: body.revision || null, - conflictDetectionKey: body.conflictDetectionKey || null, - selectedText: selectedText || '', - selectedBlockIds: selectedBlockIds, - allowedTargetBlockIds: selectedBlockIds, - selectedBlocks: selectedBlocks, - contextBlocks: contextBlocks, - pageText: contextBlocks.map(pageAiBlockText).filter(Boolean).join('\n'), - pageXml: pageAiBlocksToPageXml(contextBlocks, aggregate), - truncated: truncated, - warnings: truncated ? [{ code: 'page_ai_context_truncated', message: '页面 AI context 已按前 120 个块裁剪' }] : [] - }; - } - - function pageAiScopedPageContext(contextSnapshot) { - var aggregate = contextSnapshot.aggregate || {}; - var body = contextSnapshot.body || {}; - var subtree = contextSnapshot.subtree || null; - var outline = subtree && subtree.outline ? subtree.outline : null; - var scope = pageUiState.pageAiContextScope || 'page'; - var title = aggregate.head && aggregate.head.title ? aggregate.head.title : ''; - var selectedText = scope === 'selection' ? currentPageAiSelectedText() : ''; - var aiContext = buildPageAiContext(contextSnapshot, scope, selectedText); - var editorTarget = aiContext.activeEditorTarget || currentPageAiEditorTarget(); - return { - pageContext: { - contextScope: scope, - documentBlocks: null, - node: { - documentId: currentDocumentId(), - title: title - }, - subtree: null, - outline: null, - pageSubtreeSource: scope === 'page' ? (contextSnapshot.pageSubtreeSource || 'none') : 'scope:' + scope, - evidence: selectedText ? [{ kind: 'selection', text: selectedText }] : null, - pageOptions: scope === 'options' || scope === 'page' ? currentPageOptions() : null, - contentAccess: 'mnote.context.read_current_page', - aiContext: aiContext - }, - editorTarget: editorTarget, - selectedText: selectedText, - selectedBlockId: aiContext.selectedBlockIds[0] || null - }; - } - - function updatePageAiTriggerState() { var trigger = document.querySelector('[data-testid="wolai-floating-ai"]'); if (!(trigger instanceof HTMLElement)) return; @@ -832,150 +705,6 @@ export function createSidebarPageAiRuntime(context) { } - function pageAiSuggestions() { - var title = searchText(document.querySelector('[data-page-title-current="true"]')?.textContent) || '当前页面'; - return [ - '帮我总结《' + title + '》当前内容', - '把当前页面改写得更简洁一些', - '提炼当前页的关键待办和行动项', - '基于当前页内容生成一个三段式摘要' - ]; - } - - function pageAiStorageKey() { - return 'hermes_page_ai_session:' + currentDocumentId(); - } - - function pageAiTimestamp(value) { - if (typeof value === 'number' && Number.isFinite(value)) return value; - if (typeof value === 'string' && value.trim()) { - var parsed = Date.parse(value); - if (Number.isFinite(parsed)) return parsed; - } - return Date.now(); - } - - function pageAiBackendSessionQuery(extra) { - var params = new URLSearchParams(); - params.set('source', 'acp'); - params.set('workspaceId', resolveWorkspaceId(document.body)); - params.set('documentId', currentDocumentId()); - params.set('profile', pageAiRunProfile()); - params.set('sourceKind', currentSourceKind()); - if (currentRootUri()) params.set('rootUri', currentRootUri()); - Object.keys(extra || {}).forEach(function(key) { - var value = extra[key]; - if (value !== undefined && value !== null && String(value).trim() !== '') { - params.set(key, String(value)); - } - }); - return params.toString(); - } - - function pageAiNewSession(title) { - var now = Date.now(); - return { - id: 'sess_' + now + '_' + Math.random().toString(16).slice(2, 8), - title: title || '新会话', - profile: pageAiCurrentProfile(), - acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', - createdAt: now, - updatedAt: now, - source: 'local', - usage: null, - status: 'idle', - messages: [] - }; - } - - function pageAiUsageSummary(usage) { - if (!usage || typeof usage !== 'object') return ''; - var used = usage.used ?? usage.contextUsed ?? usage.input_tokens ?? usage.inputTokens ?? usage.prompt_tokens ?? usage.promptTokens; - var size = usage.size ?? usage.contextSize ?? usage.total_tokens ?? usage.totalTokens; - var output = usage.output_tokens ?? usage.outputTokens ?? usage.completion_tokens ?? usage.completionTokens; - var parts = []; - if (Number.isFinite(Number(used))) parts.push('ctx ' + Number(used)); - if (Number.isFinite(Number(size)) && Number(size) > 0) parts.push('/ ' + Number(size)); - if (Number.isFinite(Number(output)) && Number(output) > 0) parts.push('out ' + Number(output)); - return parts.join(' ') || ''; - } - - function pageAiPermissionMessage(payload, eventType) { - payload = payload && typeof payload === 'object' ? payload : {}; - var permissionId = String(payload.permissionId || payload.permission_id || payload.id || ('perm_' + Date.now())).trim(); - var toolName = String(payload.toolName || payload.tool || payload.name || payload.method || 'session/request_permission').trim(); - var args = payload.arguments || payload.args || payload.input || payload.params || payload; - var decision = String(payload.decision || payload.result || '').trim(); - if (!decision && eventType === 'permission.denied') decision = 'denied'; - if (!decision && eventType === 'permission.allowed') decision = 'allowed'; - return { - role: 'tool', - kind: 'permission', - permissionId: permissionId, - toolName: toolName, - argsSummary: pageAiPreviewValue(args), - content: decision === 'denied' ? '已自动拒绝权限请求' : (decision === 'allowed' ? '已自动允许权限请求' : '等待权限确认'), - resolved: decision === 'denied' || decision === 'allowed', - decision: decision - }; - } - - function pageAiApplyPermissionEvent(eventName, payloadText) { - var payload = null; - try { - payload = JSON.parse(payloadText || 'null'); - } catch (_) { - payload = {}; - } - var message = pageAiPermissionMessage(payload, eventName); - var existing = pageUiState.pageAiMessages.find(function(item) { - return item.kind === 'permission' && item.permissionId === message.permissionId; - }); - if (existing) { - Object.assign(existing, message); - } else { - pageUiState.pageAiMessages.push(message); - } - pageUiState.pageAiPermissionRequests = pageUiState.pageAiPermissionRequests.filter(function(item) { - return item.permissionId !== message.permissionId; - }).concat([message]).slice(-20); - if (!message.resolved) { - pageAiShowPermissionDialog(message); - } else { - pageAiHidePermissionDialog(); - } - } - - function pageAiResolvePermission(permissionId, decision) { - permissionId = String(permissionId || '').trim(); - if (!permissionId) return; - // 调用后端 resolve-permission 端点,让 ACP agent 得到真实响应 - var runId = pageUiState.pageAiCurrentRunId; - if (runId) { - fetch('/api/hermes/client/runs/' + encodeURIComponent(runId) + '/resolve-permission', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ permissionId: permissionId, decision: decision }) - }).then(function(response) { - if (!response.ok) console.warn('resolve-permission 后端返回异常', response.status); - }).catch(function(err) { - console.warn('resolve-permission 请求失败', err); - }); - } else { - console.warn('resolve-permission: 无活跃 runId,只能本地更新'); - } - // 本地乐观更新 UI - pageUiState.pageAiMessages.forEach(function(item) { - if (item.kind === 'permission' && item.permissionId === permissionId) { - item.resolved = true; - item.decision = decision; - item.content = decision === 'allow' ? '已允许权限请求' : '已拒绝权限请求'; - } - }); - var dialog = document.querySelector('[data-page-ai-permission-dialog]'); - if (dialog instanceof HTMLElement) dialog.hidden = true; - renderPageAiConversation(); - } function pageAiOpenLocation(loc) { var path = String(loc || '').trim(); @@ -988,594 +717,56 @@ export function createSidebarPageAiRuntime(context) { }); } - function pageAiHidePermissionDialog() { - var dialog = document.querySelector('[data-page-ai-permission-dialog]'); - if (dialog instanceof HTMLElement) dialog.hidden = true; - } - - function pageAiShowPermissionDialog(message) { - if (!message || message.kind !== 'permission') return; - if (message.resolved) { - pageAiHidePermissionDialog(); - return; - } - var dialog = document.querySelector('[data-page-ai-permission-dialog]'); - if (!(dialog instanceof HTMLElement)) { - dialog = document.createElement('div'); - dialog.className = 'wolai-page-ai-permission-dialog'; - dialog.setAttribute('data-page-ai-permission-dialog', 'true'); - dialog.innerHTML = '' + - ''; - document.body.appendChild(dialog); - } - var tool = dialog.querySelector('[data-page-ai-permission-tool]'); - if (tool instanceof HTMLElement) tool.textContent = message.toolName || 'session/request_permission'; - var args = dialog.querySelector('[data-page-ai-permission-args]'); - if (args instanceof HTMLElement) args.textContent = message.argsSummary || message.content || ''; - dialog.querySelectorAll('[data-page-ai-permission-dialog-action]').forEach(function(button) { - if (button instanceof HTMLButtonElement) { - button.setAttribute('data-page-ai-permission-id', message.permissionId || ''); - button.disabled = Boolean(message.resolved); - } - }); - dialog.hidden = false; - } - - function pageAiNormalizeSessions(sessions) { - return (Array.isArray(sessions) ? sessions : []) - .slice(0, 20) - .map(function(session) { - var sessionStorage = String(session && (session.sessionStorage || session.session_storage) || '').trim(); - var persistence = String(session && session.persistence || '').trim(); - return { - id: String(session && session.id || '').trim() || pageAiNewSession().id, - title: String(session && session.title || '').trim() || '新会话', - profile: String(session && session.profile || pageAiCurrentProfile()).trim() || 'default', - acpRuntime: String(session && session.acpRuntime || 'reasonix').trim(), - createdAt: pageAiTimestamp(session && session.createdAt), - updatedAt: pageAiTimestamp(session && session.updatedAt), - source: String(session && session.source || 'local').trim() || 'local', - persistence: persistence, - sessionStorage: sessionStorage, - permissionLevel: String(session && (session.permissionLevel || session.permission_level) || '').trim(), - shareId: String(session && (session.shareId || session.share_id) || '').trim(), - runId: String(session && (session.runId || session.run_id) || '').trim(), - status: String(session && session.status || '').trim(), - usage: session && session.usage && typeof session.usage === 'object' ? session.usage : null, - preview: String(session && session.preview || '').trim(), - messages: Array.isArray(session && session.messages) ? session.messages.slice(-300) : [] - }; - }) - .sort(function(a, b) { - return Number(b.updatedAt || 0) - Number(a.updatedAt || 0); + function pageAiChatOnlyProfileEntries() { + var profiles = pageAiNormalizeArray(pageUiState.pageAiProfiles); + return PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY.map(function(spec) { + var profile = profiles.find(function(candidate) { + var candidateSpec = pageAiChatOnlyProfileSpec(candidate); + return candidateSpec && candidateSpec.profileId === spec.profileId; }); - } - - function pageAiNormalizeBackendSessionRow(row) { - if (!row || typeof row !== 'object') return null; - var payload = row.payload && typeof row.payload === 'object' ? row.payload : {}; - var sessionId = String(row.sessionId || row.session_id || '').trim(); - if (!sessionId) return null; - var title = String(row.title || payload.title || payload.message || '').trim(); - if (title.length > 28) title = title.slice(0, 28) + '…'; - var persistence = String(row.persistence || payload.persistence || '').trim(); - var sessionStorage = String(row.sessionStorage || row.session_storage || payload.sessionStorage || '').trim(); - if (!sessionStorage && persistence === 'convex_acp_runtime_store') sessionStorage = 'cloud'; - if (!sessionStorage && persistence === 'local_ai_session_jsonl') sessionStorage = String(row.shareId || payload.shareId || '').trim() ? 'local_shared' : 'local_private'; - return { - id: sessionId, - title: title || '当前页问答', - profile: String(row.profile || payload.profile || pageAiRunProfile()).trim() || 'default', - acpRuntime: String(row.acpRuntime || row.acp_runtime || payload.acpRuntime || 'reasonix').trim(), - createdAt: pageAiTimestamp(row.createdAt || row.created_at), - updatedAt: pageAiTimestamp(row.updatedAt || row.updated_at), - source: sessionStorage === 'local_private' || sessionStorage === 'local_shared' ? 'local' : 'acp', - persistence: persistence, - sessionStorage: sessionStorage, - permissionLevel: String(row.permissionLevel || row.permission_level || payload.permissionLevel || '').trim(), - shareId: String(row.shareId || row.share_id || payload.shareId || '').trim(), - runId: String(row.runId || row.run_id || '').trim(), - status: String(row.status || (row.runtime && row.runtime.status) || '').trim(), - usage: row.usage && typeof row.usage === 'object' ? row.usage : null, - preview: String(payload.message || row.snippet || '').trim(), - messages: [] - }; - } - - function pageAiMergeSessions(localSessions, backendSessions) { - var byId = {}; - pageAiNormalizeSessions(localSessions).forEach(function(session) { - byId[session.id] = session; + return Object.assign({}, profile || { + profileId: spec.profileId, + name: spec.profileId, + alias: spec.label, + kind: 'shared', + baseProfile: spec.baseProfile, + readonly: true + }, { menuLabel: spec.label }); }); - pageAiNormalizeSessions(backendSessions).forEach(function(session) { - var existing = byId[session.id]; - byId[session.id] = Object.assign({}, existing || {}, session, { - messages: session.messages.length ? session.messages : (existing && existing.messages || []) - }); + } + + function pageAiHermesProfileEntries() { + var profiles = pageAiNormalizeArray(pageUiState.pageAiProfiles).filter(function(profile) { + return !pageAiChatOnlyProfileSpec(profile); }); - return pageAiNormalizeSessions(Object.keys(byId).map(function(id) { return byId[id]; })); + if (profiles.length) return profiles; + return [{ profileId: pageAiCurrentProfile(), name: pageAiCurrentProfile(), alias: pageAiCurrentProfile(), kind: 'personal' }]; } - function pageAiSessionStorageLabel(session) { - var storage = String(session && (session.sessionStorage || session.session_storage) || '').trim(); - var persistence = String(session && session.persistence || '').trim(); - if (storage === 'local_shared') return '共享会话'; - if (storage === 'local_private') return '本地私有'; - if (storage === 'cloud' || persistence === 'convex_acp_runtime_store') return '云端会话'; - if (persistence === 'local_ai_session_jsonl') return '本地私有'; - return String(session && session.source || '').trim() === 'acp' ? '云端会话' : '本地私有'; - } - - function pageAiLoadSessions() { - if (pageUiState.pageAiSessions.length && pageUiState.pageAiActiveSessionId) return; - try { - var raw = window.localStorage.getItem(pageAiStorageKey()); - var parsed = raw ? JSON.parse(raw) : null; - var activeId = String(parsed && parsed.activeSessionId || '').trim(); - var activeProfile = String(parsed && parsed.activeProfileName || '').trim(); - var activeAcpRuntime = String(parsed && parsed.activeAcpRuntime || '').trim(); - var sessions = pageAiNormalizeSessions(parsed && parsed.sessions); - var storageVersion = Number(parsed && parsed.version || 0); - if (storageVersion >= PAGE_AI_SESSION_STORAGE_VERSION && activeAcpRuntime) pageUiState.pageAiAcpRuntime = activeAcpRuntime; - if (activeProfile) pageAiSetActiveProfile(activeProfile); - if (sessions.length) { - pageUiState.pageAiSessions = sessions; - var activeSession = sessions.find(function(session) { return session.id === activeId; }) || sessions[0]; - pageUiState.pageAiActiveSessionId = activeSession.id; - if (activeSession.profile) pageAiSetActiveProfile(activeSession.profile); - if (!pageUiState.pageAiAcpRuntime && activeSession.acpRuntime) pageUiState.pageAiAcpRuntime = activeSession.acpRuntime; - pageUiState.pageAiMessages = Array.isArray(activeSession.messages) ? activeSession.messages.slice() : []; - return; - } - if (activeId) { - pageUiState.pageAiSessions = [pageAiNewSession('当前页问答')]; - pageUiState.pageAiSessions[0].id = activeId; - pageUiState.pageAiActiveSessionId = activeId; - pageUiState.pageAiMessages = []; - return; - } - } catch (_) {} - var fresh = pageAiNewSession(); - pageUiState.pageAiSessions = [fresh]; - pageUiState.pageAiActiveSessionId = fresh.id; - pageUiState.pageAiMessages = []; - } - - async function pageAiLoadBackendSessions() { - var response = await fetch('/api/hermes/client/sessions?' + pageAiBackendSessionQuery({ limit: 50 }), { - headers: { 'accept': 'application/json' } - }); - var payload = await response.json().catch(function(){ return null; }); - if (!response.ok || !payload || payload.ok !== true) { - throw new Error(pageAiErrorMessage(payload, 'session_list_failed_' + response.status)); - } - var backendSessions = pageAiNormalizeArray(payload.sessions).map(pageAiNormalizeBackendSessionRow).filter(Boolean); - if (!backendSessions.length) return []; - pageUiState.pageAiSessions = pageAiMergeSessions(pageUiState.pageAiSessions, backendSessions); - if (!pageUiState.pageAiActiveSessionId || !pageUiState.pageAiSessions.find(function(session) { return session.id === pageUiState.pageAiActiveSessionId; })) { - pageUiState.pageAiActiveSessionId = pageUiState.pageAiSessions[0].id; - } - var active = pageAiCurrentSession(); - if (active) { - if (active.profile) pageAiSetActiveProfile(active.profile); - if (!pageUiState.pageAiAcpRuntime && active.acpRuntime) pageUiState.pageAiAcpRuntime = active.acpRuntime; - pageUiState.pageAiMessages = Array.isArray(active.messages) ? active.messages.slice() : pageUiState.pageAiMessages; - } - pageUiState.pageAiSessionError = ''; - pageAiPersistSessions(); - renderPageAiConversation(); - renderPageAiControls(); - return backendSessions; - } - - function pageAiMessageFromRuntimeEvent(event) { - if (!event || typeof event !== 'object') return null; - var eventType = String(event.eventType || event.event_type || event.event || '').trim(); - var payload = event.payload && typeof event.payload === 'object' ? event.payload : event; - if (eventType === 'message.delta') { - var delta = String(payload.delta || payload.text || payload.output_text || '').trim(); - return delta ? { role: 'assistant', content: delta } : null; - } - if (eventType === 'thought.delta') { - var thought = String(payload.delta || payload.text || '').trim(); - return thought ? { role: 'assistant', kind: 'thought', content: thought } : null; - } - if (eventType === 'tool.started' || eventType === 'tool.completed' || eventType === 'tool.failed') { - var toolName = String(payload.toolName || payload.tool || payload.name || eventType).trim(); - var rawLocations = payload.locations; - return { - role: 'tool', - content: toolName, - toolName: toolName, - toolCallId: String(payload.toolCallId || payload.tool_call_id || payload.id || ''), - toolKind: String(payload.kind || ''), - status: eventType === 'tool.completed' ? 'completed' : (eventType === 'tool.failed' ? 'failed' : 'running'), - argsSummary: pageAiPreviewValue(payload.args || payload.arguments || payload.input), - resultSummary: pageAiPreviewValue(payload.summary || payload.result || payload.output || payload.error), - locations: Array.isArray(rawLocations) ? rawLocations.filter(function(l) { return typeof l === 'string' || (typeof l === 'object' && l && l.path); }).map(function(l) { return typeof l === 'string' ? l : l.path; }) : [], - traceId: String(payload.traceId || payload.trace_id || ''), - auditId: String(payload.auditId || payload.audit_id || '') - }; - } - if (eventType === 'permission.requested' || eventType === 'permission.denied' || eventType === 'permission.allowed') { - return pageAiPermissionMessage(payload, eventType); - } - return null; - } - - function pageAiApplyBackendSessionDetail(payload) { - var sessionPayload = payload && payload.session ? payload.session : {}; - var runs = pageAiNormalizeArray(sessionPayload.runs); - var latest = runs.length ? pageAiNormalizeBackendSessionRow(runs[0]) : null; - var events = pageAiNormalizeArray(payload && payload.events); - var messages = pageAiNormalizeArray(sessionPayload.messages).map(function(message) { - return { - role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'), - content: String(message.content || '') - }; - }).filter(function(message) { return message.content; }); - events.forEach(function(event) { - var message = pageAiMessageFromRuntimeEvent(event); - if (message) messages.push(message); - }); - var current = pageAiCurrentSession(); - if (latest && current) { - Object.assign(current, latest); - } - if (current) { - current.messages = messages.slice(-300); - current.updatedAt = Math.max(Number(current.updatedAt || 0), Date.now()); - if (latest && latest.usage) current.usage = latest.usage; - } - pageAiApplyRuntimeState(payload && payload.runtime); - pageUiState.pageAiMessages = messages.slice(-300); - pageAiPersistSessions(); - } - - async function pageAiLoadBackendSessionDetail(sessionId) { - sessionId = String(sessionId || pageUiState.pageAiActiveSessionId || '').trim(); - if (!sessionId) return null; - var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '?' + pageAiBackendSessionQuery({ limit: 200 }), { - headers: { 'accept': 'application/json' } - }); - var payload = await response.json().catch(function(){ return null; }); - if (!response.ok || !payload || payload.ok !== true) { - throw new Error(pageAiErrorMessage(payload, 'session_detail_failed_' + response.status)); - } - pageAiApplyBackendSessionDetail(payload); - renderPageAiConversation(); - renderPageAiControls(); - return payload; - } - - async function pageAiSearchBackendSessions(query) { - var q = String(query || '').trim(); - pageUiState.pageAiSessionSearchQuery = q; - if (!q) { - pageUiState.pageAiSessionSearchResults = []; - renderPageAiConversation(); - return []; - } - var response = await fetch('/api/hermes/client/sessions/search?' + pageAiBackendSessionQuery({ q: q, limit: 20 }), { - headers: { 'accept': 'application/json' } - }); - var payload = await response.json().catch(function(){ return null; }); - if (!response.ok || !payload || payload.ok !== true) { - throw new Error(pageAiErrorMessage(payload, 'session_search_failed_' + response.status)); - } - pageUiState.pageAiSessionSearchResults = pageAiNormalizeArray(payload.results).map(function(row) { - var normalized = pageAiNormalizeBackendSessionRow(row) || {}; - normalized.snippet = String(row && row.snippet || normalized.preview || '').trim(); - return normalized; - }).filter(function(row) { return row.id; }); - renderPageAiConversation(); - return pageUiState.pageAiSessionSearchResults; - } - - function pageAiPersistSessions() { - document.documentElement.setAttribute('data-mnote-page-ai-session-owner', 'hermes'); - document.documentElement.setAttribute('data-mnote-page-ai-session-key', pageAiStorageKey()); - try { - pageAiSyncCurrentSessionMessages(); - window.localStorage.setItem(pageAiStorageKey(), JSON.stringify({ - version: PAGE_AI_SESSION_STORAGE_VERSION, - activeSessionId: pageUiState.pageAiActiveSessionId, - activeProfileName: pageAiCurrentProfile(), - activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', - sessions: pageAiNormalizeSessions(pageUiState.pageAiSessions) - })); - } catch (_) {} - } - - async function pageAiEnsureHermesSession(forceCreate) { - pageAiLoadSessions(); - var current = pageAiCurrentSession(); - if (!forceCreate && current && String(current.id || '').startsWith('mnote_') && current.profile === pageAiCurrentProfile()) return current; - var response = await fetch('/api/hermes/client/sessions', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - workspaceId: resolveWorkspaceId(document.body), - documentId: currentDocumentId(), - sourceKind: currentSourceKind(), - rootUri: currentRootUri(), - traceId: 'page-ai-' + Date.now().toString(36), - profile: pageAiCurrentProfile(), - title: current && current.title ? current.title : '当前页问答' - }) - }); - var payload = await response.json().catch(function(){ return null; }); - if (!response.ok || !payload || payload.ok !== true) { - throw new Error(pageAiErrorMessage(payload, 'hermes_session_failed_' + response.status)); - } - var session = { - id: String(payload.sessionId || '').trim(), - title: String(payload.title || '当前页问答'), - profile: String(payload.profile || pageAiCurrentProfile()).trim() || 'default', - acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', - persistence: String(payload.persistence || '').trim(), - sessionStorage: String(payload.sessionStorage || '').trim(), - permissionLevel: String(payload.permissionLevel || '').trim(), - shareId: String(payload.shareId || '').trim(), - createdAt: Date.now(), - updatedAt: Date.now(), - messages: pageUiState.pageAiMessages.slice() - }; - pageUiState.pageAiSessions = pageAiNormalizeSessions([session]); - pageUiState.pageAiActiveSessionId = session.id; - pageAiPersistSessions(); - renderPageAiConversation(); - return session; - } - - async function pageAiRestoreHermesSession() { - var current = pageAiCurrentSession(); - if (!current || !String(current.id || '').startsWith('mnote_')) return; - var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(current.id) + '/resume?' + pageAiBackendSessionQuery({}), { - method: 'POST', - headers: { 'accept': 'application/json' } - }); - if (!response.ok) return; - var payload = await response.json().catch(function(){ return null; }); - if (payload && payload.persistence === 'convex_acp_runtime_store') { - pageAiApplyBackendSessionDetail(payload); - renderPageAiConversation(); - renderPageAiControls(); - return; - } - var session = payload && (payload.session || (payload.upstream && payload.upstream.session) || payload); - var messages = session && Array.isArray(session.messages) ? session.messages : []; - pageAiApplyRuntimeState(payload && payload.runtime); - if (session && (session.profile || session.profileName)) { - current.profile = String(session.profile || session.profileName || current.profile || pageAiCurrentProfile()); - } - if (!messages.length) { - renderPageAiControls(); - return; - } - pageUiState.pageAiMessages = messages.slice(-300).map(function(message) { - return { - role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'), - content: String(message.content || '') - }; - }); - current.messages = pageUiState.pageAiMessages.slice(); - current.updatedAt = Date.now(); - renderPageAiConversation(); - renderPageAiControls(); - } - - function pageAiCurrentSession() { - return pageUiState.pageAiSessions.find(function(session) { - return session.id === pageUiState.pageAiActiveSessionId; - }) || null; - } - - function pageAiSyncCurrentSessionMessages() { - var session = pageAiCurrentSession(); - if (!session) return; - session.messages = Array.isArray(pageUiState.pageAiMessages) ? pageUiState.pageAiMessages.slice(-300) : []; - session.profile = pageAiCurrentProfile(); - session.acpRuntime = pageUiState.pageAiAcpRuntime || 'reasonix'; - session.updatedAt = Date.now(); - } - - function pageAiSetActiveSession(sessionId) { - var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; }); - if (!session) return; - pageUiState.pageAiActiveSessionId = session.id; - pageUiState.pageAiMessages = Array.isArray(session.messages) ? session.messages.slice() : []; - pageUiState.pageAiPage = 'chat'; - pageAiPersistSessions(); - renderPageAiControls(); - renderPageAiConversation(); - if (session.source === 'acp' || String(session.id || '').startsWith('mnote_')) { - void pageAiLoadBackendSessionDetail(session.id).catch(function(error) { - pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); - renderPageAiControls(); - }); - } - } - - function pageAiStartNewSession() { - var session = pageAiNewSession(); - pageUiState.pageAiSessions = pageAiNormalizeSessions([session].concat(pageUiState.pageAiSessions)); - pageUiState.pageAiActiveSessionId = session.id; - pageUiState.pageAiMessages = []; - pageUiState.pageAiPage = 'chat'; - pageAiPersistSessions(); - renderPageAiControls(); - renderPageAiConversation(); - } - - async function pageAiRenameBackendSession(sessionId) { - var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; }); - if (!session) return; - var title = window.prompt('重命名 AI 会话', session.title || '当前页问答'); - if (title === null) return; - title = String(title || '').trim(); - if (!title) return; - var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/rename?' + pageAiBackendSessionQuery({}), { - method: 'POST', - headers: { 'content-type': 'application/json', 'accept': 'application/json' }, - body: JSON.stringify({ title: title }) - }); - var payload = await response.json().catch(function(){ return null; }); - if (!response.ok || !payload || payload.ok !== true) { - throw new Error(pageAiErrorMessage(payload, 'session_rename_failed_' + response.status)); - } - session.title = String((payload.result && payload.result.title) || title); - session.updatedAt = Date.now(); - pageAiPersistSessions(); - renderPageAiConversation(); - renderPageAiControls(); - } - - async function pageAiDeleteBackendSession(sessionId) { - var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; }); - if (!session) return; - if (!window.confirm('确定删除 AI 会话“' + (session.title || sessionId) + '”吗?')) return; - var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '?' + pageAiBackendSessionQuery({}), { - method: 'DELETE', - headers: { 'accept': 'application/json' } - }); - var payload = await response.json().catch(function(){ return null; }); - if (!response.ok || !payload || payload.ok !== true) { - throw new Error(pageAiErrorMessage(payload, 'session_delete_failed_' + response.status)); - } - pageUiState.pageAiSessions = pageUiState.pageAiSessions.filter(function(item) { return item.id !== sessionId; }); - if (pageUiState.pageAiActiveSessionId === sessionId) { - var next = pageUiState.pageAiSessions[0] || pageAiNewSession(); - if (!pageUiState.pageAiSessions.length) pageUiState.pageAiSessions = [next]; - pageUiState.pageAiActiveSessionId = next.id; - pageUiState.pageAiMessages = Array.isArray(next.messages) ? next.messages.slice() : []; - } - pageAiPersistSessions(); - renderPageAiConversation(); - renderPageAiControls(); - } - - async function pageAiResumeBackendSession(sessionId) { - sessionId = String(sessionId || pageUiState.pageAiActiveSessionId || '').trim(); - if (!sessionId) return; - pageAiSetActiveSession(sessionId); - var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/resume?' + pageAiBackendSessionQuery({}), { - method: 'POST', - headers: { 'accept': 'application/json' } - }); - var payload = await response.json().catch(function(){ return null; }); - if (!response.ok || !payload || payload.ok !== true) { - throw new Error(pageAiErrorMessage(payload, 'session_resume_failed_' + response.status)); - } - pageAiApplyBackendSessionDetail(payload); - pageUiState.pageAiPage = 'chat'; - renderPageAiConversation(); - renderPageAiControls(); - } - - function pageAiProviderLabel(provider) { - if (provider === 'codex') return 'Codex'; - if (provider === 'claudecode') return 'ClaudeCode'; - return 'Hermes'; - } - - function pageAiNormalizeArray(value) { - return Array.isArray(value) ? value : []; - } - - function pageAiDefaultAcpRuntimes() { - return [ - { - name: 'reasonix', - title: 'ACP · Reasonix', - description: '通过 ACP 协议直连 Reasonix(DeepSeek 缓存优先)', - model: 'deepseek-chat', - preset: 'auto' - }, - { - name: 'hermes', - title: 'ACP · Hermes', - description: '通过 ACP 协议直连 Hermes agent runtime' - } - ]; - } - - function pageAiNormalizeAcpRuntimes(runtimes) { - var byName = {}; - pageAiDefaultAcpRuntimes().forEach(function(runtime) { - byName[runtime.name] = Object.assign({}, runtime); - }); - pageAiNormalizeArray(runtimes).forEach(function(runtime) { - var name = String(runtime && runtime.name || '').trim(); - if (name !== 'reasonix' && name !== 'hermes') return; - byName[name] = Object.assign({}, byName[name] || {}, runtime, { name: name }); - }); - return ['reasonix', 'hermes'].map(function(name) { return byName[name]; }).filter(Boolean); - } - - function pageAiUnwrapUpstream(payload) { - if (payload && typeof payload === 'object' && payload.upstream) return payload.upstream; - return payload || null; - } - - function pageAiProfileValue(profile) { - if (profile && typeof profile === 'object') { - return String(profile.name || profile.profile || profile.id || '').trim(); - } - return String(profile || '').trim(); - } - - function pageAiCurrentProfile() { - var active = String(pageUiState.pageAiActiveProfileName || '').trim(); - if (active) return active; - var selected = pageUiState.pageAiProfiles.find(function(profile) { - return profile && profile.active; - }); - return pageAiProfileValue(selected) || 'mnoteai'; - } - - function pageAiRunProfile() { - return String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix' ? 'reasonix' : pageAiCurrentProfile(); - } - - function pageAiMnoteToolModel() { - return String(document.documentElement.getAttribute('data-mnote-page-ai-tool-model') || 'deepseek-v4-flash').trim() || 'deepseek-v4-flash'; - } - - function pageAiCurrentProfileRecord() { - var active = pageAiCurrentProfile(); - return pageUiState.pageAiProfiles.find(function(profile) { - return pageAiProfileValue(profile) === active; - }) || null; - } - - function pageAiCurrentModelLabel() { - var profile = pageAiCurrentProfileRecord(); - var toolModel = pageAiMnoteToolModel(); - if (!profile) return 'tool: ' + toolModel; - var model = String(profile.model || '').trim(); - var gateway = String(profile.gateway || '').trim(); - var profileLabel = [model, gateway].filter(Boolean).join(' · ') || '由 Hermes 决定'; - return 'tool: ' + toolModel + ' · profile: ' + profileLabel; - } function pageAiNormalizeProfiles(payload) { var upstream = pageAiUnwrapUpstream(payload); var profiles = pageAiNormalizeArray(upstream && upstream.profiles ? upstream.profiles : upstream); return profiles.map(function(profile) { + var profileId = String(profile && (profile.profileId || profile.id || profile.name) || '').trim(); + var kind = String(profile && profile.kind || profile && profile.profileKind || '').trim(); + var canManageSkills = profile && profile.canManageSkills !== false; return { - name: pageAiProfileValue(profile) || 'default', + profileId: profileId || pageAiProfileValue(profile) || 'default', + name: profileId || pageAiProfileValue(profile) || 'default', active: Boolean(profile && profile.active), model: String(profile && profile.model || '').trim(), gateway: String(profile && profile.gateway || '').trim(), - alias: String(profile && profile.alias || '').trim() + alias: String(profile && (profile.displayName || profile.alias) || '').trim(), + kind: kind, + ownerUserId: String(profile && profile.ownerUserId || '').trim(), + baseProfile: String(profile && profile.baseProfile || '').trim(), + isolatedProfile: String(profile && profile.isolatedProfile || '').trim(), + canRun: profile ? profile.canRun !== false : true, + canManageSkills: canManageSkills, + canManageConfig: profile ? profile.canManageConfig !== false : canManageSkills, + readonly: profile ? (profile.readonly === true || !canManageSkills) : false, + grantRole: String(profile && profile.grantRole || '').trim() }; }); } @@ -1602,6 +793,12 @@ export function createSidebarPageAiRuntime(context) { createdBy: String(skill && skill.createdBy || '').trim(), patchCount: Number(skill && skill.patchCount || 0), modified: Boolean(skill && skill.modified), + builtin: Boolean(skill && skill.builtin), + configurable: skill && skill.configurable !== false, + configScope: String(skill && skill.configScope || '').trim(), + skillKind: String(skill && skill.skillKind || '').trim(), + profileId: String(skill && skill.profileId || '').trim(), + readOnly: Boolean(skill && (skill.readOnly || skill.readonly || skill.configurable === false)), category: String(category && category.name || '').trim() }; }) @@ -1619,7 +816,13 @@ export function createSidebarPageAiRuntime(context) { origin: String(skill && skill.origin || '').trim(), createdBy: String(skill && skill.createdBy || '').trim(), patchCount: Number(skill && skill.patchCount || 0), - modified: Boolean(skill && skill.modified) + modified: Boolean(skill && skill.modified), + builtin: Boolean(skill && skill.builtin), + configurable: skill && skill.configurable !== false, + configScope: String(skill && skill.configScope || '').trim(), + skillKind: String(skill && skill.skillKind || '').trim(), + profileId: String(skill && skill.profileId || '').trim(), + readOnly: Boolean(skill && (skill.readOnly || skill.readonly || skill.configurable === false)) }; }) }; @@ -1641,7 +844,13 @@ export function createSidebarPageAiRuntime(context) { origin: skill.origin || '', createdBy: skill.createdBy || '', patchCount: Number(skill.patchCount || 0), - modified: Boolean(skill.modified) + modified: Boolean(skill.modified), + builtin: Boolean(skill.builtin), + configurable: skill.configurable !== false, + configScope: skill.configScope || '', + skillKind: skill.skillKind || '', + profileId: skill.profileId || '', + readOnly: Boolean(skill.readOnly || skill.readonly || skill.configurable === false) }); }); }); @@ -1717,24 +926,6 @@ export function createSidebarPageAiRuntime(context) { return String(name || '').trim().replace(/_/g, '.'); } - function pageAiFormatChangedFiles(files) { - return pageAiNormalizeArray(files).map(function(file) { - var path = String(file && file.path || '').trim(); - var changeType = String(file && file.changeType || file.change_type || 'modified').trim(); - var summary = String(file && file.summary || '').trim(); - var version = String(file && (file.version || file.revision || '') || '').trim(); - var hashBefore = String(file && file.hashBefore || '').trim(); - var hashAfter = String(file && file.hashAfter || '').trim(); - if (!version && (hashBefore || hashAfter)) version = 'hash ' + [hashBefore || '0', hashAfter || '0'].join('→'); - var modifiedBefore = Number(file && file.modifiedBeforeMs || 0) || 0; - var modifiedAfter = Number(file && file.modifiedAfterMs || 0) || 0; - if (!version && (modifiedBefore || modifiedAfter)) version = 'mtime ' + [String(modifiedBefore), String(modifiedAfter)].join('→'); - var actor = [file && file.agentKind, file && file.actorType, file && file.actorId].map(function(value) { - return String(value || '').trim(); - }).filter(Boolean).join('/'); - return [changeType, path, version, summary, actor].filter(Boolean).join(' · '); - }).filter(Boolean).join('\n'); - } function pageAiParentRelativePath(path) { var normalized = String(path || '').trim().replace(/\\/g, '/').replace(/^\/+/, ''); @@ -1976,28 +1167,6 @@ export function createSidebarPageAiRuntime(context) { document.documentElement.setAttribute('data-mnote-page-ai-context-scope', next); } - function pageAiRunStatusLabel(status) { - if (status === 'queued') return '排队中'; - if (status === 'running') return '运行中'; - if (status === 'tool_calling') return '调用工具'; - if (status === 'completed') return '已完成'; - if (status === 'failed') return '失败'; - if (status === 'aborted') return '已停止'; - return '空闲'; - } - - function pageAiMemoryFileLabel(section) { - if (section === 'soul') return 'SOUL.md'; - if (section === 'user') return 'USER.md'; - return 'MEMORY.md'; - } - - function pageAiContextScopeLabel(scope) { - if (scope === 'selection') return '当前选区'; - if (scope === 'block') return '当前块'; - if (scope === 'options') return '页面设置'; - return '当前页'; - } function pageAiHermesSettingsUrl() { var configured = String(window.__mnoteHermesSettingsUrl || '').trim(); @@ -2128,171 +1297,6 @@ export function createSidebarPageAiRuntime(context) { renderPageAiConversation(); } - function pageAiFilteredSkillEntries() { - var query = String(pageUiState.pageAiSkillQuery || '').trim().toLowerCase(); - return pageAiAllSkillEntries().filter(function(skill) { - if (skill.group === 'hermes' && pageAiHideHermesBuiltinSkills(pageAiCurrentProfile()) && skill.builtin === true) return false; - if (!query) return true; - return String(skill.name || skill.id || '').toLowerCase().indexOf(query) >= 0 - || String(skill.title || '').toLowerCase().indexOf(query) >= 0 - || String(skill.description || '').toLowerCase().indexOf(query) >= 0 - || String(skill.category || '').toLowerCase().indexOf(query) >= 0 - || pageAiSkillOriginLabel(skill).toLowerCase().indexOf(query) >= 0; - }); - } - - function pageAiSkillOriginLabel(skill) { - var origin = String(skill && skill.origin || '').trim(); - if (origin === 'generated' || String(skill && skill.createdBy || '') === 'agent') return '生成'; - if (origin === 'installed') return '安装'; - if (origin === 'builtin') return '内置'; - if (origin === 'copied') return '本地'; - var source = String(skill && skill.source || '').trim(); - if (source === 'hub') return '安装'; - if (source === 'builtin') return '内置'; - if (source === 'reasonix') { - if (origin === 'project') return 'Reasonix 项目'; - if (origin === 'global') return 'Reasonix 全局'; - return 'Reasonix'; - } - return '本地'; - } - - function pageAiSkillPreferenceKey(group, profile) { - var groupName = String(group || '').trim(); - if (groupName === 'mnote') return 'ai.common.skills.mnote.enabled'; - if (groupName === 'reasonix') return 'ai.agent.reasonix.skills.enabled'; - if (groupName === 'hermes') { - var nextProfile = String(profile || pageAiCurrentProfile() || 'default').trim() || 'default'; - return 'ai.agent.hermes.profile.' + nextProfile + '.skills.enabled'; - } - return ''; - } - - function pageAiSkillPreferenceTable(group, profile) { - var key = pageAiSkillPreferenceKey(group, profile); - var preferences = pageUiState.pageAiSkillPreferences || {}; - var value = preferences[key]; - if (value && typeof value === 'object' && !Array.isArray(value)) return value; - return {}; - } - - function pageAiHermesHideBuiltinPreferenceKey(profile) { - var nextProfile = String(profile || pageAiCurrentProfile() || 'default').trim() || 'default'; - return 'ai.agent.hermes.profile.' + nextProfile + '.skills.hide_builtin'; - } - - function pageAiHideHermesBuiltinSkills(profile) { - var preferences = pageUiState.pageAiSkillPreferences || {}; - return preferences[pageAiHermesHideBuiltinPreferenceKey(profile)] === true; - } - - function pageAiSetHideHermesBuiltinSkills(enabled, profile) { - var key = pageAiHermesHideBuiltinPreferenceKey(profile); - var preferences = Object.assign({}, pageUiState.pageAiSkillPreferences || {}); - preferences[key] = Boolean(enabled); - pageUiState.pageAiSkillPreferences = preferences; - pageAiPersistRawAiPreference(key, Boolean(enabled)); - renderPageAiControls(); - } - - function pageAiSkillIsBuiltin(skill) { - var origin = String(skill && skill.origin || '').trim(); - var source = String(skill && skill.source || '').trim(); - return origin === 'builtin' || source === 'builtin'; - } - - function pageAiToggleableSkillEntries(group, profile) { - var catalog = pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs[group] - ? pageUiState.pageAiSkillCatalogs[group] - : { categories: [], archived: [] }; - var overrides = pageAiSkillPreferenceTable(group, profile); - var result = []; - pageAiNormalizeArray(catalog.categories).forEach(function(category) { - pageAiNormalizeArray(category.skills).forEach(function(skill) { - var id = String(skill.id || skill.name || '').trim(); - if (!id) return; - result.push({ - group: group, - profile: profile || '', - category: category.name, - id: id, - name: skill.name || id, - title: skill.title || skill.name || id, - description: skill.description || '', - enabled: group === 'hermes' ? skill.enabled !== false : overrides[id] !== false, - toggleable: skill.toggleable !== false, - source: skill.source || group, - origin: skill.origin || '', - readOnly: skill.readOnly === true, - builtin: pageAiSkillIsBuiltin(skill), - toolNames: skill.toolNames || [], - requiresContextRefs: skill.requiresContextRefs || [] - }); - }); - }); - pageAiNormalizeArray(catalog.archived).forEach(function(skill) { - var id = String(skill.id || skill.name || '').trim(); - if (!id) return; - result.push({ - group: group, - profile: profile || '', - category: 'archived', - id: id, - name: skill.name || id, - title: skill.title || skill.name || id, - description: skill.description || '', - enabled: group === 'hermes' ? skill.enabled !== false : overrides[id] !== false, - toggleable: skill.toggleable !== false, - source: skill.source || group, - origin: skill.origin || '', - readOnly: skill.readOnly === true, - builtin: pageAiSkillIsBuiltin(skill), - toolNames: skill.toolNames || [], - requiresContextRefs: skill.requiresContextRefs || [] - }); - }); - return result; - } - - function pageAiAllSkillEntries() { - return [] - .concat(pageAiToggleableSkillEntries('mnote', '')) - .concat(pageAiToggleableSkillEntries('reasonix', '')) - .concat(pageAiToggleableSkillEntries('hermes', pageAiCurrentProfile())); - } - - function pageAiSkillGroupCollapsed(group) { - var table = pageUiState.pageAiCollapsedSkillGroups || {}; - return table[String(group || '').trim()] === true; - } - - function pageAiToggleSkillGroup(group) { - var normalized = String(group || '').trim(); - if (!normalized) return; - var table = Object.assign({}, pageUiState.pageAiCollapsedSkillGroups || {}); - table[normalized] = table[normalized] !== true; - pageUiState.pageAiCollapsedSkillGroups = table; - pageAiPersistRawAiPreference('ai.common.skills.groups.collapsed', table); - renderPageAiControls(); - } - - function pageAiSetSkillPreference(group, skillId, enabled, profile) { - var key = pageAiSkillPreferenceKey(group, profile); - if (!key || !skillId) return; - var preferences = Object.assign({}, pageUiState.pageAiSkillPreferences || {}); - var current = preferences[key] && typeof preferences[key] === 'object' && !Array.isArray(preferences[key]) - ? Object.assign({}, preferences[key]) - : {}; - current[skillId] = Boolean(enabled); - preferences[key] = current; - pageUiState.pageAiSkillPreferences = preferences; - pageAiPersistRawAiPreference(key, current); - } - - function pageAiSkillEnabled(skill) { - return skill.enabled !== false; - } function pageAiLoadSkillCatalog(runtime, profile) { var params = new URLSearchParams(); @@ -2302,7 +1306,7 @@ export function createSidebarPageAiRuntime(context) { } else if (runtime === 'reasonix') { params.set('runtime', 'reasonix'); } else if (runtime === 'hermes' && profile) { - params.set('profile', profile); + params.set('profileId', profile); } return fetch('/api/hermes/client/skills?' + params.toString(), { headers: { 'accept': 'application/json' } @@ -2316,25 +1320,31 @@ export function createSidebarPageAiRuntime(context) { async function pageAiLoadProfiles() { try { - var response = await fetch('/api/hermes/client/profiles', { + var response = await fetch('/api/ai/agent-profiles?agentId=hermes', { headers: { 'accept': 'application/json' } }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profiles_failed_' + response.status)); var profiles = pageAiNormalizeProfiles(payload); - pageUiState.pageAiProfiles = profiles.length ? profiles : [{ name: 'mnoteai', active: true }]; - var acpRuntimes = Array.isArray(payload.acpRuntimes) ? payload.acpRuntimes : []; - pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(acpRuntimes); + pageUiState.pageAiProfiles = profiles.length ? profiles : [{ profileId: 'shared_lite', name: 'shared_lite', alias: 'Lite', kind: 'shared', readonly: true, canManageSkills: false }]; + try { + var runtimeResponse = await fetch('/api/hermes/client/profiles', { headers: { 'accept': 'application/json' } }); + var runtimePayload = await runtimeResponse.json().catch(function(){ return null; }); + var acpRuntimes = Array.isArray(runtimePayload && runtimePayload.acpRuntimes) ? runtimePayload.acpRuntimes : []; + pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(acpRuntimes); + } catch (_) { + pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(pageUiState.pageAiAcpRuntimes); + } var current = pageAiCurrentProfile(); var hasCurrent = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === current; }); - var hasMnoteAi = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === 'mnoteai'; }); + var personal = pageUiState.pageAiProfiles.find(function(profile) { return profile.kind === 'personal'; }); var active = pageAiProfileValue(pageUiState.pageAiProfiles.find(function(profile) { return profile.active; })); - pageAiSetActiveProfile(hasCurrent && current !== 'default' ? current : (hasMnoteAi ? 'mnoteai' : (active || current))); + pageAiSetActiveProfile(hasCurrent && current !== 'default' ? current : (active || pageAiProfileValue(personal) || pageAiProfileValue(pageUiState.pageAiProfiles[0]) || current)); pageUiState.pageAiProfileError = ''; void pageAiLoadGatewayHealth(); } catch (error) { pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error); - if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ name: 'mnoteai', active: true }]; + if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ profileId: 'shared_lite', name: 'shared_lite', alias: 'Lite', kind: 'shared', readonly: true, canManageSkills: false }]; pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(pageUiState.pageAiAcpRuntimes); pageAiSetActiveProfile(pageAiCurrentProfile()); } @@ -2345,8 +1355,13 @@ export function createSidebarPageAiRuntime(context) { async function pageAiSwitchProfile(profileName) { var next = String(profileName || '').trim(); if (!next) return; + var previousSkillSource = pageAiCurrentSkillSource(); pageAiSetActiveProfile(next); pageAiPersistRawAiPreference('ai.agent.hermes.profile_id', next); + if (previousSkillSource.indexOf('hermes:') === 0) { + pageUiState.pageAiActiveSkillSource = 'hermes:' + next; + pageAiPersistAiPreference('skills.active_source', pageUiState.pageAiActiveSkillSource); + } pageUiState.pageAiSkills = { categories: [], archived: [] }; pageUiState.pageAiSkillCatalogs = Object.assign({}, pageUiState.pageAiSkillCatalogs || {}, { hermes: { categories: [], archived: [] } @@ -2355,13 +1370,6 @@ export function createSidebarPageAiRuntime(context) { renderPageAiProviderButtons(); renderPageAiControls(); try { - var response = await fetch('/api/hermes/client/profiles/active', { - method: 'PUT', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ name: next }) - }); - var payload = await response.json().catch(function(){ return null; }); - if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profile_switch_failed_' + response.status)); pageUiState.pageAiSessions = pageUiState.pageAiSessions.filter(function(session) { return session && session.profile === next; }); @@ -2382,7 +1390,7 @@ export function createSidebarPageAiRuntime(context) { async function pageAiLoadProfileMemory() { try { - var response = await fetch('/api/hermes/client/profile-memory?profile=' + encodeURIComponent(pageAiCurrentProfile()), { + var response = await fetch('/api/hermes/client/profile-memory?profileId=' + encodeURIComponent(pageAiCurrentProfile()), { headers: { 'accept': 'application/json' } }); var payload = await response.json().catch(function(){ return null; }); @@ -2405,7 +1413,7 @@ export function createSidebarPageAiRuntime(context) { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ - profile: pageAiCurrentProfile(), + profileId: pageAiCurrentProfile(), section: normalized, content: content }) @@ -2423,25 +1431,23 @@ export function createSidebarPageAiRuntime(context) { } async function pageAiLoadSkills() { - var requestedProfile = pageAiCurrentProfile(); + var requestedSource = pageAiCurrentSkillSource(); + var sourceParts = pageAiSkillSourceParts(requestedSource); var loadSeq = (Number(pageUiState.pageAiSkillLoadSeq || 0) || 0) + 1; pageUiState.pageAiSkillLoadSeq = loadSeq; try { - var catalogs = await Promise.all([ - pageAiLoadSkillCatalog('mnote', ''), - pageAiLoadSkillCatalog('reasonix', ''), - pageAiLoadSkillCatalog('hermes', requestedProfile) - ]); - if (pageUiState.pageAiSkillLoadSeq !== loadSeq || pageAiCurrentProfile() !== requestedProfile) return; - pageUiState.pageAiSkillCatalogs = { - mnote: catalogs[0], - reasonix: catalogs[1], - hermes: catalogs[2] - }; - pageUiState.pageAiSkills = catalogs[2]; + var catalog = await pageAiLoadSkillCatalog(sourceParts.group, sourceParts.profile || pageAiCurrentProfile()); + if (pageUiState.pageAiSkillLoadSeq !== loadSeq + || pageAiCurrentSkillSource() !== requestedSource) return; + pageUiState.pageAiSkillCatalogs = Object.assign({}, pageUiState.pageAiSkillCatalogs || {}, { + [sourceParts.group]: catalog, + [requestedSource]: catalog + }); + pageUiState.pageAiSkills = catalog; pageUiState.pageAiSkillError = ''; } catch (error) { - if (pageUiState.pageAiSkillLoadSeq !== loadSeq || pageAiCurrentProfile() !== requestedProfile) return; + if (pageUiState.pageAiSkillLoadSeq !== loadSeq + || pageAiCurrentSkillSource() !== requestedSource) return; pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error); pageUiState.pageAiSkills = { categories: [], archived: [] }; pageUiState.pageAiSkillCatalogs = pageUiState.pageAiSkillCatalogs || { mnote: { categories: [], archived: [] }, reasonix: { categories: [], archived: [] }, hermes: { categories: [], archived: [] } }; @@ -2455,14 +1461,40 @@ export function createSidebarPageAiRuntime(context) { if (!name) return; var skillGroup = String(group || 'hermes').trim() || 'hermes'; var skillProfile = String(profile || pageAiCurrentProfile() || '').trim(); - if (skillGroup === 'mnote' || skillGroup === 'reasonix') { + if (skillGroup === 'reasonix') { pageAiSetSkillPreference(skillGroup, name, Boolean(enabled), skillProfile); document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', skillGroup + ':' + name); renderPageAiControls(); return; } + if (skillGroup === 'mnote') { + try { + var mnoteResponse = await fetch('/api/hermes/client/skills/toggle', { + method: 'PUT', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + skillKind: 'mnote_builtin', + name: name, + enabled: Boolean(enabled) + }) + }); + var mnotePayload = await mnoteResponse.json().catch(function(){ return null; }); + if (!mnoteResponse.ok) throw new Error(pageAiErrorMessage(mnotePayload, 'mnote_skill_toggle_failed_' + mnoteResponse.status)); + pageAiSetSkillPreference(skillGroup, name, Boolean(enabled), skillProfile); + pageAiNormalizeArray(pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs.mnote && pageUiState.pageAiSkillCatalogs.mnote.categories).forEach(function(category) { + pageAiNormalizeArray(category.skills).forEach(function(skill) { + if (skill.id === name || skill.name === name) skill.enabled = Boolean(enabled); + }); + }); + document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', skillGroup + ':' + name); + } catch (error) { + pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error); + } + renderPageAiControls(); + return; + } var previous = null; - pageAiToggleableSkillEntries('hermes', pageAiCurrentProfile()).forEach(function(skill) { + pageAiToggleableSkillEntries('hermes', skillProfile || pageAiCurrentProfile()).forEach(function(skill) { if (skill.id === name || skill.name === name) previous = skill.enabled !== false; }); try { @@ -2471,6 +1503,8 @@ export function createSidebarPageAiRuntime(context) { headers: { 'content-type': 'application/json' }, body: JSON.stringify({ profile: skillProfile || pageAiCurrentProfile(), + profileId: skillProfile || pageAiCurrentProfile(), + skillKind: 'hermes_profile', name: name, enabled: Boolean(enabled) }) @@ -2545,773 +1579,6 @@ export function createSidebarPageAiRuntime(context) { renderPageAiControls(); } - function renderPageAiProviderButtons() { - var drawer = ensurePageAiDrawer(); - var isAcp = pageUiState.pageAiAcpRuntime !== ''; - drawer.querySelectorAll('[data-page-ai-provider]').forEach(function(button) { - var provider = button.getAttribute('data-page-ai-provider') || 'hermes'; - var active = provider === pageUiState.pageAiProvider; - button.classList.toggle('is-active', active); - button.setAttribute('aria-pressed', active ? 'true' : 'false'); - }); - var providerNode = drawer.querySelector('.wolai-page-ai-subtitle span:first-child'); - if (providerNode instanceof HTMLElement) { - providerNode.textContent = pageAiAgentRecord(pageAiCurrentAgentId()).label; - } - } - - function renderPageAiControls() { - var drawer = ensurePageAiDrawer(); - var isAcp = pageUiState.pageAiAcpRuntime !== ''; - var activeAgentId = pageAiCurrentAgentId(); - var activeProfile = pageAiCurrentProfile(); - drawer.setAttribute('data-page-ai-page', pageUiState.pageAiPage || 'chat'); - drawer.setAttribute('data-page-ai-active-agent-id', activeAgentId); - drawer.setAttribute('data-mnote-acp-runtime', pageUiState.pageAiAcpRuntime || 'reasonix'); - document.documentElement.setAttribute('data-mnote-page-ai-agent-id', activeAgentId); - var agentButton = drawer.querySelector('[data-page-ai-agent-button]'); - var agentPopoverId = 'mnote-page-ai-agent-popover'; - if (agentButton instanceof HTMLElement) { - var activeAgent = pageAiAgentRecord(activeAgentId); - agentButton.textContent = 'AI'; - agentButton.setAttribute('aria-expanded', pageUiState.pageAiAgentPopoverOpen ? 'true' : 'false'); - agentButton.setAttribute('aria-controls', agentPopoverId); - agentButton.setAttribute('data-page-ai-agent-summary', activeAgent.label); - agentButton.setAttribute('aria-label', 'Agent:' + activeAgent.label); - agentButton.setAttribute('title', 'Agent:' + activeAgent.label); - } - var agentPopover = drawer.querySelector('[data-page-ai-agent-popover]'); - if (agentPopover instanceof HTMLElement) { - agentPopover.id = agentPopoverId; - agentPopover.hidden = !pageUiState.pageAiAgentPopoverOpen; - agentPopover.innerHTML = '' + - '
    ' + - '选择 Agent' + - '' + - '
    ' + - '
    ' + - PAGE_AI_AGENT_REGISTRY.map(function(agent) { - var active = agent.id === activeAgentId; - var detail = agent.canWriteFiles ? '可读取并在授权目录内写文件' : '只聊天,不申请文件写权限'; - return '' + - ''; - }).join('') + - '
    ' + - (activeAgentId === 'hermes' - ? '' - : ''); - } - var contextButton = drawer.querySelector('[data-page-ai-context-button]'); - var contextPopoverId = 'mnote-page-ai-context-popover'; - if (contextButton instanceof HTMLElement) { - var summary = pageAiContextButtonSummary(); - contextButton.textContent = '⇅'; - contextButton.setAttribute('aria-expanded', pageUiState.pageAiContextPopoverOpen ? 'true' : 'false'); - contextButton.setAttribute('aria-controls', contextPopoverId); - contextButton.setAttribute('data-page-ai-context-summary', summary); - contextButton.setAttribute('aria-label', '上下文:' + summary); - contextButton.setAttribute('title', '上下文:' + summary); - } - var contextPopover = drawer.querySelector('[data-page-ai-context-popover]'); - if (contextPopover instanceof HTMLElement) { - var selectedRefs = pageAiEnsureContextRefState(); - contextPopover.id = contextPopoverId; - contextPopover.hidden = !pageUiState.pageAiContextPopoverOpen; - contextPopover.innerHTML = '' + - '
    ' + - '发送给 AI 的上下文' + - '' + - '
    ' + - '
    ' + - PAGE_AI_CONTEXT_REF_REGISTRY.map(function(ref) { - var checked = selectedRefs[ref.id] === true; - var disabled = pageAiContextRefDisabled(ref.id); - return '' + - ''; - }).join('') + - '
    ' + - '
    ' + - '授权区域' + - '' + escapeHtml(pageAiBuildAllowedRoots().length ? pageAiBuildAllowedRoots().map(function(root) { return root.permission + ' · ' + root.rootUri.replace(/^file:\/\//, ''); }).join(' / ') : '未选择授权区域') + '' + - '
    '; - } - var allowedRootsNode = drawer.querySelector('[data-page-ai-allowed-roots]'); - if (allowedRootsNode instanceof HTMLElement) { - var allowedRoots = pageAiBuildAllowedRoots(); - if (!allowedRoots.length) { - var errorText = String(pageUiState.pageAiAllowedRootsError || '').trim(); - allowedRootsNode.innerHTML = '' + escapeHtml(errorText || '未选择授权区域') + ''; - } else { - allowedRootsNode.innerHTML = allowedRoots.map(function(root) { - var label = root.rootUri.replace(/^file:\/\//, '') || root.rootUri; - return '' + escapeHtml(root.permission + ' · ' + label) + ''; - }).join(''); - } - } - // Populate ACP runtime dropdown - var acpSelect = drawer.querySelector('[data-page-ai-acp-runtime]'); - if (acpSelect instanceof HTMLSelectElement) { - var runtimes = pageUiState.pageAiAcpRuntimes.length ? pageUiState.pageAiAcpRuntimes : pageAiNormalizeAcpRuntimes([]); - acpSelect.innerHTML = runtimes.map(function(rt) { - return ''; - }).join(''); - acpSelect.value = pageUiState.pageAiAcpRuntime || 'reasonix'; - } - // Show/hide Hermes-specific profile select - var profileLabel = drawer.querySelector('[data-page-ai-hermes-profile]'); - if (profileLabel instanceof HTMLElement) { - profileLabel.style.display = pageUiState.pageAiAcpRuntime === 'reasonix' ? 'none' : ''; - } - // When ACP is selected, populate agent panel with ACP runtime info - var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]'); - if (agentPanel instanceof HTMLElement) { - agentPanel.innerHTML = '' + - '
    ' + - '
    授权区域
    ' + escapeHtml(pageAiBuildAllowedRoots().length ? 'SQLite directory_grants' : '需要授权') + '
    ' + - '
    ' + - '
    ' + - '
    默认上下文
    ContextRefs
    ' + - '
    ' + - '
    ' + - '
    审计
    changed files 默认摘要
    ' + - '
    '; - } - var profileSummary = drawer.querySelector('[data-page-ai-profile-summary]'); - if (profileSummary instanceof HTMLElement) profileSummary.textContent = '上下文可选'; - var profileSummary = drawer.querySelector('[data-page-ai-profile-summary]'); - if (profileSummary instanceof HTMLElement) profileSummary.textContent = '上下文可选'; - drawer.querySelectorAll('[data-page-ai-profile-select]').forEach(function(profileSelect) { - if (!(profileSelect instanceof HTMLSelectElement)) return; - var profiles = pageUiState.pageAiProfiles.length ? pageUiState.pageAiProfiles : [{ name: activeProfile, active: true }]; - profileSelect.innerHTML = profiles.map(function(profile) { - var name = pageAiProfileValue(profile) || 'default'; - var model = [profile.model, profile.gateway].filter(Boolean).join(' / '); - var label = [name, profile.alias, model].filter(Boolean).join(' · '); - return ''; - }).join(''); - profileSelect.value = activeProfile; - }); - var runStatus = drawer.querySelector('[data-page-ai-run-status]'); - if (runStatus instanceof HTMLElement) { - var queueSuffix = pageUiState.pageAiQueueLength > 0 ? ' · 队列 ' + pageUiState.pageAiQueueLength : ''; - runStatus.textContent = pageAiRunStatusLabel(pageUiState.pageAiRunStatus) + queueSuffix; - } - var stopButton = drawer.querySelector('[data-page-ai-action="stop-run"]'); - if (stopButton instanceof HTMLButtonElement) { - var canStop = ['queued', 'running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0 && pageUiState.pageAiCurrentRunId; - stopButton.disabled = !canStop; - stopButton.setAttribute('aria-disabled', canStop ? 'false' : 'true'); - } - var settingsLink = drawer.querySelector('[data-page-ai-action="open-hermes-settings"]'); - if (settingsLink instanceof HTMLButtonElement) { - settingsLink.disabled = !pageAiHermesSettingsUrl(); - settingsLink.title = pageAiHermesSettingsUrl() ? '打开 Hermes 设置' : '未配置 MNOTE_WEB_HERMES_UPSTREAM_URL'; - } - var queueList = drawer.querySelector('[data-page-ai-queue-list]'); - if (queueList instanceof HTMLElement) { - if (!pageUiState.pageAiQueuedItems.length) { - queueList.innerHTML = '
    暂无排队项。
    '; - } else { - queueList.innerHTML = pageUiState.pageAiQueuedItems.map(function(item, index) { - var label = '队列 ' + (index + 1); - return '' + - '
    ' + - '
    ' + escapeHtml(label) + '
    ' + escapeHtml(item.queueId) + '
    ' + - '' + - '
    '; - }).join(''); - } - } - var sessionNode = drawer.querySelector('[data-page-ai-session-status]'); - if (sessionNode instanceof HTMLElement) { - var session = pageAiCurrentSession(); - var usageText = session && session.usage ? pageAiUsageSummary(session.usage) : ''; - sessionNode.textContent = session && session.id - ? [String(session.title || '当前页问答'), pageAiSessionStorageLabel(session), usageText].filter(Boolean).join(' · ') - : '等待 AI session'; - } - var modelNode = drawer.querySelector('[data-page-ai-model-status]'); - if (modelNode instanceof HTMLElement) modelNode.textContent = pageAiBuildAllowedRoots().length ? '已授权' : '需授权'; - var scopeSelect = drawer.querySelector('[data-page-ai-context-scope]'); - if (scopeSelect instanceof HTMLSelectElement) scopeSelect.value = pageUiState.pageAiContextScope; - var scopeLabel = drawer.querySelector('[data-page-ai-context-scope-label]'); - if (scopeLabel instanceof HTMLElement) scopeLabel.textContent = pageAiContextScopeLabel(pageUiState.pageAiContextScope); - drawer.querySelectorAll('[data-page-ai-tab]').forEach(function(button) { - var target = button.getAttribute('data-page-ai-tab') || 'chat'; - var active = target === pageUiState.pageAiPage; - button.classList.toggle('is-active', active); - button.setAttribute('aria-selected', active ? 'true' : 'false'); - }); - drawer.querySelectorAll('[data-page-ai-panel]').forEach(function(panel) { - if (panel instanceof HTMLElement) { - panel.hidden = panel.getAttribute('data-page-ai-panel') !== pageUiState.pageAiPage; - } - }); - var profileError = drawer.querySelector('[data-page-ai-profile-error]'); - if (profileError instanceof HTMLElement) { - profileError.textContent = pageUiState.pageAiProfileError || ''; - profileError.hidden = !pageUiState.pageAiProfileError; - } - var memoryError = drawer.querySelector('[data-page-ai-memory-error]'); - if (memoryError instanceof HTMLElement) { - var memoryErrorText = pageUiState.pageAiProfileMemoryError || ''; - if (memoryErrorText && memoryErrorText === pageUiState.pageAiProfileError) memoryErrorText = ''; - memoryError.textContent = memoryErrorText; - memoryError.hidden = !memoryErrorText; - } - var hermesMemoryPanel = drawer.querySelector('[data-page-ai-hermes-panel]'); - if (hermesMemoryPanel instanceof HTMLElement) { - hermesMemoryPanel.innerHTML = ['soul', 'user', 'memory'].map(function(section) { - var label = pageAiMemoryFileLabel(section); - var value = pageUiState.pageAiProfileMemoryDrafts[section] || ''; - return '' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + escapeHtml(label) + '
    ' + - '
    保存到 Hermes profile: ' + escapeHtml(activeProfile) + '
    ' + - '
    ' + - '' + - '
    ' + - '' + - '
    '; - }).join(''); - } - var skillError = drawer.querySelector('[data-page-ai-skill-error]'); - if (skillError instanceof HTMLElement) { - skillError.textContent = pageUiState.pageAiSkillError || ''; - skillError.hidden = !pageUiState.pageAiSkillError; - } - var sessionError = drawer.querySelector('[data-page-ai-session-error]'); - if (sessionError instanceof HTMLElement) { - sessionError.textContent = pageUiState.pageAiSessionError || ''; - sessionError.hidden = !pageUiState.pageAiSessionError; - } - var sessionSearch = drawer.querySelector('[data-page-ai-session-search]'); - if (sessionSearch instanceof HTMLInputElement && document.activeElement !== sessionSearch) { - sessionSearch.value = pageUiState.pageAiSessionSearchQuery || ''; - } - var skillSearch = drawer.querySelector('[data-page-ai-skill-search]'); - if (skillSearch instanceof HTMLInputElement && document.activeElement !== skillSearch) { - skillSearch.value = pageUiState.pageAiSkillQuery; - } - var skillList = drawer.querySelector('[data-page-ai-skill-list]'); - if (skillList instanceof HTMLElement) { - var skills = pageAiFilteredSkillEntries(); - if (!skills.length) { - skillList.innerHTML = '
    没有匹配的技能。
    '; - } else { - var groupLabels = { mnote: 'MNote 内置技能', reasonix: 'Reasonix 技能', hermes: 'Hermes 技能' }; - skillList.innerHTML = ['mnote', 'reasonix', 'hermes'].map(function(group) { - var groupSkills = skills.filter(function(skill) { return skill.group === group; }); - if (!groupSkills.length) return ''; - var headingExtra = group === 'hermes' ? ' · ' + pageAiCurrentProfile() : ''; - var collapsed = pageAiSkillGroupCollapsed(group); - return '' + - '
    ' + - '' + - (collapsed ? '' : groupSkills.map(function(skill) { - var sourceText = pageAiSkillOriginLabel(skill) + (skill.modified ? ' · modified' : ''); - var description = String(skill.description || '').trim(); - var hasDescription = description && description !== '---' && description !== '无描述'; - var displayName = String(skill.title || skill.name || skill.id || '').trim(); - return '' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + escapeHtml(displayName) + '
    ' + - '
    ' + escapeHtml(sourceText) + '
    ' + - '
    ' + - (hasDescription ? '
    ' + escapeHtml(description) + '
    ' : '') + - '
    ' + - '' + - '
    '; - }).join('')) + - '
    '; - }).join(''); - } - } - var toolsList = drawer.querySelector('[data-page-ai-tool-list]'); - if (toolsList instanceof HTMLElement) { - var tools = pageAiNormalizeArray(pageUiState.pageAiTools); - if (!tools.length) { - toolsList.innerHTML = '
    尚未读取到 mnote tool manifest。
    '; - } else { - toolsList.innerHTML = tools.map(function(tool) { - return '' + - '
    ' + - '
    ' + - '
    ' + escapeHtml(tool.name) + '
    ' + - '
    ' + escapeHtml(tool.scope || tool.kind || 'mnote') + ' · ' + escapeHtml(tool.status || 'available') + '
    ' + - (tool.unavailableReason ? '
    ' + escapeHtml(tool.unavailableReason) + '
    ' : '') + - '
    ' + - '' + - '
    '; - }).join(''); - } - } - var gatewayError = drawer.querySelector('[data-page-ai-gateway-error]'); - if (gatewayError instanceof HTMLElement) { - gatewayError.textContent = pageUiState.pageAiGatewayHealthError || ''; - gatewayError.hidden = !pageUiState.pageAiGatewayHealthError; - } - var gatewayStatusNode = drawer.querySelector('[data-page-ai-gateway-status]'); - var gatewayDetail = drawer.querySelector('[data-page-ai-gateway-detail]'); - var gatewayHealth = pageUiState.pageAiGatewayHealth; - if (gatewayStatusNode instanceof HTMLElement) { - if (!gatewayHealth) { - gatewayStatusNode.textContent = '未检查'; - } else { - var gateway = gatewayHealth.gateway || {}; - var profile = gatewayHealth.profile || {}; - gatewayStatusNode.textContent = gatewayHealth.ok ? '可用' : '需要设置'; - if (gateway.status) gatewayStatusNode.textContent += ' · ' + gateway.status; - if (profile.name) gatewayStatusNode.textContent += ' · ' + profile.name; - } - } - if (gatewayDetail instanceof HTMLElement) { - if (!gatewayHealth) { - gatewayDetail.innerHTML = '
    打开高级后会检查 agent runtime 与当前 profile。
    '; - } else { - var gatewayInfo = gatewayHealth.gateway || {}; - var profileInfo = gatewayHealth.profile || {}; - var suggestionText = pageAiNormalizeArray(gatewayHealth.suggestions).join(';'); - gatewayDetail.innerHTML = '' + - '
    ' + - '
    ' + escapeHtml(profileInfo.name || activeProfile) + '
    ' + - '
    model.default: ' + escapeHtml(profileInfo.modelDefault || '未设置') + '
    ' + - '
    provider: ' + escapeHtml(profileInfo.provider || '未设置') + ' · API key: ' + escapeHtml(profileInfo.apiKeyConfigured ? '已配置' : '未检测到') + '
    ' + - '
    ' + - '
    ' + - '
    ' + escapeHtml(gatewayInfo.upstream || '未配置 upstream') + '
    ' + - '
    gateway: ' + escapeHtml(gatewayInfo.status || 'unknown') + (gatewayInfo.httpStatus ? ' · HTTP ' + escapeHtml(gatewayInfo.httpStatus) : '') + '
    ' + - (suggestionText ? '
    ' + escapeHtml(suggestionText) + '
    ' : '') + - '
    '; - } - } - var toolError = drawer.querySelector('[data-page-ai-tool-error]'); - if (toolError instanceof HTMLElement) { - toolError.textContent = pageUiState.pageAiToolsError || ''; - toolError.hidden = !pageUiState.pageAiToolsError; - } - var lastTool = drawer.querySelector('[data-page-ai-last-tool]'); - if (lastTool instanceof HTMLElement) { - var call = pageUiState.pageAiLastToolCall; - lastTool.textContent = call - ? [call.event, call.name, call.runId, call.traceId || call.auditId].filter(Boolean).join(' · ') - : '暂无 tool call'; - } - var reasonixPanel = drawer.querySelector('[data-page-ai-reasonix-panel]'); - if (reasonixPanel instanceof HTMLElement) { - reasonixPanel.innerHTML = '
    Reasonix 专属设置
    ACP runtime / skills
    '; - } - var chatOnlyPanel = drawer.querySelector('[data-page-ai-chat-only-panel]'); - if (chatOnlyPanel instanceof HTMLElement) { - chatOnlyPanel.innerHTML = '
    Chat-only
    默认不申请文件写权限
    '; - } - } - - function humanizePageAiResponse(rawText, promptText) { - var providerLabel = pageAiProviderLabel(pageUiState.pageAiProvider); - var text = String(rawText || '').trim(); - if (!text) { - return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”,但这次没有返回可读内容。'; - } - if (text.startsWith('{')) { - try { - var payload = JSON.parse(text); - var operation = payload && payload.operation ? payload.operation : {}; - var normalized = operation && operation.normalized_input ? operation.normalized_input : {}; - var args = normalized && normalized.args ? normalized.args : {}; - var documentId = searchText(args.documentId || args.pageId || currentDocumentId()); - var toolName = searchText(operation.tool_name || normalized.toolName || 'doc_get'); - return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”。当前已通过 Hermes 调用 mnote plugin 工具,目标页面是 ' + (documentId || '当前页面') + ',本次选择的工具是 ' + toolName + '。如果你继续追问,我会沿当前页面上下文继续回复。'; - } catch (_) { - return providerLabel + ' 已返回结果,但当前结果是结构化文本,已为你保留原始内容。'; - } - } - return text; - } - - function ensurePageAiDrawer() { - var existing = document.querySelector('[data-testid="wolai-page-ai-drawer"]'); - if (existing instanceof HTMLElement) return existing; - var drawer = document.createElement('aside'); - drawer.className = 'wolai-page-ai-drawer'; - drawer.setAttribute('data-testid', 'wolai-page-ai-drawer'); - drawer.setAttribute('data-mnote-surface', 'page-ai'); - drawer.hidden = true; - drawer.innerHTML = '' + - ''; - document.body.appendChild(drawer); - return drawer; - } - - function renderPageAiSuggestions() { - var drawer = ensurePageAiDrawer(); - var list = drawer.querySelector('[data-page-ai-panel="chat"] [data-page-ai-suggestion-list]'); - if (!(list instanceof HTMLElement)) return; - var container = list.closest('.wolai-page-ai-suggestions'); - if (container instanceof HTMLElement) { - container.hidden = pageUiState.pageAiPage !== 'chat' || pageUiState.pageAiMessages.length > 0; - } - var suggestions = pageAiSuggestions(); - var offset = pageUiState.pageAiSuggestionIndex % suggestions.length; - var ordered = suggestions.slice(offset).concat(suggestions.slice(0, offset)).slice(0, 3); - list.innerHTML = ordered.map(function(text) { - return ''; - }).join(''); - } - - function renderPageAiConversation() { - var drawer = ensurePageAiDrawer(); - renderPageAiSuggestions(); - var conversation = drawer.querySelector('[data-page-ai-panel="' + cssEscape(pageUiState.pageAiPage || 'chat') + '"] [data-page-ai-conversation]'); - if (!(conversation instanceof HTMLElement)) return; - if (pageUiState.pageAiPage === 'history') { - var historyRows = pageUiState.pageAiSessionSearchResults.length - ? pageUiState.pageAiSessionSearchResults - : pageUiState.pageAiSessions; - if (!historyRows.length) { - conversation.innerHTML = '
    尚未产生历史会话。
    '; - return; - } - conversation.innerHTML = historyRows.map(function(session) { - var preview = Array.isArray(session.messages) && session.messages.length - ? session.messages.slice(-1)[0].content - : (session.snippet || session.preview || '暂无消息'); - var active = session.id === pageUiState.pageAiActiveSessionId; - var usage = pageAiUsageSummary(session.usage); - var meta = [pageAiSessionStorageLabel(session), session.permissionLevel, session.shareId, session.status, usage].filter(Boolean).join(' · '); - return '' + - '
    ' + - '' + - '
    ' + - '' + - '' + - '' + - '
    ' + - '
    '; - }).join(''); - return; - } - if (!pageUiState.pageAiMessages.length) { - conversation.innerHTML = '
    围绕当前页面提问,我会优先使用当前页内容、页面结构和已保存设置。
    '; - return; - } - conversation.innerHTML = pageUiState.pageAiMessages.map(function(item) { - var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI'); - if (item.role === 'tool') { - var statusLabel = item.status === 'completed' ? '完成' : (item.status === 'failed' ? '失败' : '运行中'); - var locationRows = Array.isArray(item.locations) && item.locations.length - ? '
    位置:' + item.locations.map(function(loc, idx) { - return '' + - '' + escapeHtml(loc) + '' + - '' + - ''; - }).join('') + '
    ' - : ''; - var detailRows = [ - locationRows, - item.argsSummary ? '
    参数 ' + escapeHtml(item.argsSummary) + '
    ' : '', - item.resultSummary ? '
    结果 ' + escapeHtml(item.resultSummary) + '
    ' : '', - item.changedFiles && item.changedFiles.length ? '
    ' + escapeHtml(pageAiFormatChangedFiles(item.changedFiles)) + '
    ' : '', - (item.traceId || item.auditId) ? '
    ' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '
    ' : '' - ].filter(Boolean).join(''); - return '' + - '
    ' + - '
    ' + escapeHtml(roleLabel) + '
    ' + - '
    ' + - '
    ' + - '' + - '' + escapeHtml(item.toolName || item.content || 'tool') + '' + - '' + escapeHtml([statusLabel, item.toolKind, item.toolCallId].filter(Boolean).join(' · ')) + '' + - '' + - (detailRows || '
    暂无参数或结果详情。
    ') + - '
    ' + - '
    ' + - '
    '; - } - if (item.kind === 'thought') { - return '' + - '
    ' + - '思考过程' + - '
    ' + escapeHtml(item.content || '') + '
    ' + - '
    '; - } - if (item.kind === 'permission') { - var permissionActions = item.resolved ? '' : ( - '
    ' + - '' + - '' + - '
    ' - ); - return '' + - '
    ' + - '
    权限
    ' + - '
    ' + - '' + escapeHtml(item.toolName || 'session/request_permission') + '
    ' + - '' + escapeHtml(item.argsSummary || item.content || '') + '' + - permissionActions + - '
    ' + - '
    '; - } - if (item.kind === 'plan') { - var planEntries = Array.isArray(item.entries) ? item.entries : []; - var listHtml = planEntries.map(function(entry, idx) { - return '
  • ' + escapeHtml(String(entry || '')) + '
  • '; - }).join(''); - return '' + - '
    ' + - '
    ' + - '执行计划 · ' + planEntries.length + ' 步' + - '
    ' + - '
      ' + listHtml + '
    ' + - '
    ' + - '
    ' + - '
    '; - } - var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : ''; - return '' + - '
    ' + - '
    ' + escapeHtml(roleLabel) + '
    ' + - '
    ' + escapeHtml(item.content || '') + '
    ' + - '
    '; - }).join(''); - conversation.scrollTop = conversation.scrollHeight; - } function openPageAiDrawer() { pageUiState.pageAiAgentId = pageAiCurrentAgentId(); @@ -3551,6 +1818,7 @@ export function createSidebarPageAiRuntime(context) { renderPageAiControls(); var contextSnapshot = currentPageAiContextSnapshot(); var scopedContext = pageAiScopedPageContext(contextSnapshot); + scopedContext.editorTarget = currentPageAiScopedEditorTarget(); assertPageAiTargetInCurrentWorkspace(scopedContext.editorTarget); await assertPageAiTargetWritable(scopedContext.editorTarget); var runTargetSnapshot = pageAiBuildRunTargetSnapshot(scopedContext, prompt); @@ -3559,12 +1827,15 @@ export function createSidebarPageAiRuntime(context) { } var contextRefs = pageAiBuildContextRefs(scopedContext, runTargetSnapshot); var allowedRoots = pageAiBuildAllowedRoots(); + var agentTargetPackage = pageAiBuildAgentTargetPackage(scopedContext, runTargetSnapshot); if (scopedContext.pageContext && scopedContext.pageContext.aiContext) { scopedContext.pageContext.aiContext.runTargetSnapshot = runTargetSnapshot; + scopedContext.pageContext.aiContext.agentTargetPackage = agentTargetPackage; } var requestPageContext = pageAiPageContextForRefs(scopedContext.pageContext, contextRefs); pageUiState.pageAiMessages.push({ role: 'user', content: prompt }); currentSession = pageAiCurrentSession(); + var runProfile = pageAiRunProfile(); if (currentSession) { if (currentSession.title === '新会话') { currentSession.title = prompt.length > 18 ? prompt.slice(0, 18) + '…' : prompt; @@ -3587,7 +1858,9 @@ export function createSidebarPageAiRuntime(context) { rootUri: currentRootUri(), agentId: pageAiCurrentAgentId(), sessionId: pageUiState.pageAiActiveSessionId, - profile: pageAiRunProfile(), + acpSessionId: currentSession && currentSession.acpSessionId ? currentSession.acpSessionId : '', + profile: runProfile, + profileId: pageUiState.pageAiAcpRuntime === 'hermes' ? runProfile : '', acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', contextScope: pageUiState.pageAiContextScope, contextRefs: contextRefs, @@ -3595,15 +1868,16 @@ export function createSidebarPageAiRuntime(context) { skillPreferences: { mnote: pageAiSkillPreferenceTable('mnote', ''), reasonix: pageAiSkillPreferenceTable('reasonix', ''), - hermes: pageAiSkillPreferenceTable('hermes', pageAiCurrentProfile()) + hermes: pageAiSkillPreferenceTable('hermes', runProfile) }, message: prompt, model: pageAiMnoteToolModel(), pageContext: requestPageContext, editorTarget: scopedContext.editorTarget, runTargetSnapshot: runTargetSnapshot, + targetPackage: agentTargetPackage, selectedBlockId: scopedContext.selectedBlockId, - selectedText: '', + selectedText: scopedContext.selectedText || '', traceId: 'page-ai-run-' + Date.now().toString(36) }) }); @@ -3754,8 +2028,15 @@ export function createSidebarPageAiRuntime(context) { try { var infoPayload = JSON.parse(payloadText || 'null') || {}; var newTitle = String(infoPayload.title || '').trim(); + var acpSessionId = String(infoPayload.acpSessionId || infoPayload.acp_session_id || '').trim(); + var sessionForInfo = pageAiCurrentSession(); + if (acpSessionId && sessionForInfo) { + sessionForInfo.acpSessionId = acpSessionId; + sessionForInfo.updatedAt = Date.now(); + pageAiPersistSessions(); + } if (newTitle) { - var sessionForTitle = pageAiCurrentSession(); + var sessionForTitle = sessionForInfo || pageAiCurrentSession(); if (sessionForTitle) { sessionForTitle.title = newTitle; sessionForTitle.updatedAt = Date.now(); @@ -3821,8 +2102,355 @@ export function createSidebarPageAiRuntime(context) { } } + function handlePageAiClick(event, helpers) { + var closestAction = helpers && helpers.closestAction; + if (typeof closestAction !== 'function') return false; + var pageAiClose = closestAction(event.target, '[data-page-ai-action="close"]'); + if (pageAiClose) { + event.preventDefault(); + closePageAiDrawer(); + return true; + } + var pageAiSettings = closestAction(event.target, '[data-page-ai-action="open-hermes-settings"]'); + if (pageAiSettings) { + event.preventDefault(); + pageAiOpenHermesSettings(); + return true; + } + var pageAiStop = closestAction(event.target, '[data-page-ai-action="stop-run"]'); + if (pageAiStop) { + event.preventDefault(); + void pageAiStopRun(); + return true; + } + var pageAiRotate = closestAction(event.target, '[data-page-ai-action="rotate"]'); + if (pageAiRotate) { + event.preventDefault(); + pageUiState.pageAiSuggestionIndex += 1; + renderPageAiSuggestions(); + return true; + } + var pageAiIntent = closestAction(event.target, '[data-page-ai-intent]'); + if (pageAiIntent) { + event.preventDefault(); + var intentName = pageAiIntent.getAttribute('data-page-ai-intent') || ''; + if (intentName === 'create-summary') void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_summary,为当前页面创建或更新 AI Summary。'); + if (intentName === 'create-ai-note') void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_ai_note,基于当前页面创建一篇新的 AI Note。'); + return true; + } + var pageAiTab = closestAction(event.target, '[data-page-ai-tab]'); + if (pageAiTab) { + event.preventDefault(); + pageUiState.pageAiPage = pageAiTab.getAttribute('data-page-ai-tab') || 'chat'; + if (pageUiState.pageAiPage === 'runtime') void pageAiLoadGatewayHealth(); + if (pageUiState.pageAiPage === 'skills') void pageAiLoadSkills(); + renderPageAiControls(); + renderPageAiConversation(); + return true; + } + var pageAiProvider = closestAction(event.target, '[data-page-ai-provider]'); + if (pageAiProvider) { + event.preventDefault(); + pageUiState.pageAiProvider = pageAiProvider.getAttribute('data-page-ai-provider') || 'hermes'; + renderPageAiProviderButtons(); + return true; + } + var pageAiAgent = closestAction(event.target, '[data-page-ai-agent-id]'); + if (pageAiAgent) { + event.preventDefault(); + var nextAgentId = pageAiAgent.getAttribute('data-page-ai-agent-id') || 'reasonix'; + var nextProfileId = pageAiAgent.getAttribute('data-page-ai-profile-id') || ''; + pageAiSetAgentId(nextAgentId); + if (nextProfileId) { + pageUiState.pageAiAgentPopoverOpen = false; + void pageAiSwitchProfile(nextProfileId); + renderPageAiControls(); + } + return true; + } + var pageAiAgentButton = closestAction(event.target, '[data-page-ai-agent-button]'); + if (pageAiAgentButton) { + event.preventDefault(); + pageAiSetAgentPopoverOpen(pageAiAgentButton.getAttribute('aria-expanded') !== 'true'); + return true; + } + var pageAiContextButton = closestAction(event.target, '[data-page-ai-context-button]'); + if (pageAiContextButton) { + event.preventDefault(); + pageAiSetContextPopoverOpen(pageAiContextButton.getAttribute('aria-expanded') !== 'true'); + return true; + } + var pageAiTargetButton = closestAction(event.target, '[data-page-ai-target-button]'); + if (pageAiTargetButton) { + event.preventDefault(); + pageAiSetTargetPopoverOpen(pageAiTargetButton.getAttribute('aria-expanded') !== 'true'); + return true; + } + var pageAiAction = closestAction(event.target, '[data-page-ai-action]'); + if (pageAiAction) { + event.preventDefault(); + var action = pageAiAction.getAttribute('data-page-ai-action') || ''; + if (action === 'close-agent-popover') pageAiSetAgentPopoverOpen(false); + if (action === 'close-context-popover') pageAiSetContextPopoverOpen(false); + if (action === 'close-target-popover') pageAiSetTargetPopoverOpen(false); + if (action === 'new-session') { + pageUiState.pageAiPage = 'chat'; + pageAiStartNewSession(); + } + if (action === 'history') { + pageAiLoadSessions(); + pageUiState.pageAiPage = pageUiState.pageAiPage === 'history' ? 'chat' : 'history'; + renderPageAiControls(); + renderPageAiConversation(); + if (pageUiState.pageAiPage === 'history') { + void pageAiLoadBackendSessions().catch(function(error) { + pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); + renderPageAiControls(); + }); + } + } + if (action === 'send') { + var input = ensurePageAiDrawer().querySelector('[data-page-ai-input]'); + if (input instanceof HTMLTextAreaElement) { + var message = input.value; + input.value = ''; + void sendPageAiMessage(message); + } + } + if (action === 'cancel-queued-run') { + void pageAiCancelQueuedRun(pageAiAction.getAttribute('data-page-ai-queue-id')); + } + return true; + } + var pageAiTargetOption = closestAction(event.target, '[data-page-ai-target-option]'); + if (pageAiTargetOption) { + event.preventDefault(); + pageAiSelectTarget(pageAiTargetOption.getAttribute('data-page-ai-target-option') || ''); + return true; + } + var pageAiContextRef = closestAction(event.target, '[data-page-ai-context-ref]'); + if (pageAiContextRef) { + event.preventDefault(); + pageAiToggleContextRef(pageAiContextRef.getAttribute('data-page-ai-context-ref') || ''); + return true; + } + var pageAiMemorySave = closestAction(event.target, '[data-page-ai-memory-save]'); + if (pageAiMemorySave) { + event.preventDefault(); + void pageAiSaveProfileMemory(pageAiMemorySave.getAttribute('data-page-ai-memory-save') || ''); + return true; + } + var pageAiSkillToggle = closestAction(event.target, '[data-page-ai-skill-toggle]'); + if (pageAiSkillToggle) { + event.preventDefault(); + void pageAiToggleSkill( + pageAiSkillToggle.getAttribute('data-page-ai-skill-toggle') || '', + pageAiSkillToggle.getAttribute('aria-pressed') !== 'true', + pageAiSkillToggle.getAttribute('data-page-ai-skill-group') || '', + pageAiSkillToggle.getAttribute('data-page-ai-skill-profile') || '' + ); + return true; + } + var pageAiSkillGroupToggle = closestAction(event.target, '[data-page-ai-skill-group-toggle]'); + if (pageAiSkillGroupToggle) { + event.preventDefault(); + pageAiToggleSkillGroup(pageAiSkillGroupToggle.getAttribute('data-page-ai-skill-group-toggle') || ''); + return true; + } + var pageAiToolToggle = closestAction(event.target, '[data-page-ai-tool-toggle]'); + if (pageAiToolToggle) { + event.preventDefault(); + void pageAiToggleTool(pageAiToolToggle.getAttribute('data-page-ai-tool-toggle') || '', pageAiToolToggle.getAttribute('aria-pressed') !== 'true'); + return true; + } + var pageAiSessionResume = closestAction(event.target, '[data-page-ai-session-resume]'); + if (pageAiSessionResume) { + event.preventDefault(); + void pageAiResumeBackendSession(pageAiSessionResume.getAttribute('data-page-ai-session-resume') || '').catch(function(error) { + pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); + renderPageAiControls(); + }); + return true; + } + var pageAiSessionRename = closestAction(event.target, '[data-page-ai-session-rename]'); + if (pageAiSessionRename) { + event.preventDefault(); + void pageAiRenameBackendSession(pageAiSessionRename.getAttribute('data-page-ai-session-rename') || '').catch(function(error) { + pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); + renderPageAiControls(); + }); + return true; + } + var pageAiSessionDelete = closestAction(event.target, '[data-page-ai-session-delete]'); + if (pageAiSessionDelete) { + event.preventDefault(); + void pageAiDeleteBackendSession(pageAiSessionDelete.getAttribute('data-page-ai-session-delete') || '').catch(function(error) { + pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); + renderPageAiControls(); + }); + return true; + } + var pageAiPermissionAction = closestAction(event.target, '[data-page-ai-permission-action]'); + if (pageAiPermissionAction) { + event.preventDefault(); + pageAiResolvePermission(pageAiPermissionAction.getAttribute('data-page-ai-permission-id') || '', pageAiPermissionAction.getAttribute('data-page-ai-permission-action') || 'deny'); + return true; + } + var pageAiOpenLocationAction = closestAction(event.target, '[data-page-ai-open-location]'); + if (pageAiOpenLocationAction) { + event.preventDefault(); + var loc = String(pageAiOpenLocationAction.getAttribute('data-page-ai-open-location') || '').trim(); + if (loc) pageAiOpenLocation(loc); + return true; + } + var pageAiSession = closestAction(event.target, '[data-page-ai-session]'); + if (pageAiSession) { + event.preventDefault(); + pageAiSetActiveSession(pageAiSession.getAttribute('data-page-ai-session') || ''); + return true; + } + var pageAiSuggestion = closestAction(event.target, '[data-page-ai-suggestion]'); + if (pageAiSuggestion) { + event.preventDefault(); + var text = pageAiSuggestion.getAttribute('data-page-ai-suggestion') || ''; + var inputNode = ensurePageAiDrawer().querySelector('[data-page-ai-input]'); + if (inputNode instanceof HTMLTextAreaElement) { + inputNode.value = text; + inputNode.focus(); + } + return true; + } + return false; + } + + function handlePageAiKeyDown(event, helpers) { + var closestAction = helpers && helpers.closestAction; + if (typeof closestAction !== 'function') return false; + if (event.key === 'Enter' && !event.shiftKey) { + var aiInput = closestAction(event.target, '[data-page-ai-input]'); + if (aiInput instanceof HTMLTextAreaElement) { + event.preventDefault(); + var text = aiInput.value; + aiInput.value = ''; + void sendPageAiMessage(text); + return true; + } + } + return false; + } + + function handlePageAiInput(event, helpers) { + var closestAction = helpers && helpers.closestAction; + if (typeof closestAction !== 'function') return false; + var skillSearch = closestAction(event.target, '[data-page-ai-skill-search]'); + if (skillSearch instanceof HTMLInputElement) { + pageUiState.pageAiSkillQuery = skillSearch.value; + renderPageAiControls(); + return true; + } + var sessionSearch = closestAction(event.target, '[data-page-ai-session-search]'); + if (sessionSearch instanceof HTMLInputElement) { + var sessionQuery = sessionSearch.value; + window.clearTimeout(pageUiState.pageAiSessionSearchTimer || 0); + pageUiState.pageAiSessionSearchTimer = window.setTimeout(function() { + void pageAiSearchBackendSessions(sessionQuery).catch(function(error) { + pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); + renderPageAiControls(); + }); + }, 200); + return true; + } + var memoryEditor = closestAction(event.target, '[data-page-ai-memory-editor]'); + if (memoryEditor instanceof HTMLTextAreaElement) { + var section = memoryEditor.getAttribute('data-page-ai-memory-editor') || ''; + if (['memory', 'user', 'soul'].indexOf(section) >= 0) pageUiState.pageAiProfileMemoryDrafts[section] = memoryEditor.value; + return true; + } + return false; + } + + function handlePageAiChange(event, helpers) { + var closestAction = helpers && helpers.closestAction; + if (typeof closestAction !== 'function') return false; + var pageAiAcpRuntimeSelect = closestAction(event.target, '[data-page-ai-acp-runtime]'); + if (pageAiAcpRuntimeSelect instanceof HTMLSelectElement) { + var next = String(pageAiAcpRuntimeSelect.value || 'reasonix').trim() || 'reasonix'; + pageUiState.pageAiAcpRuntime = next; + pageUiState.pageAiSkills = { categories: [], archived: [] }; + pageUiState.pageAiSkillError = ''; + pageAiPersistSessions(); + void pageAiLoadProfiles(); + if (next !== 'reasonix') void pageAiLoadProfileMemory(); + void pageAiLoadSkills(); + void pageAiLoadBackendSessions().catch(function(error) { + pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); + }); + renderPageAiControls(); + renderPageAiProviderButtons(); + return true; + } + var pageAiProfileSelect = closestAction(event.target, '[data-page-ai-profile-select]'); + if (pageAiProfileSelect instanceof HTMLSelectElement) { + void pageAiSwitchProfile(pageAiProfileSelect.value); + return true; + } + var pageAiSkillSourceSelect = closestAction(event.target, '[data-page-ai-skill-source-select]'); + if (pageAiSkillSourceSelect instanceof HTMLSelectElement) { + pageAiSetSkillSource(pageAiSkillSourceSelect.value); + return true; + } + var pageAiSessionAgentFilter = closestAction(event.target, '[data-page-ai-session-agent-filter]'); + if (pageAiSessionAgentFilter instanceof HTMLSelectElement) { + pageUiState.pageAiSessionAgentFilter = String(pageAiSessionAgentFilter.value || 'all').trim() || 'all'; + renderPageAiControls(); + renderPageAiConversation(); + return true; + } + var pageAiHideHermesBuiltin = closestAction(event.target, '[data-page-ai-hide-hermes-builtin]'); + if (pageAiHideHermesBuiltin instanceof HTMLInputElement) { + pageAiSetHideHermesBuiltinSkills(pageAiHideHermesBuiltin.checked); + return true; + } + var pageAiReasonixMemory = closestAction(event.target, '[data-page-ai-reasonix-memory]'); + if (pageAiReasonixMemory instanceof HTMLInputElement) { + pageAiSetReasonixMemoryEnabled(pageAiReasonixMemory.checked); + return true; + } + var pageAiContextSelect = closestAction(event.target, '[data-page-ai-context-scope]'); + if (pageAiContextSelect instanceof HTMLSelectElement) { + pageAiSetContextScope(pageAiContextSelect.value); + renderPageAiControls(); + return true; + } + return false; + } + + function installPageAiDelegates() { + if (pageAiDelegatesInstalled) return; + pageAiDelegatesInstalled = true; + var helpers = { closestAction: closestAction }; + document.addEventListener('click', function(event) { + handlePageAiClick(event, helpers); + }); + document.addEventListener('keydown', function(event) { + handlePageAiKeyDown(event, helpers); + }); + document.addEventListener('input', function(event) { + handlePageAiInput(event, helpers); + }); + document.addEventListener('change', function(event) { + handlePageAiChange(event, helpers); + }); + } + return { + ensurePageAiStateFacade: (...args) => ensurePageAiStateFacade(...args), + installPageAiDelegates: (...args) => installPageAiDelegates(...args), + handlePageAiClick: (...args) => handlePageAiClick(...args), + handlePageAiKeyDown: (...args) => handlePageAiKeyDown(...args), + handlePageAiInput: (...args) => handlePageAiInput(...args), + handlePageAiChange: (...args) => handlePageAiChange(...args), openPageAiDrawer: (...args) => openPageAiDrawer(...args), closePageAiDrawer: (...args) => closePageAiDrawer(...args), isPageAiDrawerOpen: (...args) => isPageAiDrawerOpen(...args), @@ -3849,13 +2477,17 @@ export function createSidebarPageAiRuntime(context) { pageAiLoadBackendSessions: (...args) => pageAiLoadBackendSessions(...args), pageAiCancelQueuedRun: (...args) => pageAiCancelQueuedRun(...args), pageAiSearchBackendSessions: (...args) => pageAiSearchBackendSessions(...args), + pageAiSetTargetPopoverOpen: (...args) => pageAiSetTargetPopoverOpen(...args), + pageAiSelectTarget: (...args) => pageAiSelectTarget(...args), pageAiPersistSessions: (...args) => pageAiPersistSessions(...args), pageAiLoadProfiles: (...args) => pageAiLoadProfiles(...args), pageAiLoadProfileMemory: (...args) => pageAiLoadProfileMemory(...args), pageAiLoadSkills: (...args) => pageAiLoadSkills(...args), pageAiSwitchProfile: (...args) => pageAiSwitchProfile(...args), pageAiToggleSkillGroup: (...args) => pageAiToggleSkillGroup(...args), + pageAiSetReasonixMemoryEnabled: (...args) => pageAiSetReasonixMemoryEnabled(...args), pageAiSetHideHermesBuiltinSkills: (...args) => pageAiSetHideHermesBuiltinSkills(...args), + pageAiSetSkillSource: (...args) => pageAiSetSkillSource(...args), pageAiSetContextScope: (...args) => pageAiSetContextScope(...args), pageAiSetAgentId: (...args) => pageAiSetAgentId(...args), pageAiToggleContextRef: (...args) => pageAiToggleContextRef(...args), diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-session-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-session-runtime.js new file mode 100644 index 00000000..750d08be --- /dev/null +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-session-runtime.js @@ -0,0 +1,613 @@ +export function createSidebarPageAiSessionRuntime(context) { + const { + currentDocumentId, + currentRootUri, + currentSourceKind, + documentRef, + pageAiApplyRuntimeState, + pageAiCurrentAgentId, + pageAiCurrentProfile, + pageAiErrorMessage, + pageAiNormalizeAgentId, + pageAiNormalizeArray, + pageAiNormalizeChatOnlyProfileId, + pageAiPermissionMessage, + pageAiPreviewValue, + pageAiRunProfile, + pageAiSetActiveProfile, + pageAiTimestamp, + pageUiState, + renderPageAiControls, + renderPageAiConversation, + resolveWorkspaceId, + sessionStorageVersion, + windowRef, + } = context; + const doc = documentRef || document; + const win = windowRef || window; + + function pageAiStorageKey() { + return 'hermes_page_ai_session:' + currentDocumentId(); + } + + function pageAiBackendSessionQuery(extra) { + var params = new URLSearchParams(); + params.set('source', 'acp'); + params.set('workspaceId', resolveWorkspaceId(doc.body)); + params.set('documentId', currentDocumentId()); + params.set('profile', pageAiRunProfile()); + params.set('sourceKind', currentSourceKind()); + if (currentRootUri()) params.set('rootUri', currentRootUri()); + Object.keys(extra || {}).forEach(function(key) { + var value = extra[key]; + if (value !== undefined && value !== null && String(value).trim() !== '') { + params.set(key, String(value)); + } + }); + return params.toString(); + } + + function pageAiNewSession(title) { + var now = Date.now(); + var agentId = pageAiCurrentAgentId(); + var profile = agentId === 'chat_only' ? pageAiNormalizeChatOnlyProfileId(pageAiCurrentProfile()) : pageAiCurrentProfile(); + return { + id: 'sess_' + now + '_' + Math.random().toString(16).slice(2, 8), + title: title || '新会话', + agentId: agentId, + profileId: pageUiState.pageAiAcpRuntime === 'hermes' ? profile : '', + profile: profile, + acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', + createdAt: now, + updatedAt: now, + source: 'local', + usage: null, + status: 'idle', + messages: [] + }; + } + + function pageAiNormalizeSessions(sessions) { + return (Array.isArray(sessions) ? sessions : []) + .slice(0, 20) + .map(function(session) { + var sessionStorage = String(session && (session.sessionStorage || session.session_storage) || '').trim(); + var persistence = String(session && session.persistence || '').trim(); + var agentId = pageAiNormalizeAgentId(session && (session.agentId || session.agent_id)); + var profileId = String(session && (session.profileId || session.profile_id || '') || '').trim(); + var profile = String(session && session.profile || pageAiCurrentProfile()).trim() || 'default'; + if (!profileId && agentId !== 'reasonix') profileId = profile; + if (agentId === 'chat_only') { + profileId = pageAiNormalizeChatOnlyProfileId(profileId || profile); + profile = profileId; + } + return { + id: String(session && session.id || '').trim() || pageAiNewSession().id, + title: String(session && session.title || '').trim() || '新会话', + agentId: agentId, + profileId: profileId, + profile: profile, + acpRuntime: String(session && session.acpRuntime || 'reasonix').trim(), + createdAt: pageAiTimestamp(session && session.createdAt), + updatedAt: pageAiTimestamp(session && session.updatedAt), + source: String(session && session.source || 'local').trim() || 'local', + persistence: persistence, + sessionStorage: sessionStorage, + permissionLevel: String(session && (session.permissionLevel || session.permission_level) || '').trim(), + shareId: String(session && (session.shareId || session.share_id) || '').trim(), + acpSessionId: String(session && (session.acpSessionId || session.acp_session_id) || '').trim(), + runId: String(session && (session.runId || session.run_id) || '').trim(), + status: String(session && session.status || '').trim(), + usage: session && session.usage && typeof session.usage === 'object' ? session.usage : null, + preview: String(session && session.preview || '').trim(), + messages: Array.isArray(session && session.messages) ? session.messages.slice(-300) : [] + }; + }) + .sort(function(a, b) { + return Number(b.updatedAt || 0) - Number(a.updatedAt || 0); + }); + } + + function pageAiNormalizeBackendSessionRow(row) { + if (!row || typeof row !== 'object') return null; + var payload = row.payload && typeof row.payload === 'object' ? row.payload : {}; + var sessionId = String(row.sessionId || row.session_id || '').trim(); + if (!sessionId) return null; + var title = String(row.title || payload.title || payload.message || '').trim(); + if (title.length > 28) title = title.slice(0, 28) + '…'; + var persistence = String(row.persistence || payload.persistence || '').trim(); + var sessionStorage = String(row.sessionStorage || row.session_storage || payload.sessionStorage || '').trim(); + var agentId = pageAiNormalizeAgentId(row.agentId || row.agent_id || payload.agentId || payload.agent_id); + var profileId = String(row.profileId || row.profile_id || payload.profileId || payload.profile_id || '').trim(); + var profile = String(row.profile || payload.profile || pageAiRunProfile()).trim() || 'default'; + if (!profileId && agentId !== 'reasonix') profileId = profile; + if (agentId === 'chat_only') { + profileId = pageAiNormalizeChatOnlyProfileId(profileId || profile); + profile = profileId; + } + if (!sessionStorage && persistence === 'convex_acp_runtime_store') sessionStorage = 'cloud'; + if (!sessionStorage && persistence === 'local_ai_session_jsonl') sessionStorage = String(row.shareId || payload.shareId || '').trim() ? 'local_shared' : 'local_private'; + return { + id: sessionId, + title: title || '当前页问答', + agentId: agentId, + profileId: profileId, + profile: profile, + acpRuntime: String(row.acpRuntime || row.acp_runtime || payload.acpRuntime || 'reasonix').trim(), + createdAt: pageAiTimestamp(row.createdAt || row.created_at), + updatedAt: pageAiTimestamp(row.updatedAt || row.updated_at), + source: sessionStorage === 'local_private' || sessionStorage === 'local_shared' ? 'local' : 'acp', + persistence: persistence, + sessionStorage: sessionStorage, + permissionLevel: String(row.permissionLevel || row.permission_level || payload.permissionLevel || '').trim(), + shareId: String(row.shareId || row.share_id || payload.shareId || '').trim(), + acpSessionId: String(row.acpSessionId || row.acp_session_id || payload.acpSessionId || payload.acp_session_id || '').trim(), + runId: String(row.runId || row.run_id || '').trim(), + status: String(row.status || (row.runtime && row.runtime.status) || '').trim(), + usage: row.usage && typeof row.usage === 'object' ? row.usage : null, + preview: String(payload.message || row.snippet || '').trim(), + messages: [] + }; + } + + function pageAiMergeSessions(localSessions, backendSessions) { + var byId = {}; + pageAiNormalizeSessions(localSessions).forEach(function(session) { + byId[session.id] = session; + }); + pageAiNormalizeSessions(backendSessions).forEach(function(session) { + var existing = byId[session.id]; + byId[session.id] = Object.assign({}, existing || {}, session, { + messages: session.messages.length ? session.messages : (existing && existing.messages || []) + }); + }); + return pageAiNormalizeSessions(Object.keys(byId).map(function(id) { return byId[id]; })); + } + + function pageAiSessionStorageLabel(session) { + var storage = String(session && (session.sessionStorage || session.session_storage) || '').trim(); + var persistence = String(session && session.persistence || '').trim(); + if (storage === 'local_shared') return '共享会话'; + if (storage === 'local_private') return '本地私有'; + if (storage === 'cloud' || persistence === 'convex_acp_runtime_store') return '云端会话'; + if (persistence === 'local_ai_session_jsonl') return '本地私有'; + return String(session && session.source || '').trim() === 'acp' ? '云端会话' : '本地私有'; + } + + function pageAiLoadSessions() { + if (pageUiState.pageAiSessions.length && pageUiState.pageAiActiveSessionId) return; + try { + var raw = win.localStorage.getItem(pageAiStorageKey()); + var parsed = raw ? JSON.parse(raw) : null; + var activeId = String(parsed && parsed.activeSessionId || '').trim(); + var activeProfile = String(parsed && parsed.activeProfileName || '').trim(); + var activeAcpRuntime = String(parsed && parsed.activeAcpRuntime || '').trim(); + var sessions = pageAiNormalizeSessions(parsed && parsed.sessions); + var storageVersion = Number(parsed && parsed.version || 0); + if (storageVersion >= sessionStorageVersion && activeAcpRuntime) pageUiState.pageAiAcpRuntime = activeAcpRuntime; + if (activeProfile) pageAiSetActiveProfile(activeProfile); + if (sessions.length) { + pageUiState.pageAiSessions = sessions; + var activeSession = sessions.find(function(session) { return session.id === activeId; }) || sessions[0]; + pageUiState.pageAiActiveSessionId = activeSession.id; + if (activeSession.profile) pageAiSetActiveProfile(activeSession.profile); + if (activeSession.agentId) pageUiState.pageAiAgentId = pageAiNormalizeAgentId(activeSession.agentId); + if (!pageUiState.pageAiAcpRuntime && activeSession.acpRuntime) pageUiState.pageAiAcpRuntime = activeSession.acpRuntime; + pageUiState.pageAiMessages = Array.isArray(activeSession.messages) ? activeSession.messages.slice() : []; + return; + } + if (activeId) { + pageUiState.pageAiSessions = [pageAiNewSession('当前页问答')]; + pageUiState.pageAiSessions[0].id = activeId; + pageUiState.pageAiActiveSessionId = activeId; + pageUiState.pageAiMessages = []; + return; + } + } catch (_) {} + var fresh = pageAiNewSession(); + pageUiState.pageAiSessions = [fresh]; + pageUiState.pageAiActiveSessionId = fresh.id; + pageUiState.pageAiMessages = []; + } + + async function pageAiLoadBackendSessions() { + var response = await fetch('/api/hermes/client/sessions?' + pageAiBackendSessionQuery({ limit: 50 }), { + headers: { 'accept': 'application/json' } + }); + var payload = await response.json().catch(function(){ return null; }); + if (!response.ok || !payload || payload.ok !== true) { + throw new Error(pageAiErrorMessage(payload, 'session_list_failed_' + response.status)); + } + var backendSessions = pageAiNormalizeArray(payload.sessions).map(pageAiNormalizeBackendSessionRow).filter(Boolean); + if (!backendSessions.length) return []; + pageUiState.pageAiSessions = pageAiMergeSessions(pageUiState.pageAiSessions, backendSessions); + if (!pageUiState.pageAiActiveSessionId || !pageUiState.pageAiSessions.find(function(session) { return session.id === pageUiState.pageAiActiveSessionId; })) { + pageUiState.pageAiActiveSessionId = pageUiState.pageAiSessions[0].id; + } + var active = pageAiCurrentSession(); + if (active) { + if (active.profile) pageAiSetActiveProfile(active.profile); + if (!pageUiState.pageAiAcpRuntime && active.acpRuntime) pageUiState.pageAiAcpRuntime = active.acpRuntime; + pageUiState.pageAiMessages = Array.isArray(active.messages) ? active.messages.slice() : pageUiState.pageAiMessages; + } + pageUiState.pageAiSessionError = ''; + pageAiPersistSessions(); + renderPageAiConversation(); + renderPageAiControls(); + return backendSessions; + } + + function pageAiMessageFromRuntimeEvent(event) { + if (!event || typeof event !== 'object') return null; + var eventType = String(event.eventType || event.event_type || event.event || '').trim(); + var payload = event.payload && typeof event.payload === 'object' ? event.payload : event; + if (eventType === 'message.delta') { + var delta = String(payload.delta || payload.text || payload.output_text || '').trim(); + return delta ? { role: 'assistant', content: delta } : null; + } + if (eventType === 'thought.delta') { + var thought = String(payload.delta || payload.text || '').trim(); + return thought ? { role: 'assistant', kind: 'thought', content: thought } : null; + } + if (eventType === 'tool.started' || eventType === 'tool.completed' || eventType === 'tool.failed') { + var toolName = String(payload.toolName || payload.tool || payload.name || eventType).trim(); + var rawLocations = payload.locations; + return { + role: 'tool', + content: toolName, + toolName: toolName, + toolCallId: String(payload.toolCallId || payload.tool_call_id || payload.id || ''), + toolKind: String(payload.kind || ''), + status: eventType === 'tool.completed' ? 'completed' : (eventType === 'tool.failed' ? 'failed' : 'running'), + argsSummary: pageAiPreviewValue(payload.args || payload.arguments || payload.input), + resultSummary: pageAiPreviewValue(payload.summary || payload.result || payload.output || payload.error), + locations: Array.isArray(rawLocations) ? rawLocations.filter(function(l) { return typeof l === 'string' || (typeof l === 'object' && l && l.path); }).map(function(l) { return typeof l === 'string' ? l : l.path; }) : [], + traceId: String(payload.traceId || payload.trace_id || ''), + auditId: String(payload.auditId || payload.audit_id || '') + }; + } + if (eventType === 'permission.requested' || eventType === 'permission.denied' || eventType === 'permission.allowed') { + return pageAiPermissionMessage(payload, eventType); + } + if (eventType === 'run.completed') { + var output = String(payload.output || payload.text || '').trim(); + return output ? { role: 'assistant', content: output } : null; + } + return null; + } + + function pageAiApplyBackendSessionDetail(payload) { + var sessionPayload = payload && payload.session ? payload.session : {}; + var runs = pageAiNormalizeArray(sessionPayload.runs); + var latest = runs.length ? pageAiNormalizeBackendSessionRow(runs[0]) : null; + var events = pageAiNormalizeArray(payload && payload.events); + var storedMessages = pageAiNormalizeArray(sessionPayload.messages).map(function(message) { + return { + role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'), + content: String(message.content || '') + }; + }).filter(function(message) { return message.content; }); + var eventsByRunId = {}; + events.forEach(function(event) { + var runId = String(event && (event.runId || event.run_id) || '').trim(); + if (!runId) return; + if (!eventsByRunId[runId]) eventsByRunId[runId] = []; + eventsByRunId[runId].push(event); + }); + var messages = []; + if (runs.length) { + runs.slice().reverse().forEach(function(run) { + var runPayload = run && run.payload && typeof run.payload === 'object' ? run.payload : {}; + var userMessage = String(runPayload.message || runPayload.input || '').trim(); + if (userMessage) messages.push({ role: 'user', content: userMessage }); + var runId = String(run && (run.runId || run.run_id) || '').trim(); + pageAiNormalizeArray(eventsByRunId[runId]).forEach(function(event) { + var message = pageAiMessageFromRuntimeEvent(event); + if (message) messages.push(message); + }); + }); + } + if (!messages.length) messages = storedMessages; + var acpSessionId = ''; + events.forEach(function(event) { + var eventType = String(event && (event.eventType || event.event_type || event.event) || '').trim(); + var payload = event && event.payload && typeof event.payload === 'object' ? event.payload : event; + if (eventType === 'session.info.updated') { + var nextAcpSessionId = String(payload && (payload.acpSessionId || payload.acp_session_id) || '').trim(); + if (nextAcpSessionId) acpSessionId = nextAcpSessionId; + } + }); + var current = pageAiCurrentSession(); + if (latest && current) { + Object.assign(current, latest); + } + if (current) { + current.messages = messages.slice(-300); + current.updatedAt = Math.max(Number(current.updatedAt || 0), Date.now()); + if (acpSessionId) current.acpSessionId = acpSessionId; + if (latest && latest.usage) current.usage = latest.usage; + } + pageAiApplyRuntimeState(payload && payload.runtime); + pageUiState.pageAiMessages = messages.slice(-300); + pageAiPersistSessions(); + } + + async function pageAiLoadBackendSessionDetail(sessionId) { + sessionId = String(sessionId || pageUiState.pageAiActiveSessionId || '').trim(); + if (!sessionId) return null; + var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '?' + pageAiBackendSessionQuery({ limit: 200 }), { + headers: { 'accept': 'application/json' } + }); + var payload = await response.json().catch(function(){ return null; }); + if (!response.ok || !payload || payload.ok !== true) { + throw new Error(pageAiErrorMessage(payload, 'session_detail_failed_' + response.status)); + } + pageAiApplyBackendSessionDetail(payload); + renderPageAiConversation(); + renderPageAiControls(); + return payload; + } + + async function pageAiSearchBackendSessions(query) { + var q = String(query || '').trim(); + pageUiState.pageAiSessionSearchQuery = q; + if (!q) { + pageUiState.pageAiSessionSearchResults = []; + renderPageAiConversation(); + return []; + } + var response = await fetch('/api/hermes/client/sessions/search?' + pageAiBackendSessionQuery({ q: q, limit: 20 }), { + headers: { 'accept': 'application/json' } + }); + var payload = await response.json().catch(function(){ return null; }); + if (!response.ok || !payload || payload.ok !== true) { + throw new Error(pageAiErrorMessage(payload, 'session_search_failed_' + response.status)); + } + pageUiState.pageAiSessionSearchResults = pageAiNormalizeArray(payload.results).map(function(row) { + var normalized = pageAiNormalizeBackendSessionRow(row) || {}; + normalized.snippet = String(row && row.snippet || normalized.preview || '').trim(); + return normalized; + }).filter(function(row) { return row.id; }); + renderPageAiConversation(); + return pageUiState.pageAiSessionSearchResults; + } + + function pageAiPersistSessions() { + doc.documentElement.setAttribute('data-mnote-page-ai-session-owner', 'hermes'); + doc.documentElement.setAttribute('data-mnote-page-ai-session-key', pageAiStorageKey()); + try { + pageAiSyncCurrentSessionMessages(); + win.localStorage.setItem(pageAiStorageKey(), JSON.stringify({ + version: sessionStorageVersion, + activeSessionId: pageUiState.pageAiActiveSessionId, + activeProfileName: pageAiCurrentProfile(), + activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', + sessions: pageAiNormalizeSessions(pageUiState.pageAiSessions) + })); + } catch (_) {} + } + + async function pageAiEnsureHermesSession(forceCreate) { + pageAiLoadSessions(); + var current = pageAiCurrentSession(); + var runProfile = pageAiRunProfile(); + if (!forceCreate && current && String(current.id || '').startsWith('mnote_') && current.profile === runProfile) return current; + var response = await fetch('/api/hermes/client/sessions', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + workspaceId: resolveWorkspaceId(doc.body), + documentId: currentDocumentId(), + sourceKind: currentSourceKind(), + rootUri: currentRootUri(), + traceId: 'page-ai-' + Date.now().toString(36), + profile: runProfile, + agentId: pageAiCurrentAgentId(), + profileId: pageUiState.pageAiAcpRuntime === 'hermes' ? runProfile : '', + title: current && current.title ? current.title : '当前页问答' + }) + }); + var payload = await response.json().catch(function(){ return null; }); + if (!response.ok || !payload || payload.ok !== true) { + throw new Error(pageAiErrorMessage(payload, 'hermes_session_failed_' + response.status)); + } + var session = { + id: String(payload.sessionId || '').trim(), + title: String(payload.title || '当前页问答'), + agentId: pageAiCurrentAgentId(), + profileId: pageUiState.pageAiAcpRuntime === 'hermes' ? runProfile : '', + profile: String(payload.profile || runProfile).trim() || 'default', + acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', + persistence: String(payload.persistence || '').trim(), + sessionStorage: String(payload.sessionStorage || '').trim(), + permissionLevel: String(payload.permissionLevel || '').trim(), + shareId: String(payload.shareId || '').trim(), + acpSessionId: String(payload.acpSessionId || payload.acp_session_id || '').trim(), + createdAt: Date.now(), + updatedAt: Date.now(), + messages: pageUiState.pageAiMessages.slice() + }; + pageUiState.pageAiSessions = pageAiNormalizeSessions([session]); + pageUiState.pageAiActiveSessionId = session.id; + pageAiPersistSessions(); + renderPageAiConversation(); + return session; + } + + async function pageAiRestoreHermesSession() { + var current = pageAiCurrentSession(); + if (!current || !String(current.id || '').startsWith('mnote_')) return; + var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(current.id) + '/resume?' + pageAiBackendSessionQuery({}), { + method: 'POST', + headers: { 'accept': 'application/json' } + }); + if (!response.ok) return; + var payload = await response.json().catch(function(){ return null; }); + if (payload && payload.persistence === 'convex_acp_runtime_store') { + pageAiApplyBackendSessionDetail(payload); + renderPageAiConversation(); + renderPageAiControls(); + return; + } + var session = payload && (payload.session || (payload.upstream && payload.upstream.session) || payload); + var messages = session && Array.isArray(session.messages) ? session.messages : []; + pageAiApplyRuntimeState(payload && payload.runtime); + if (session && (session.profile || session.profileName)) { + current.profile = String(session.profile || session.profileName || current.profile || pageAiCurrentProfile()); + } + if (!messages.length) { + renderPageAiControls(); + return; + } + pageUiState.pageAiMessages = messages.slice(-300).map(function(message) { + return { + role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'), + content: String(message.content || '') + }; + }); + current.messages = pageUiState.pageAiMessages.slice(); + current.updatedAt = Date.now(); + renderPageAiConversation(); + renderPageAiControls(); + } + + function pageAiCurrentSession() { + return pageUiState.pageAiSessions.find(function(session) { + return session.id === pageUiState.pageAiActiveSessionId; + }) || null; + } + + function pageAiSyncCurrentSessionMessages() { + var session = pageAiCurrentSession(); + if (!session) return; + var runProfile = pageAiRunProfile(); + session.messages = Array.isArray(pageUiState.pageAiMessages) ? pageUiState.pageAiMessages.slice(-300) : []; + session.agentId = pageAiCurrentAgentId(); + session.profileId = pageUiState.pageAiAcpRuntime === 'hermes' ? runProfile : ''; + session.profile = runProfile; + session.acpRuntime = pageUiState.pageAiAcpRuntime || 'reasonix'; + session.updatedAt = Date.now(); + } + + function pageAiSetActiveSession(sessionId) { + var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; }); + if (!session) return; + pageUiState.pageAiActiveSessionId = session.id; + if (session.agentId) pageUiState.pageAiAgentId = pageAiNormalizeAgentId(session.agentId); + if (session.acpRuntime) pageUiState.pageAiAcpRuntime = session.acpRuntime; + if (session.profileId || session.profile) pageAiSetActiveProfile(session.profileId || session.profile); + pageUiState.pageAiMessages = Array.isArray(session.messages) ? session.messages.slice() : []; + pageUiState.pageAiPage = 'chat'; + pageAiPersistSessions(); + renderPageAiControls(); + renderPageAiConversation(); + if (session.source === 'acp' || String(session.id || '').startsWith('mnote_')) { + void pageAiLoadBackendSessionDetail(session.id).catch(function(error) { + pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); + renderPageAiControls(); + }); + } + } + + function pageAiStartNewSession() { + var session = pageAiNewSession(); + pageUiState.pageAiSessions = pageAiNormalizeSessions([session].concat(pageUiState.pageAiSessions)); + pageUiState.pageAiActiveSessionId = session.id; + pageUiState.pageAiMessages = []; + pageUiState.pageAiPage = 'chat'; + pageAiPersistSessions(); + renderPageAiControls(); + renderPageAiConversation(); + } + + async function pageAiRenameBackendSession(sessionId) { + var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; }); + if (!session) return; + var title = win.prompt('重命名 AI 会话', session.title || '当前页问答'); + if (title === null) return; + title = String(title || '').trim(); + if (!title) return; + var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/rename?' + pageAiBackendSessionQuery({}), { + method: 'POST', + headers: { 'content-type': 'application/json', 'accept': 'application/json' }, + body: JSON.stringify({ title: title }) + }); + var payload = await response.json().catch(function(){ return null; }); + if (!response.ok || !payload || payload.ok !== true) { + throw new Error(pageAiErrorMessage(payload, 'session_rename_failed_' + response.status)); + } + session.title = String((payload.result && payload.result.title) || title); + session.updatedAt = Date.now(); + pageAiPersistSessions(); + renderPageAiConversation(); + renderPageAiControls(); + } + + async function pageAiDeleteBackendSession(sessionId) { + var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; }); + if (!session) return; + if (!win.confirm('确定删除 AI 会话“' + (session.title || sessionId) + '”吗?')) return; + var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '?' + pageAiBackendSessionQuery({}), { + method: 'DELETE', + headers: { 'accept': 'application/json' } + }); + var payload = await response.json().catch(function(){ return null; }); + if (!response.ok || !payload || payload.ok !== true) { + throw new Error(pageAiErrorMessage(payload, 'session_delete_failed_' + response.status)); + } + pageUiState.pageAiSessions = pageUiState.pageAiSessions.filter(function(item) { return item.id !== sessionId; }); + if (pageUiState.pageAiActiveSessionId === sessionId) { + var next = pageUiState.pageAiSessions[0] || pageAiNewSession(); + if (!pageUiState.pageAiSessions.length) pageUiState.pageAiSessions = [next]; + pageUiState.pageAiActiveSessionId = next.id; + pageUiState.pageAiMessages = Array.isArray(next.messages) ? next.messages.slice() : []; + } + pageAiPersistSessions(); + renderPageAiConversation(); + renderPageAiControls(); + } + + async function pageAiResumeBackendSession(sessionId) { + sessionId = String(sessionId || pageUiState.pageAiActiveSessionId || '').trim(); + if (!sessionId) return; + pageAiSetActiveSession(sessionId); + var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/resume?' + pageAiBackendSessionQuery({}), { + method: 'POST', + headers: { 'accept': 'application/json' } + }); + var payload = await response.json().catch(function(){ return null; }); + if (!response.ok || !payload || payload.ok !== true) { + throw new Error(pageAiErrorMessage(payload, 'session_resume_failed_' + response.status)); + } + pageAiApplyBackendSessionDetail(payload); + pageUiState.pageAiPage = 'chat'; + renderPageAiConversation(); + renderPageAiControls(); + } + + return { + pageAiStorageKey, + pageAiBackendSessionQuery, + pageAiNewSession, + pageAiNormalizeSessions, + pageAiNormalizeBackendSessionRow, + pageAiMergeSessions, + pageAiSessionStorageLabel, + pageAiLoadSessions, + pageAiLoadBackendSessions, + pageAiMessageFromRuntimeEvent, + pageAiApplyBackendSessionDetail, + pageAiLoadBackendSessionDetail, + pageAiSearchBackendSessions, + pageAiPersistSessions, + pageAiEnsureHermesSession, + pageAiRestoreHermesSession, + pageAiCurrentSession, + pageAiSyncCurrentSessionMessages, + pageAiSetActiveSession, + pageAiStartNewSession, + pageAiRenameBackendSession, + pageAiDeleteBackendSession, + pageAiResumeBackendSession + }; +} diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-skill-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-skill-runtime.js new file mode 100644 index 00000000..06bdbd50 --- /dev/null +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-skill-runtime.js @@ -0,0 +1,285 @@ +export function createSidebarPageAiSkillRuntime(context) { + const { + pageAiCurrentAgentId, + pageAiCurrentProfile, + pageAiLoadSkills, + pageAiNormalizeArray, + pageAiPersistAiPreference, + pageAiPersistRawAiPreference, + pageAiProfileValue, + pageUiState, + renderPageAiControls, + } = context; + + function pageAiSkillSourceOptions() { + var options = [ + { value: 'mnote', group: 'mnote', label: 'mnote', profile: '' }, + { value: 'reasonix', group: 'reasonix', label: 'reasonix', profile: '' } + ]; + pageAiNormalizeArray(pageUiState.pageAiProfiles).forEach(function(profile) { + var profileId = pageAiProfileValue(profile); + if (!profileId) return; + var label = profile.kind === 'shared' + ? (profile.baseProfile === 'lite' || profileId === 'shared_lite' ? 'hermes_lite' : 'Hermes_shared') + : 'Hermes_user'; + var alias = String(profile.alias || '').trim(); + options.push({ + value: 'hermes:' + profileId, + group: 'hermes', + profile: profileId, + label: alias && alias !== label ? label + ' · ' + alias : label, + readonly: profile.readonly === true + }); + }); + return options; + } + + function pageAiDefaultSkillSource() { + if (pageAiCurrentAgentId() === 'hermes') return 'hermes:' + pageAiCurrentProfile(); + if (pageAiCurrentAgentId() === 'reasonix') return 'reasonix'; + return 'mnote'; + } + + function pageAiNormalizeSkillSource(source) { + var value = String(source || '').trim(); + var options = pageAiSkillSourceOptions(); + if (options.some(function(option) { return option.value === value; })) return value; + if (value === 'hermes') return 'hermes:' + pageAiCurrentProfile(); + if (value === 'mnote_builtin') return 'mnote'; + var fallback = pageAiDefaultSkillSource(); + if (options.some(function(option) { return option.value === fallback; })) return fallback; + return options.length ? options[0].value : 'mnote'; + } + + function pageAiCurrentSkillSource() { + var normalized = pageAiNormalizeSkillSource(pageUiState.pageAiActiveSkillSource); + pageUiState.pageAiActiveSkillSource = normalized; + return normalized; + } + + function pageAiSetSkillSource(source) { + var normalized = pageAiNormalizeSkillSource(source); + pageUiState.pageAiActiveSkillSource = normalized; + pageAiPersistAiPreference('skills.active_source', normalized); + pageUiState.pageAiSkills = { categories: [], archived: [] }; + pageUiState.pageAiSkillError = ''; + void pageAiLoadSkills(); + renderPageAiControls(); + } + + function pageAiSkillSourceParts(source) { + var normalized = pageAiNormalizeSkillSource(source); + if (normalized.indexOf('hermes:') === 0) { + return { group: 'hermes', profile: normalized.slice('hermes:'.length), source: normalized }; + } + if (normalized === 'reasonix') return { group: 'reasonix', profile: '', source: normalized }; + return { group: 'mnote', profile: '', source: 'mnote' }; + } + + function pageAiCurrentSkillSourceLabel() { + var source = pageAiCurrentSkillSource(); + var option = pageAiSkillSourceOptions().find(function(item) { return item.value === source; }); + return option ? option.label : source; + } + + function pageAiSkillOriginLabel(skill) { + var origin = String(skill && skill.origin || '').trim(); + if (origin === 'generated' || String(skill && skill.createdBy || '') === 'agent') return '生成'; + if (origin === 'installed') return '安装'; + if (origin === 'builtin') return '内置'; + if (origin === 'copied') return '本地'; + var source = String(skill && skill.source || '').trim(); + if (source === 'hub') return '安装'; + if (source === 'builtin') return '内置'; + if (source === 'reasonix') { + if (origin === 'project') return 'Reasonix 项目'; + if (origin === 'global') return 'Reasonix 全局'; + return 'Reasonix'; + } + return '本地'; + } + + function pageAiSkillPreferenceKey(group, profile) { + var groupName = String(group || '').trim(); + if (groupName === 'mnote') return 'ai.agent.mnote_builtin.skills.enabled'; + if (groupName === 'reasonix') return 'ai.agent.reasonix.skills.enabled'; + if (groupName === 'hermes') { + var nextProfile = String(profile || pageAiCurrentProfile() || 'default').trim() || 'default'; + return 'ai.agent.hermes.profile.' + nextProfile + '.skills.enabled'; + } + return ''; + } + + function pageAiSkillPreferenceTable(group, profile) { + var key = pageAiSkillPreferenceKey(group, profile); + var preferences = pageUiState.pageAiSkillPreferences || {}; + var value = preferences[key]; + if (value && typeof value === 'object' && !Array.isArray(value)) return value; + return {}; + } + + function pageAiHermesHideBuiltinPreferenceKey(profile) { + return 'ai.agent.hermes.skills.hide_builtin'; + } + + function pageAiHideHermesBuiltinSkills(profile) { + var preferences = pageUiState.pageAiSkillPreferences || {}; + return preferences[pageAiHermesHideBuiltinPreferenceKey(profile)] === true; + } + + function pageAiReasonixMemoryEnabled() { + var preferences = pageUiState.pageAiSkillPreferences || {}; + return preferences['ai.agent.reasonix.memory_enabled'] === true; + } + + function pageAiSetReasonixMemoryEnabled(enabled) { + var preferences = Object.assign({}, pageUiState.pageAiSkillPreferences || {}); + preferences['ai.agent.reasonix.memory_enabled'] = Boolean(enabled); + pageUiState.pageAiSkillPreferences = preferences; + pageAiPersistRawAiPreference('ai.agent.reasonix.memory_enabled', Boolean(enabled)); + renderPageAiControls(); + } + + function pageAiSetHideHermesBuiltinSkills(enabled, profile) { + var key = pageAiHermesHideBuiltinPreferenceKey(profile); + var preferences = Object.assign({}, pageUiState.pageAiSkillPreferences || {}); + preferences[key] = Boolean(enabled); + pageUiState.pageAiSkillPreferences = preferences; + pageAiPersistRawAiPreference(key, Boolean(enabled)); + renderPageAiControls(); + } + + function pageAiSkillIsBuiltin(skill) { + var origin = String(skill && skill.origin || '').trim(); + var source = String(skill && skill.source || '').trim(); + return origin === 'builtin' || source === 'builtin'; + } + + function pageAiToggleableSkillEntries(group, profile) { + var catalogKey = group === 'hermes' && profile ? 'hermes:' + profile : group; + var catalog = pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs[catalogKey] + ? pageUiState.pageAiSkillCatalogs[catalogKey] + : pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs[group] + ? pageUiState.pageAiSkillCatalogs[group] + : { categories: [], archived: [] }; + var overrides = pageAiSkillPreferenceTable(group, profile); + var result = []; + pageAiNormalizeArray(catalog.categories).forEach(function(category) { + pageAiNormalizeArray(category.skills).forEach(function(skill) { + var id = String(skill.id || skill.name || '').trim(); + if (!id) return; + result.push({ + group: group, + profile: profile || '', + category: category.name, + id: id, + name: skill.name || id, + title: skill.title || skill.name || id, + description: skill.description || '', + enabled: group === 'hermes' ? skill.enabled !== false : (skill.enabled !== false && overrides[id] !== false), + toggleable: skill.toggleable !== false, + source: skill.source || group, + origin: skill.origin || '', + readOnly: skill.readOnly === true || skill.readonly === true || skill.configurable === false, + builtin: pageAiSkillIsBuiltin(skill), + configurable: skill.configurable !== false, + configScope: skill.configScope || '', + skillKind: skill.skillKind || '', + profileId: skill.profileId || '', + toolNames: skill.toolNames || [], + requiresContextRefs: skill.requiresContextRefs || [] + }); + }); + }); + pageAiNormalizeArray(catalog.archived).forEach(function(skill) { + var id = String(skill.id || skill.name || '').trim(); + if (!id) return; + result.push({ + group: group, + profile: profile || '', + category: 'archived', + id: id, + name: skill.name || id, + title: skill.title || skill.name || id, + description: skill.description || '', + enabled: group === 'hermes' ? skill.enabled !== false : (skill.enabled !== false && overrides[id] !== false), + toggleable: skill.toggleable !== false, + source: skill.source || group, + origin: skill.origin || '', + readOnly: skill.readOnly === true || skill.readonly === true || skill.configurable === false, + builtin: pageAiSkillIsBuiltin(skill), + configurable: skill.configurable !== false, + configScope: skill.configScope || '', + skillKind: skill.skillKind || '', + profileId: skill.profileId || '', + toolNames: skill.toolNames || [], + requiresContextRefs: skill.requiresContextRefs || [] + }); + }); + return result; + } + + function pageAiAllSkillEntries() { + return [] + .concat(pageAiToggleableSkillEntries('mnote', '')) + .concat(pageAiToggleableSkillEntries('reasonix', '')) + .concat(pageAiToggleableSkillEntries('hermes', pageAiCurrentProfile())); + } + + function pageAiSkillGroupCollapsed(group) { + var table = pageUiState.pageAiCollapsedSkillGroups || {}; + return table[String(group || '').trim()] === true; + } + + function pageAiToggleSkillGroup(group) { + var normalized = String(group || '').trim(); + if (!normalized) return; + var table = Object.assign({}, pageUiState.pageAiCollapsedSkillGroups || {}); + table[normalized] = table[normalized] !== true; + pageUiState.pageAiCollapsedSkillGroups = table; + pageAiPersistRawAiPreference('ai.common.skills.groups.collapsed', table); + renderPageAiControls(); + } + + function pageAiSetSkillPreference(group, skillId, enabled, profile) { + var key = pageAiSkillPreferenceKey(group, profile); + if (!key || !skillId) return; + var preferences = Object.assign({}, pageUiState.pageAiSkillPreferences || {}); + var current = preferences[key] && typeof preferences[key] === 'object' && !Array.isArray(preferences[key]) + ? Object.assign({}, preferences[key]) + : {}; + current[skillId] = Boolean(enabled); + preferences[key] = current; + pageUiState.pageAiSkillPreferences = preferences; + pageAiPersistRawAiPreference(key, current); + } + + function pageAiSkillEnabled(skill) { + return skill.enabled !== false; + } + + return { + pageAiSkillSourceOptions, + pageAiDefaultSkillSource, + pageAiNormalizeSkillSource, + pageAiCurrentSkillSource, + pageAiSetSkillSource, + pageAiSkillSourceParts, + pageAiCurrentSkillSourceLabel, + pageAiSkillOriginLabel, + pageAiSkillPreferenceKey, + pageAiSkillPreferenceTable, + pageAiHermesHideBuiltinPreferenceKey, + pageAiHideHermesBuiltinSkills, + pageAiReasonixMemoryEnabled, + pageAiSetReasonixMemoryEnabled, + pageAiSetHideHermesBuiltinSkills, + pageAiSkillIsBuiltin, + pageAiToggleableSkillEntries, + pageAiAllSkillEntries, + pageAiSkillGroupCollapsed, + pageAiToggleSkillGroup, + pageAiSetSkillPreference, + pageAiSkillEnabled + }; +} diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-target-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-target-runtime.js new file mode 100644 index 00000000..9f6ed76b --- /dev/null +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-target-runtime.js @@ -0,0 +1,758 @@ +export function createSidebarPageAiTargetRuntime(context) { + const { + currentDocumentId, + currentRootUri, + currentSourceKind, + currentPageOptions, + documentRef, + escapeHtml, + pageAiEnsureContextRefState, + pageUiState, + resolveWorkspaceId, + searchText, + pageAiNormalizeArray, + } = context; + + function pageAiCloneJson(value) { + if (value == null) return null; + try { + return JSON.parse(JSON.stringify(value)); + } catch (_) { + return null; + } + } + + function localMarkdownRelativePathFromPageAiDocumentId(documentId) { + var value = String(documentId || '').trim(); + if (!value.startsWith('local-md:')) return ''; + return value.slice('local-md:'.length).replace(/~2F/g, '/'); + } + + function localMarkdownDocumentIdFromPageAiRelativePath(relativePath) { + var normalized = String(relativePath || '').trim().replace(/\\/g, '/').replace(/^\/+/, ''); + if (!normalized) return ''; + return 'local-md:' + normalized.split('/').map(function(segment) { + return encodeURIComponent(segment).replace(/%20/g, '~20'); + }).join('~2F'); + } + + function pageAiWorkspacePathForDocument(documentId, seed) { + var relativePath = String(seed && seed.relativePath || localMarkdownRelativePathFromPageAiDocumentId(documentId) || '').trim().replace(/\\/g, '/').replace(/^\/+/, ''); + var resolvedDocumentId = String(documentId || seed && seed.documentId || '').trim() + || localMarkdownDocumentIdFromPageAiRelativePath(relativePath); + return { + schema: 'mnote.workspace_path.v1', + workspaceId: String(seed && seed.workspaceId || resolveWorkspaceId(documentRef.body) || '').trim(), + sourceKind: String(seed && seed.sourceKind || currentSourceKind() || '').trim(), + rootUri: String(seed && seed.rootUri || currentRootUri() || '').trim(), + relativePath: relativePath, + documentId: resolvedDocumentId, + objectIdentity: seed && seed.objectIdentity && typeof seed.objectIdentity === 'object' + ? seed.objectIdentity + : String(seed && seed.objectIdentity || resolvedDocumentId || '').trim(), + assetId: String(seed && seed.assetId || '').trim(), + resourceKind: String(seed && seed.resourceKind || 'markdown_page').trim() + }; + } + + function pageAiResourceKindForTarget(entry) { + var kind = String(entry && (entry.editorKind || entry.kind) || '').trim().toLowerCase(); + var assetId = String(entry && entry.assetId || '').trim(); + var path = String(entry && entry.path || '').trim().toLowerCase(); + if (kind === 'page' || kind === 'markdown_page') return 'markdown_page'; + if (kind === 'mindmap' || path.endsWith('.mindmap.json')) return 'mindmap'; + if (kind === 'office' || kind === 'onlyoffice' || kind === 'only_office' || /\.(doc|docx|ppt|pptx|xls|xlsx)$/.test(path)) return 'only_office'; + if (kind === 'resource' && assetId) return 'resource'; + return kind || 'markdown_page'; + } + + function pageAiTargetId(entry) { + if (!entry || typeof entry !== 'object') return ''; + var workspacePath = entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : {}; + var entryIdentity = typeof entry.objectIdentity === 'string' ? entry.objectIdentity : ''; + var workspaceIdentity = typeof workspacePath.objectIdentity === 'string' ? workspacePath.objectIdentity : ''; + return String(entryIdentity || workspaceIdentity || entry.documentId || entry.assetId || entry.path || '').trim(); + } + + function pageAiWorkspacePathForTarget(entry) { + var seed = Object.assign({}, entry && entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : {}); + var resourceKind = pageAiResourceKindForTarget(entry); + if (!seed.relativePath && entry && entry.path) seed.relativePath = entry.path; + if (!seed.assetId && entry && entry.assetId) seed.assetId = entry.assetId; + var seedResourceKind = String(seed.resourceKind || '').trim(); + if (!seedResourceKind || seedResourceKind === 'page' || seedResourceKind === 'office') seed.resourceKind = resourceKind; + if (!seed.objectIdentity && entry && entry.objectIdentity) seed.objectIdentity = entry.objectIdentity; + if (!seed.workspaceId && entry && entry.workspaceId) seed.workspaceId = entry.workspaceId; + return pageAiWorkspacePathForDocument(entry && entry.documentId || currentDocumentId(), seed); + } + + function pageAiTargetFromOpenEditor(entry, source) { + if (!entry || typeof entry !== 'object') return null; + var workspacePath = pageAiWorkspacePathForTarget(entry); + var targetId = pageAiTargetId(entry) || workspacePath.objectIdentity || workspacePath.documentId; + if (!targetId) return null; + var objectIdentity = typeof entry.objectIdentity === 'string' + ? entry.objectIdentity + : (typeof workspacePath.objectIdentity === 'string' ? workspacePath.objectIdentity : targetId); + return { + schema: 'mnote.ai_editor_target.v1', + source: source || 'open_editors_snapshot', + targetId: targetId, + objectIdentity: objectIdentity, + workspacePath: workspacePath, + paneRole: entry.paneRole || 'primary', + documentId: entry.documentId || workspacePath.documentId, + workspaceId: entry.workspaceId || workspacePath.workspaceId || resolveWorkspaceId(documentRef.body), + editorKind: entry.editorKind || entry.kind || workspacePath.resourceKind, + resourceKind: workspacePath.resourceKind, + title: entry.title || '', + active: entry.active === true, + dirtyState: entry.dirtyState || '', + preview: entry.preview === true, + pinned: entry.pinned === true, + lastActiveAt: entry.lastActiveAt || 0, + assetId: entry.assetId || workspacePath.assetId || '', + path: entry.path || workspacePath.relativePath || '', + onlyofficeSessionId: entry.onlyofficeSessionId || entry.bridgeSessionId || '', + bridgeSessionId: entry.bridgeSessionId || entry.onlyofficeSessionId || '', + bridgeSessionReady: entry.bridgeSessionReady === true + }; + } + + function normalizeOpenEditorEntry(entry) { + if (!entry || typeof entry !== 'object') return null; + return { + objectIdentity: String(entry.objectIdentity || '').trim(), + workspacePath: entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : null, + paneRole: String(entry.paneRole || 'primary').trim() === 'secondary' ? 'secondary' : 'primary', + documentId: String(entry.documentId || '').trim(), + workspaceId: String(entry.workspaceId || '').trim(), + title: String(entry.title || '').trim(), + kind: String(entry.kind || entry.editorKind || '').trim(), + editorKind: String(entry.editorKind || entry.kind || '').trim(), + active: entry.active === true, + dirtyState: String(entry.dirtyState || entry.dirtyGuard || '').trim(), + preview: entry.preview === true, + pinned: entry.pinned === true, + lastActiveAt: Number(entry.lastActiveAt || 0) || 0, + assetId: String(entry.assetId || '').trim(), + path: String(entry.path || '').trim(), + onlyofficeSessionId: String(entry.onlyofficeSessionId || entry.bridgeSessionId || '').trim(), + bridgeSessionId: String(entry.bridgeSessionId || entry.onlyofficeSessionId || '').trim(), + bridgeSessionReady: entry.bridgeSessionReady === true + }; + } + + function currentPageAiOpenEditorsSnapshot() { + var snapshot = null; + try { + if (window.__mnoteDocumentPaneRuntime && typeof window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot === 'function') { + snapshot = window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot(); + } + } catch (_) {} + if (!snapshot || typeof snapshot !== 'object') snapshot = window.__mnoteOpenEditorsSnapshot || null; + if (!snapshot || typeof snapshot !== 'object') return null; + var editors = Array.isArray(snapshot.editors) + ? snapshot.editors.map(normalizeOpenEditorEntry).filter(Boolean) + : []; + var resources = Array.isArray(snapshot.resourceEditors) + ? snapshot.resourceEditors.map(normalizeOpenEditorEntry).filter(Boolean) + : editors.filter(function(entry) { return entry.kind !== 'page'; }); + var normalizeGroup = function(group, paneRole) { + var groupEditors = group && Array.isArray(group.editors) + ? group.editors.map(normalizeOpenEditorEntry).filter(Boolean) + : editors.filter(function(entry) { return entry.paneRole === paneRole; }); + var groupResources = group && Array.isArray(group.resourceEditors) + ? group.resourceEditors.map(normalizeOpenEditorEntry).filter(Boolean) + : resources.filter(function(entry) { return entry.paneRole === paneRole; }); + var groupActive = groupEditors.find(function(entry) { return entry.active; }) || null; + return { + paneRole: paneRole, + activeObjectIdentity: String(group && group.activeObjectIdentity || (groupActive ? groupActive.objectIdentity : '') || '').trim(), + editors: groupEditors, + resourceEditors: groupResources + }; + }; + var groups = { + primary: normalizeGroup(snapshot.groups && snapshot.groups.primary, 'primary'), + secondary: normalizeGroup(snapshot.groups && snapshot.groups.secondary, 'secondary') + }; + var activeObjectIdentity = String(snapshot.activeObjectIdentity || '').trim(); + var allTargets = editors.concat(resources); + var activeEditor = allTargets.find(function(entry) { + return entry.active && (!activeObjectIdentity || entry.objectIdentity === activeObjectIdentity); + }) || groups.primary.editors.find(function(entry) { + return entry.active; + }) || groups.primary.resourceEditors.find(function(entry) { + return entry.active; + }) || groups.secondary.editors.find(function(entry) { + return entry.active; + }) || groups.secondary.resourceEditors.find(function(entry) { + return entry.active; + }) || null; + return { + schema: String(snapshot.schema || 'mnote.open_editors_snapshot.v1'), + generatedAt: Number(snapshot.generatedAt || 0) || Date.now(), + activeObjectIdentity: activeObjectIdentity || (activeEditor ? activeEditor.objectIdentity : ''), + activeEditor: activeEditor, + editors: editors, + resourceEditors: resources, + groups: groups + }; + } + + function pageAiFallbackEditorTarget() { + var fallbackDocumentId = currentDocumentId(); + var fallbackWorkspacePath = pageAiWorkspacePathForDocument(fallbackDocumentId, null); + var fallbackTargetId = fallbackWorkspacePath.objectIdentity || fallbackDocumentId || ''; + return { + schema: 'mnote.ai_editor_target.v1', + source: 'fallback_current_document', + targetId: fallbackTargetId, + objectIdentity: fallbackTargetId, + workspacePath: fallbackWorkspacePath, + paneRole: 'primary', + documentId: fallbackDocumentId, + workspaceId: resolveWorkspaceId(documentRef.body), + editorKind: 'page', + active: true, + dirtyState: '', + preview: false, + pinned: true, + lastActiveAt: Date.now(), + assetId: '', + path: '' + }; + } + + function pageAiEditorTargetCandidates() { + var snapshot = currentPageAiOpenEditorsSnapshot(); + var entries = []; + if (snapshot) { + entries = pageAiNormalizeArray(snapshot.editors).concat(pageAiNormalizeArray(snapshot.resourceEditors)); + } + var seen = {}; + var targets = entries.map(function(entry) { + return pageAiTargetFromOpenEditor(entry, 'open_editors_snapshot'); + }).filter(function(target) { + var id = String(target && target.targetId || '').trim(); + if (!id || seen[id]) return false; + seen[id] = true; + return true; + }); + if (!targets.length) targets.push(pageAiFallbackEditorTarget()); + return targets; + } + + function currentPageAiEditorTarget() { + var targets = pageAiEditorTargetCandidates(); + var selectedId = String(pageUiState.pageAiSelectedTargetId || '').trim(); + var selected = selectedId ? targets.find(function(target) { return target.targetId === selectedId; }) : null; + return selected + || targets.find(function(target) { return target.active === true; }) + || targets[0] + || pageAiFallbackEditorTarget(); + } + + function currentPageAiPageEditorTarget() { + var snapshot = currentPageAiOpenEditorsSnapshot(); + var documentId = currentDocumentId(); + var workspaceId = resolveWorkspaceId(documentRef.body); + var sourceKind = currentSourceKind(); + var rootUri = currentRootUri(); + var relativePath = localMarkdownRelativePathFromPageAiDocumentId(documentId); + var fallbackWorkspacePath = pageAiWorkspacePathForDocument(documentId, { + relativePath: relativePath + }); + var pageEditor = snapshot && Array.isArray(snapshot.editors) + ? snapshot.editors.find(function(entry) { + return entry + && String(entry.editorKind || entry.kind || '') === 'page' + && String(entry.documentId || '').trim() === String(documentId || '').trim(); + }) + : null; + var workspacePath = pageEditor && pageEditor.workspacePath + ? Object.assign({}, pageEditor.workspacePath, { + workspaceId: workspaceId, + sourceKind: sourceKind, + rootUri: rootUri, + relativePath: relativePath, + documentId: documentId, + resourceKind: pageEditor.workspacePath.resourceKind || 'markdown_page', + objectIdentity: pageEditor.workspacePath.objectIdentity || fallbackWorkspacePath.objectIdentity + }) + : fallbackWorkspacePath; + var objectIdentity = String(pageEditor && pageEditor.objectIdentity || workspacePath.objectIdentity || documentId || '').trim(); + return { + schema: 'mnote.ai_editor_target.v1', + source: pageEditor ? 'current_page_from_open_editors_snapshot' : 'current_page_fallback', + targetId: objectIdentity, + objectIdentity: objectIdentity, + workspacePath: Object.assign({}, workspacePath, { objectIdentity: objectIdentity }), + paneRole: String(pageEditor && pageEditor.paneRole || 'primary').trim() === 'secondary' ? 'secondary' : 'primary', + documentId: documentId, + workspaceId: workspaceId, + editorKind: 'page', + active: pageEditor ? pageEditor.active === true : true, + dirtyState: String(pageEditor && pageEditor.dirtyState || '').trim(), + preview: pageEditor ? pageEditor.preview === true : false, + pinned: true, + lastActiveAt: Number(pageEditor && pageEditor.lastActiveAt || 0) || Date.now(), + assetId: '', + path: relativePath + }; + } + + function currentPageAiScopedEditorTarget() { + var selected = pageAiEnsureContextRefState(); + return selected.active_editor ? currentPageAiEditorTarget() : currentPageAiPageEditorTarget(); + } + + function pageAiSetRunTargetSnapshot(snapshot) { + pageUiState.pageAiCurrentRunTargetSnapshot = snapshot || null; + var target = snapshot && snapshot.editorTarget ? snapshot.editorTarget : null; + var documentId = String(target && target.documentId || snapshot && snapshot.documentId || '').trim(); + var workspaceId = String(target && target.workspaceId || snapshot && snapshot.workspaceId || '').trim(); + var rootUri = String(target && target.workspacePath && target.workspacePath.rootUri || snapshot && snapshot.rootUri || '').trim(); + if (documentId) documentRef.documentElement.setAttribute('data-mnote-page-ai-run-target-document-id', documentId); + if (workspaceId) documentRef.documentElement.setAttribute('data-mnote-page-ai-run-target-workspace-id', workspaceId); + if (rootUri) documentRef.documentElement.setAttribute('data-mnote-page-ai-run-target-root-uri', rootUri); + } + + function assertPageAiTargetInCurrentWorkspace(editorTarget) { + var target = editorTarget && typeof editorTarget === 'object' ? editorTarget : {}; + var workspacePath = target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {}; + var targetSourceKind = String(workspacePath.sourceKind || '').trim(); + var currentKind = String(currentSourceKind() || '').trim(); + if (targetSourceKind && currentKind && targetSourceKind !== currentKind) { + var sourceError = new Error('AI target 与当前页面 sourceKind 不一致,请重新选择当前工作区内的目标。'); + sourceError.code = 'page_ai_target_workspace_mismatch'; + throw sourceError; + } + var targetRootUri = String(workspacePath.rootUri || '').trim(); + var currentRoot = String(currentRootUri() || '').trim(); + if (targetRootUri && currentRoot && targetRootUri !== currentRoot) { + var rootError = new Error('AI target 与当前本地工作区 rootUri 不一致,请重新选择当前工作区内的目标。'); + rootError.code = 'page_ai_target_workspace_mismatch'; + throw rootError; + } + var targetWorkspaceId = String(target.workspaceId || workspacePath.workspaceId || '').trim(); + var currentWorkspaceId = String(resolveWorkspaceId(documentRef.body) || '').trim(); + if (targetWorkspaceId && currentWorkspaceId && targetWorkspaceId !== currentWorkspaceId) { + var workspaceError = new Error('AI target 与当前 workspaceId 不一致,请重新选择当前工作区内的目标。'); + workspaceError.code = 'page_ai_target_workspace_mismatch'; + throw workspaceError; + } + } + + function pageAiBuildContextRefs(scopedContext, runTargetSnapshot) { + var selected = pageAiEnsureContextRefState(); + var refs = []; + var documentId = currentDocumentId(); + var rootUri = currentRootUri(); + var workspaceId = resolveWorkspaceId(documentRef.body); + var editorTarget = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget(); + if (selected.current_page) { + refs.push({ + kind: 'current_page', + documentId: documentId, + rootUri: rootUri, + workspaceId: workspaceId + }); + } + if (selected.selection && scopedContext && scopedContext.selectedText) { + refs.push({ + kind: 'selection', + documentId: documentId, + rootUri: rootUri, + selectedBlockId: scopedContext.selectedBlockId || '' + }); + } + if (selected.active_editor && editorTarget) { + var workspacePath = editorTarget.workspacePath && typeof editorTarget.workspacePath === 'object' ? editorTarget.workspacePath : {}; + refs.push({ + kind: 'active_editor', + documentId: editorTarget.documentId || documentId, + workspaceId: editorTarget.workspaceId || workspacePath.workspaceId || workspaceId, + rootUri: workspacePath.rootUri || rootUri, + relativePath: workspacePath.relativePath || '', + editorKind: editorTarget.editorKind || '', + resourceKind: editorTarget.resourceKind || workspacePath.resourceKind || '', + targetId: editorTarget.targetId || workspacePath.objectIdentity || '', + objectIdentity: editorTarget.objectIdentity || workspacePath.objectIdentity || '', + assetId: editorTarget.assetId || workspacePath.assetId || '', + onlyofficeSessionId: editorTarget.onlyofficeSessionId || editorTarget.bridgeSessionId || '', + bridgeSessionId: editorTarget.bridgeSessionId || editorTarget.onlyofficeSessionId || '' + }); + } + if (selected.file) { + refs.push({ + kind: 'file', + documentId: documentId, + rootUri: rootUri, + relativePath: localMarkdownRelativePathFromPageAiDocumentId(documentId) + }); + } + if (selected.folder) { + refs.push({ + kind: 'folder', + rootUri: rootUri, + relativePath: '' + }); + } + if (selected.changed_files) { + refs.push({ + kind: 'changed_files', + rootUri: rootUri, + sinceRunTargetSnapshot: runTargetSnapshot && runTargetSnapshot.frozenAt || null + }); + } + return refs.filter(function(ref) { + return ref && String(ref.kind || '').trim(); + }); + } + + function pageAiBuildAllowedRoots() { + return pageAiNormalizeArray(pageUiState.pageAiAllowedRoots).map(function(root) { + return { + rootUri: root.rootUri, + permission: root.permission === 'write' || root.permission === 'read_write' ? 'write' : 'read', + recursive: root.recursive !== false, + source: root.source === 'auto' || root.source === 'user' || root.source === 'admin' + ? 'sqlite_directory_grant' + : (root.source || 'sqlite_directory_grant'), + grantId: root.id || '' + }; + }); + } + + function pageAiBuildRunTargetSnapshot(scopedContext, prompt) { + var aiContext = scopedContext && scopedContext.pageContext && scopedContext.pageContext.aiContext + ? scopedContext.pageContext.aiContext + : {}; + var editorTarget = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget(); + return { + schema: 'mnote.page_ai_run_target_snapshot.v1', + source: 'open_editors_snapshot', + frozenAt: Date.now(), + workspaceId: resolveWorkspaceId(documentRef.body), + documentId: currentDocumentId(), + sourceKind: currentSourceKind(), + rootUri: currentRootUri(), + contextScope: pageUiState.pageAiContextScope || 'page', + promptPreview: searchText(prompt || '').slice(0, 160), + editorTarget: pageAiCloneJson(editorTarget), + activeEditorTarget: pageAiCloneJson(aiContext.activeEditorTarget || editorTarget), + openEditorsSnapshot: pageAiCloneJson(aiContext.openEditorsSnapshot || currentPageAiOpenEditorsSnapshot()) + }; + } + + function pageAiContextKindsFromRefs(contextRefs) { + var kinds = {}; + pageAiNormalizeArray(contextRefs).forEach(function(ref) { + var kind = String(ref && ref.kind || '').trim(); + if (kind) kinds[kind] = true; + }); + return kinds; + } + + function pageAiPageContextForRefs(pageContext, contextRefs) { + var cloned = pageAiCloneJson(pageContext) || {}; + var aiContext = cloned.aiContext && typeof cloned.aiContext === 'object' ? cloned.aiContext : {}; + var kinds = pageAiContextKindsFromRefs(contextRefs); + delete cloned.documentBlocks; + delete cloned.evidence; + delete aiContext.contextBlocks; + delete aiContext.pageText; + delete aiContext.pageXml; + delete aiContext.truncated; + delete aiContext.warnings; + if (!kinds.selection) { + delete aiContext.selectedText; + delete aiContext.selectedBlockIds; + delete aiContext.selectedBlocks; + delete aiContext.allowedTargetBlockIds; + } + cloned.aiContext = aiContext; + return cloned; + } + + function pageAiBuildAgentTargetPackage(scopedContext, runTargetSnapshot) { + var target = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget(); + var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object' + ? pageAiWorkspacePathForDocument(target.documentId || currentDocumentId(), target.workspacePath) + : pageAiWorkspacePathForDocument(target && target.documentId || currentDocumentId(), null); + var relativePath = String(workspacePath.relativePath || '').trim(); + var allowedFiles = relativePath ? [relativePath] : []; + var writable = pageAiBuildAllowedRoots().some(function(root) { + return String(root && root.rootUri || '').trim() === String(workspacePath.rootUri || '').trim() + && String(root && root.permission || '').trim() === 'write'; + }); + var primaryTargetId = String(target && target.targetId || workspacePath.objectIdentity || workspacePath.documentId || '').trim(); + var onlyofficeSessionId = String(target && (target.onlyofficeSessionId || target.bridgeSessionId) || '').trim(); + var targetEntry = { + targetId: primaryTargetId, + objectIdentity: primaryTargetId, + documentId: workspacePath.documentId, + workspaceId: workspacePath.workspaceId, + sourceKind: workspacePath.sourceKind, + rootUri: workspacePath.rootUri, + relativePath: relativePath, + resourceKind: workspacePath.resourceKind, + assetId: workspacePath.assetId || target && target.assetId || '', + onlyofficeSessionId: onlyofficeSessionId, + bridgeSessionId: onlyofficeSessionId, + paneRole: target && target.paneRole || 'primary', + title: target && target.title || '', + policy: { + permission: allowedFiles.length && writable ? 'read_write' : 'read', + writeRequiresCleanBuffer: true, + conflictPolicy: 'fail_on_dirty_or_stale' + } + }; + return { + schema: 'mnote.agent_target_package.v1', + source: 'page_ai_run_target_snapshot', + frozenAt: runTargetSnapshot && runTargetSnapshot.frozenAt || Date.now(), + primaryTargetId: primaryTargetId, + onlyofficeSessionId: onlyofficeSessionId, + bridgeSessionId: onlyofficeSessionId, + workspaceId: workspacePath.workspaceId, + sourceKind: workspacePath.sourceKind, + rootUri: workspacePath.rootUri, + documentId: workspacePath.documentId, + objectIdentity: primaryTargetId, + resourceKind: workspacePath.resourceKind, + workspacePath: workspacePath, + currentFile: relativePath ? { + rootUri: workspacePath.rootUri, + relativePath: relativePath, + documentId: workspacePath.documentId, + objectIdentity: primaryTargetId, + resourceKind: workspacePath.resourceKind + } : null, + allowedFiles: allowedFiles, + targets: [targetEntry], + policy: { + writeRequiresExplicitTarget: true, + allowedFilesSource: 'selected_page_ai_target', + conflictPolicy: 'fail_on_dirty_or_stale' + } + }; + } + + function pageAiBlockingDirtyState(dirtyState) { + var state = String(dirtyState || '').trim(); + var normalized = state.toLowerCase(); + if (normalized === 'dirty') return 'Dirty'; + if (normalized === 'stale') return 'Stale'; + if (normalized === 'deleted') return 'Deleted'; + if (normalized === 'externalmodified' || normalized === 'external-change-conflict' || normalized === 'hasexternalconflict') return 'ExternalModified'; + return ''; + } + + async function fetchPageAiTargetBufferState(editorTarget) { + if (currentSourceKind() !== 'local_folder') return null; + var target = editorTarget && typeof editorTarget === 'object' ? editorTarget : {}; + var workspacePath = target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {}; + var resourceKind = String(target.resourceKind || target.editorKind || workspacePath.resourceKind || '').trim(); + if (resourceKind === 'office' || resourceKind === 'only_office' || resourceKind === 'onlyoffice') return null; + var documentId = String(target.documentId || currentDocumentId() || '').trim(); + var rootUri = String(workspacePath.rootUri || currentRootUri() || '').trim(); + if (!documentId || !rootUri) return null; + var relativePath = String(workspacePath.relativePath || '').trim() + || localMarkdownRelativePathFromPageAiDocumentId(documentId); + var url = new URL('/api/documents/buffer-state', window.location.origin); + url.searchParams.set('documentId', documentId); + url.searchParams.set('sourceKind', 'local_folder'); + url.searchParams.set('rootUri', rootUri); + var workspaceId = String(target.workspaceId || resolveWorkspaceId(documentRef.body) || '').trim(); + if (workspaceId) url.searchParams.set('workspaceId', workspaceId); + if (relativePath) url.searchParams.set('relativePath', relativePath); + try { + var response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } }); + var payload = await response.json().catch(function() { return null; }); + if (!response.ok || !payload || payload.ok !== true) return null; + return payload.result || null; + } catch (_) { + return null; + } + } + + async function assertPageAiTargetWritable(editorTarget) { + var workspacePath = editorTarget && editorTarget.workspacePath && typeof editorTarget.workspacePath === 'object' ? editorTarget.workspacePath : {}; + var resourceKind = String(editorTarget && (editorTarget.resourceKind || editorTarget.editorKind) || workspacePath.resourceKind || '').trim(); + var onlyofficeSessionId = String(editorTarget && (editorTarget.onlyofficeSessionId || editorTarget.bridgeSessionId) || '').trim(); + if ((resourceKind === 'office' || resourceKind === 'only_office' || resourceKind === 'onlyoffice') && !onlyofficeSessionId) { + var sessionError = new Error('ONLYOFFICE 资源仍在连接 MNote bridge,请等待 Office 页面加载完成后再让 AI 操作。'); + sessionError.code = 'page_ai_onlyoffice_bridge_not_ready'; + throw sessionError; + } + var snapshotState = pageAiBlockingDirtyState(editorTarget && editorTarget.dirtyState); + var bufferState = await fetchPageAiTargetBufferState(editorTarget); + var bufferDirtyState = pageAiBlockingDirtyState(bufferState && bufferState.dirtyState); + var blockedState = bufferDirtyState || snapshotState; + if (!blockedState) return bufferState; + var documentId = String(editorTarget && editorTarget.documentId || currentDocumentId() || '').trim(); + var error = new Error('目标文档存在未保存或外部变更状态(' + blockedState + '),请先保存、解决冲突或刷新后再让 AI 写入。'); + error.code = 'page_ai_target_buffer_not_writable'; + error.documentId = documentId; + error.dirtyState = blockedState; + throw error; + } + + function currentPageAiSelectedText() { + try { + var selection = window.getSelection ? window.getSelection() : null; + return selection ? searchText(selection.toString() || '') : ''; + } catch (_) { + return ''; + } + } + + function pageAiProjectionBlocks(aggregate) { + var blocks = aggregate && aggregate.body && aggregate.body.blockDocument && aggregate.body.blockDocument.blocks; + return Array.isArray(blocks) ? blocks : []; + } + + function pageAiBlockText(block) { + return searchText(block && (block.text || block.title || block.content) || ''); + } + + function pageAiSelectedBlockIdsFromSelection() { + try { + var selection = window.getSelection ? window.getSelection() : null; + if (!selection || selection.rangeCount === 0 || searchText(selection.toString() || '') === '') return []; + var range = selection.getRangeAt(0); + var editor = documentRef.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror'); + if (!(editor instanceof HTMLElement)) return []; + return Array.from(editor.children).filter(function(node) { + if (!(node instanceof HTMLElement)) return false; + try { + return range.intersectsNode(node); + } catch (_) { + return false; + } + }).map(function(node) { + return searchText(node.getAttribute('data-id') || node.id || ''); + }).filter(Boolean); + } catch (_) { + return []; + } + } + + function pageAiBlocksToPageXml(blocks, aggregate) { + var revision = aggregate && aggregate.body ? String(aggregate.body.revision || '') : ''; + var pageId = currentDocumentId() || 'current-page'; + var lines = ['']; + blocks.forEach(function(block) { + var blockId = String(block && (block.blockId || block.id) || ''); + var type = String(block && block.type || 'paragraph'); + var revisionRef = String(block && block.revisionRef || ''); + var level = block && block.attrs && block.attrs.level ? ' level="' + escapeHtml(block.attrs.level) + '"' : ''; + lines.push(' ' + escapeHtml(pageAiBlockText(block)) + ''); + }); + lines.push(''); + return lines.join('\n'); + } + + function buildPageAiContext(contextSnapshot, scope, selectedText) { + var aggregate = contextSnapshot.aggregate || {}; + var body = aggregate.body || {}; + var allBlocks = pageAiProjectionBlocks(aggregate); + var selectedBlockIds = scope === 'selection' ? pageAiSelectedBlockIdsFromSelection() : []; + var selectedSet = {}; + selectedBlockIds.forEach(function(id) { selectedSet[id] = true; }); + var selectedBlocks = selectedBlockIds.length + ? allBlocks.filter(function(block) { return selectedSet[String(block.blockId || block.id || '')]; }) + : []; + var contextBlocks = selectedBlocks.length ? selectedBlocks : allBlocks.slice(0, 120); + var truncated = !selectedBlocks.length && allBlocks.length > contextBlocks.length; + return { + schema: 'mnote.page_ai_context.v1', + workspaceId: resolveWorkspaceId(documentRef.body), + documentId: currentDocumentId(), + activeEditorTarget: currentPageAiScopedEditorTarget(), + openEditorsSnapshot: currentPageAiOpenEditorsSnapshot(), + scope: scope, + revision: body.revision || null, + conflictDetectionKey: body.conflictDetectionKey || null, + selectedText: selectedText || '', + selectedBlockIds: selectedBlockIds, + allowedTargetBlockIds: selectedBlockIds, + selectedBlocks: selectedBlocks, + contextBlocks: contextBlocks, + pageText: contextBlocks.map(pageAiBlockText).filter(Boolean).join('\n'), + pageXml: pageAiBlocksToPageXml(contextBlocks, aggregate), + truncated: truncated, + warnings: truncated ? [{ code: 'page_ai_context_truncated', message: '页面 AI context 已按前 120 个块裁剪' }] : [] + }; + } + + function pageAiScopedPageContext(contextSnapshot) { + var aggregate = contextSnapshot.aggregate || {}; + var body = contextSnapshot.body || {}; + var subtree = contextSnapshot.subtree || null; + var outline = subtree && subtree.outline ? subtree.outline : null; + var scope = pageUiState.pageAiContextScope || 'page'; + var title = aggregate.head && aggregate.head.title ? aggregate.head.title : ''; + var selectedText = scope === 'selection' ? currentPageAiSelectedText() : ''; + var aiContext = buildPageAiContext(contextSnapshot, scope, selectedText); + var editorTarget = aiContext.activeEditorTarget || currentPageAiScopedEditorTarget(); + return { + pageContext: { + contextScope: scope, + documentBlocks: null, + node: { + documentId: currentDocumentId(), + title: title + }, + subtree: null, + outline: null, + pageSubtreeSource: scope === 'page' ? (contextSnapshot.pageSubtreeSource || 'none') : 'scope:' + scope, + evidence: selectedText ? [{ kind: 'selection', text: selectedText }] : null, + pageOptions: scope === 'options' || scope === 'page' ? currentPageOptions() : null, + contentAccess: 'mnote.context.read_current_page', + aiContext: aiContext + }, + editorTarget: editorTarget, + selectedText: selectedText, + selectedBlockId: aiContext.selectedBlockIds[0] || null + }; + } + + return { + currentPageAiSelectedText, + currentPageAiEditorTarget, + currentPageAiPageEditorTarget, + currentPageAiScopedEditorTarget, + currentPageAiOpenEditorsSnapshot, + pageAiBlockText, + pageAiBlockingDirtyState, + pageAiBlocksToPageXml, + pageAiBuildAgentTargetPackage, + pageAiBuildAllowedRoots, + pageAiBuildContextRefs, + pageAiBuildRunTargetSnapshot, + pageAiCloneJson, + pageAiContextKindsFromRefs, + pageAiEditorTargetCandidates, + pageAiFallbackEditorTarget, + pageAiPageContextForRefs, + pageAiProjectionBlocks, + pageAiResourceKindForTarget, + pageAiSelectedBlockIdsFromSelection, + pageAiSetRunTargetSnapshot, + pageAiScopedPageContext, + pageAiTargetFromOpenEditor, + pageAiTargetId, + pageAiWorkspacePathForDocument, + pageAiWorkspacePathForTarget, + assertPageAiTargetInCurrentWorkspace, + assertPageAiTargetWritable, + buildPageAiContext, + fetchPageAiTargetBufferState, + localMarkdownDocumentIdFromPageAiRelativePath, + localMarkdownRelativePathFromPageAiDocumentId, + }; +} diff --git a/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js index 0370e112..b0a0cc45 100644 --- a/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js @@ -262,6 +262,9 @@ export function createSidebarPageSettingsRuntime(context) { var shell = document.querySelector('.document-shell'); var editorRoot = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]'); var editorSurface = editorRoot && editorRoot.querySelector('.editor-surface'); + var mindmapWidthPreference = currentPageWidthPreferences().mindmap || DEFAULT_PAGE_WIDTH_PREFERENCES.mindmap; + var mindmapMaxWidth = String(mindmapWidthPreference.cssMaxWidth || pageWidthCssMaxWidth(mindmapWidthPreference.resolvedMode)); + var mindmapMaxWidthValue = mindmapMaxWidth === 'none' ? 'none' : mindmapMaxWidth; if (shell instanceof HTMLElement) { var widthPreference = activeResourceWidthPreference(options); var cssMaxWidth = String(widthPreference.cssMaxWidth || pageWidthCssMaxWidth(widthPreference.resolvedMode)); @@ -276,12 +279,14 @@ export function createSidebarPageSettingsRuntime(context) { shell.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers)); shell.style.width = '100%'; shell.style.maxWidth = cssMaxWidth === 'none' ? 'none' : cssMaxWidth; + shell.style.setProperty('--mnote-mindmap-block-max-width', mindmapMaxWidthValue); } if (editorRoot instanceof HTMLElement) { editorRoot.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout))); editorRoot.setAttribute('data-page-small-text', String(Boolean(options.smallText))); editorRoot.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers)); editorRoot.setAttribute('data-page-embed-default-block-id', options.embedDefaultBlockId == null ? '' : String(options.embedDefaultBlockId)); + editorRoot.style.setProperty('--mnote-mindmap-block-max-width', mindmapMaxWidthValue); } if (editorSurface instanceof HTMLElement) { editorSurface.setAttribute('data-layout-density', String(options.layoutDensity || 'normal')); @@ -299,6 +304,7 @@ export function createSidebarPageSettingsRuntime(context) { document.documentElement.setAttribute('data-global-show-heading-numbers', String(globalShowHeadingNumbers)); document.documentElement.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers)); document.documentElement.setAttribute('data-page-hide-title-header', String(Boolean(options.hideTitleHeader))); + document.documentElement.style.setProperty('--mnote-mindmap-block-max-width', mindmapMaxWidthValue); var titleHeader = document.querySelector('.document-pane[data-pane-role="primary"] .document-shell-header') || document.querySelector('.document-shell-header'); if (titleHeader instanceof HTMLElement) { var hidden = Boolean(options.hideTitleHeader); diff --git a/rust/crates/mnote-web/browser/sidebar-tree-runtime.js b/rust/crates/mnote-web/browser/sidebar-tree-runtime.js index 21afb147..97afca92 100644 --- a/rust/crates/mnote-web/browser/sidebar-tree-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-tree-runtime.js @@ -32,46 +32,6 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim pageWidthPreferences: null, historySnapshots: [], pageSettingsOpen: false, - pageAiOpen: false, - pageAiBusy: false, - pageAiMessages: [], - pageAiSuggestionIndex: 0, - pageAiProvider: 'hermes', - pageAiPage: 'chat', - pageAiRunStatus: 'idle', - pageAiCurrentRunId: '', - pageAiAcpRuntime: 'reasonix', - pageAiAcpRuntimes: [], - pageAiQueueLength: 0, - pageAiQueuedItems: [], - pageAiStoppedRunIds: {}, - pageAiAbortController: null, - pageAiContextScope: 'page', - pageAiTools: [], - pageAiToolsError: '', - pageAiGatewayHealth: null, - pageAiGatewayHealthError: '', - pageAiLastToolCall: null, - pageAiProfiles: [], - pageAiActiveProfileName: 'mnoteai', - pageAiProfileError: '', - pageAiProfileMemory: { memory: '', user: '', soul: '' }, - pageAiProfileMemoryDrafts: { memory: '', user: '', soul: '' }, - pageAiProfileMemoryError: '', - pageAiSkills: { categories: [], archived: [] }, - pageAiSkillCatalogs: { mnote: { categories: [], archived: [] }, reasonix: { categories: [], archived: [] }, hermes: { categories: [], archived: [] } }, - pageAiSkillPreferences: {}, - pageAiCollapsedSkillGroups: {}, - pageAiSkillQuery: '', - pageAiSkillError: '', - pageAiSkillLoadSeq: 0, - pageAiSessions: [], - pageAiActiveSessionId: '', - pageAiSessionSearchQuery: '', - pageAiSessionSearchResults: [], - pageAiSessionSearchTimer: 0, - pageAiSessionError: '', - pageAiPermissionRequests: [], localIndexSummary: { scopeKey: '', loading: false, @@ -928,6 +888,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim function fileTreeRuntimeDeps() { return { currentSourceKind: currentSourceKind, + currentRootUri: currentRootUri, currentDocumentId: currentDocumentId, localFilePathFromAssetId: localFilePathFromAssetId, rowTitle: rowTitle, @@ -2382,12 +2343,16 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim const pageAiLoadProfileMemory = (...args) => sidebarPageAi.pageAiLoadProfileMemory(...args); const pageAiLoadSkills = (...args) => sidebarPageAi.pageAiLoadSkills(...args); const pageAiSwitchProfile = (...args) => sidebarPageAi.pageAiSwitchProfile(...args); + const pageAiSetSkillSource = (...args) => sidebarPageAi.pageAiSetSkillSource(...args); const pageAiToggleSkillGroup = (...args) => sidebarPageAi.pageAiToggleSkillGroup(...args); + const pageAiSetReasonixMemoryEnabled = (...args) => sidebarPageAi.pageAiSetReasonixMemoryEnabled(...args); const pageAiSetHideHermesBuiltinSkills = (...args) => sidebarPageAi.pageAiSetHideHermesBuiltinSkills(...args); const pageAiSetContextScope = (...args) => sidebarPageAi.pageAiSetContextScope(...args); const pageAiSetAgentId = (...args) => sidebarPageAi.pageAiSetAgentId(...args); const pageAiToggleContextRef = (...args) => sidebarPageAi.pageAiToggleContextRef(...args); const pageAiSetAgentPopoverOpen = (...args) => sidebarPageAi.pageAiSetAgentPopoverOpen(...args); + const pageAiSetTargetPopoverOpen = (...args) => sidebarPageAi.pageAiSetTargetPopoverOpen(...args); + const pageAiSelectTarget = (...args) => sidebarPageAi.pageAiSelectTarget(...args); const updatePageAiTriggerState = (...args) => sidebarPageAi.updatePageAiTriggerState(...args); const sidebarAttachmentOpen = createSidebarAttachmentOpenRuntime({ @@ -2532,255 +2497,6 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim return; } - var pageAiClose = closestAction(e.target, '[data-page-ai-action="close"]'); - if (pageAiClose) { - e.preventDefault(); - closePageAiDrawer(); - return; - } - - var pageAiSettings = closestAction(e.target, '[data-page-ai-action="open-hermes-settings"]'); - if (pageAiSettings) { - e.preventDefault(); - pageAiOpenHermesSettings(); - return; - } - - var pageAiStop = closestAction(e.target, '[data-page-ai-action="stop-run"]'); - if (pageAiStop) { - e.preventDefault(); - void pageAiStopRun(); - return; - } - - var pageAiRotate = closestAction(e.target, '[data-page-ai-action="rotate"]'); - if (pageAiRotate) { - e.preventDefault(); - pageUiState.pageAiSuggestionIndex += 1; - renderPageAiSuggestions(); - return; - } - - var pageAiIntent = closestAction(e.target, '[data-page-ai-intent]'); - if (pageAiIntent) { - e.preventDefault(); - var intentName = pageAiIntent.getAttribute('data-page-ai-intent') || ''; - if (intentName === 'create-summary') { - void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_summary,为当前页面创建或更新 AI Summary。'); - return; - } - if (intentName === 'create-ai-note') { - void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_ai_note,基于当前页面创建一篇新的 AI Note。'); - return; - } - } - - var pageAiTab = closestAction(e.target, '[data-page-ai-tab]'); - if (pageAiTab) { - e.preventDefault(); - pageUiState.pageAiPage = pageAiTab.getAttribute('data-page-ai-tab') || 'chat'; - if (pageUiState.pageAiPage === 'runtime') void pageAiLoadGatewayHealth(); - renderPageAiControls(); - renderPageAiConversation(); - return; - } - - var pageAiProvider = closestAction(e.target, '[data-page-ai-provider]'); - if (pageAiProvider) { - e.preventDefault(); - pageUiState.pageAiProvider = pageAiProvider.getAttribute('data-page-ai-provider') || 'hermes'; - renderPageAiProviderButtons(); - return; - } - - var pageAiAgent = closestAction(e.target, '[data-page-ai-agent-id]'); - if (pageAiAgent) { - e.preventDefault(); - pageAiSetAgentId(pageAiAgent.getAttribute('data-page-ai-agent-id') || 'reasonix'); - return; - } - - var pageAiAgentButton = closestAction(e.target, '[data-page-ai-agent-button]'); - if (pageAiAgentButton) { - e.preventDefault(); - pageAiSetAgentPopoverOpen(pageAiAgentButton.getAttribute('aria-expanded') !== 'true'); - return; - } - - var pageAiAgentPopoverClose = closestAction(e.target, '[data-page-ai-action="close-agent-popover"]'); - if (pageAiAgentPopoverClose) { - e.preventDefault(); - pageAiSetAgentPopoverOpen(false); - return; - } - - var pageAiContextButton = closestAction(e.target, '[data-page-ai-context-button]'); - if (pageAiContextButton) { - e.preventDefault(); - sidebarPageAi.pageAiSetContextPopoverOpen( - pageAiContextButton.getAttribute('aria-expanded') !== 'true' - ); - return; - } - - var pageAiContextPopoverClose = closestAction(e.target, '[data-page-ai-action="close-context-popover"]'); - if (pageAiContextPopoverClose) { - e.preventDefault(); - sidebarPageAi.pageAiSetContextPopoverOpen(false); - return; - } - - var pageAiContextRef = closestAction(e.target, '[data-page-ai-context-ref]'); - if (pageAiContextRef) { - e.preventDefault(); - pageAiToggleContextRef(pageAiContextRef.getAttribute('data-page-ai-context-ref') || ''); - return; - } - - var pageAiMemorySave = closestAction(e.target, '[data-page-ai-memory-save]'); - if (pageAiMemorySave) { - e.preventDefault(); - void pageAiSaveProfileMemory(pageAiMemorySave.getAttribute('data-page-ai-memory-save') || ''); - return; - } - - var pageAiSkillToggle = closestAction(e.target, '[data-page-ai-skill-toggle]'); - if (pageAiSkillToggle) { - e.preventDefault(); - var skillName = pageAiSkillToggle.getAttribute('data-page-ai-skill-toggle') || ''; - var skillGroup = pageAiSkillToggle.getAttribute('data-page-ai-skill-group') || ''; - var skillProfile = pageAiSkillToggle.getAttribute('data-page-ai-skill-profile') || ''; - var nextEnabled = pageAiSkillToggle.getAttribute('aria-pressed') !== 'true'; - void pageAiToggleSkill(skillName, nextEnabled, skillGroup, skillProfile); - return; - } - - var pageAiSkillGroupToggle = closestAction(e.target, '[data-page-ai-skill-group-toggle]'); - if (pageAiSkillGroupToggle) { - e.preventDefault(); - pageAiToggleSkillGroup(pageAiSkillGroupToggle.getAttribute('data-page-ai-skill-group-toggle') || ''); - return; - } - - var pageAiToolToggle = closestAction(e.target, '[data-page-ai-tool-toggle]'); - if (pageAiToolToggle) { - e.preventDefault(); - var toolName = pageAiToolToggle.getAttribute('data-page-ai-tool-toggle') || ''; - var nextToolEnabled = pageAiToolToggle.getAttribute('aria-pressed') !== 'true'; - void pageAiToggleTool(toolName, nextToolEnabled); - return; - } - - var pageAiSessionResume = closestAction(e.target, '[data-page-ai-session-resume]'); - if (pageAiSessionResume) { - e.preventDefault(); - void pageAiResumeBackendSession(pageAiSessionResume.getAttribute('data-page-ai-session-resume') || '').catch(function(error) { - pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); - renderPageAiControls(); - }); - return; - } - - var pageAiSessionRename = closestAction(e.target, '[data-page-ai-session-rename]'); - if (pageAiSessionRename) { - e.preventDefault(); - void pageAiRenameBackendSession(pageAiSessionRename.getAttribute('data-page-ai-session-rename') || '').catch(function(error) { - pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); - renderPageAiControls(); - }); - return; - } - - var pageAiSessionDelete = closestAction(e.target, '[data-page-ai-session-delete]'); - if (pageAiSessionDelete) { - e.preventDefault(); - void pageAiDeleteBackendSession(pageAiSessionDelete.getAttribute('data-page-ai-session-delete') || '').catch(function(error) { - pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); - renderPageAiControls(); - }); - return; - } - - var pageAiPermissionAction = closestAction(e.target, '[data-page-ai-permission-action]'); - if (pageAiPermissionAction) { - e.preventDefault(); - pageAiResolvePermission( - pageAiPermissionAction.getAttribute('data-page-ai-permission-id') || '', - pageAiPermissionAction.getAttribute('data-page-ai-permission-action') || 'deny' - ); - return; - } - - var pageAiOpenLocationAction = closestAction(e.target, '[data-page-ai-open-location]'); - if (pageAiOpenLocationAction) { - e.preventDefault(); - var loc = String(pageAiOpenLocationAction.getAttribute('data-page-ai-open-location') || '').trim(); - if (loc) pageAiOpenLocation(loc); - return; - } - - var pageAiSession = closestAction(e.target, '[data-page-ai-session]'); - if (pageAiSession) { - e.preventDefault(); - pageAiSetActiveSession(pageAiSession.getAttribute('data-page-ai-session') || ''); - return; - } - - var pageAiSuggestion = closestAction(e.target, '[data-page-ai-suggestion]'); - if (pageAiSuggestion) { - e.preventDefault(); - var text = pageAiSuggestion.getAttribute('data-page-ai-suggestion') || ''; - var inputNode = ensurePageAiDrawer().querySelector('[data-page-ai-input]'); - if (inputNode instanceof HTMLTextAreaElement) { - inputNode.value = text; - inputNode.focus(); - } - return; - } - - var pageAiNewSession = closestAction(e.target, '[data-page-ai-action="new-session"]'); - if (pageAiNewSession) { - e.preventDefault(); - pageUiState.pageAiPage = 'chat'; - pageAiStartNewSession(); - return; - } - - var pageAiHistory = closestAction(e.target, '[data-page-ai-action="history"]'); - if (pageAiHistory) { - e.preventDefault(); - pageAiLoadSessions(); - pageUiState.pageAiPage = pageUiState.pageAiPage === 'history' ? 'chat' : 'history'; - renderPageAiControls(); - renderPageAiConversation(); - if (pageUiState.pageAiPage === 'history') { - void pageAiLoadBackendSessions().catch(function(error) { - pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); - renderPageAiControls(); - }); - } - return; - } - - var pageAiSend = closestAction(e.target, '[data-page-ai-action="send"]'); - if (pageAiSend) { - e.preventDefault(); - var input = ensurePageAiDrawer().querySelector('[data-page-ai-input]'); - if (input instanceof HTMLTextAreaElement) { - var message = input.value; - input.value = ''; - void sendPageAiMessage(message); - } - return; - } - - var cancelQueuedRun = closestAction(e.target, '[data-page-ai-action="cancel-queued-run"]'); - if (cancelQueuedRun) { - e.preventDefault(); - void pageAiCancelQueuedRun(cancelQueuedRun.getAttribute('data-page-ai-queue-id')); - return; - } - var searchTrigger = closestAction(e.target, '[data-mnote-action="open-search-modal"]'); if (searchTrigger) { e.preventDefault(); @@ -3112,79 +2828,9 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim closeSearchModal(); closeTreeContextMenu(); } - if (event.key === 'Enter' && !event.shiftKey) { - var aiInput = closestAction(event.target, '[data-page-ai-input]'); - if (aiInput instanceof HTMLTextAreaElement) { - event.preventDefault(); - var text = aiInput.value; - aiInput.value = ''; - void sendPageAiMessage(text); - } - } - }); - - document.addEventListener('input', function(event) { - var skillSearch = closestAction(event.target, '[data-page-ai-skill-search]'); - if (skillSearch instanceof HTMLInputElement) { - pageUiState.pageAiSkillQuery = skillSearch.value; - renderPageAiControls(); - return; - } - var sessionSearch = closestAction(event.target, '[data-page-ai-session-search]'); - if (sessionSearch instanceof HTMLInputElement) { - var sessionQuery = sessionSearch.value; - window.clearTimeout(pageUiState.pageAiSessionSearchTimer || 0); - pageUiState.pageAiSessionSearchTimer = window.setTimeout(function() { - void pageAiSearchBackendSessions(sessionQuery).catch(function(error) { - pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); - renderPageAiControls(); - }); - }, 200); - return; - } - var memoryEditor = closestAction(event.target, '[data-page-ai-memory-editor]'); - if (memoryEditor instanceof HTMLTextAreaElement) { - var section = memoryEditor.getAttribute('data-page-ai-memory-editor') || ''; - if (['memory', 'user', 'soul'].indexOf(section) >= 0) { - pageUiState.pageAiProfileMemoryDrafts[section] = memoryEditor.value; - } - } }); document.addEventListener('change', function(event) { - var pageAiAcpRuntimeSelect = closestAction(event.target, '[data-page-ai-acp-runtime]'); - if (pageAiAcpRuntimeSelect instanceof HTMLSelectElement) { - var next = String(pageAiAcpRuntimeSelect.value || 'reasonix').trim() || 'reasonix'; - pageUiState.pageAiAcpRuntime = next; - pageUiState.pageAiSkills = { categories: [], archived: [] }; - pageUiState.pageAiSkillError = ''; - pageAiPersistSessions(); - void pageAiLoadProfiles(); - if (next !== 'reasonix') void pageAiLoadProfileMemory(); - void pageAiLoadSkills(); - void pageAiLoadBackendSessions().catch(function(error) { - pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); - }); - renderPageAiControls(); - renderPageAiProviderButtons(); - return; - } - var pageAiProfileSelect = closestAction(event.target, '[data-page-ai-profile-select]'); - if (pageAiProfileSelect instanceof HTMLSelectElement) { - void pageAiSwitchProfile(pageAiProfileSelect.value); - return; - } - var pageAiHideHermesBuiltin = closestAction(event.target, '[data-page-ai-hide-hermes-builtin]'); - if (pageAiHideHermesBuiltin instanceof HTMLInputElement) { - pageAiSetHideHermesBuiltinSkills(pageAiHideHermesBuiltin.checked); - return; - } - var pageAiContextSelect = closestAction(event.target, '[data-page-ai-context-scope]'); - if (pageAiContextSelect instanceof HTMLSelectElement) { - pageAiSetContextScope(pageAiContextSelect.value); - renderPageAiControls(); - return; - } var globalCheckbox = closestAction(event.target, '[data-global-option-checkbox="showHeadingNumbers"]'); if (globalCheckbox instanceof HTMLInputElement) { writeGlobalShowHeadingNumbers(globalCheckbox.checked); @@ -3218,6 +2864,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim void persistPageWidthPreference(pageWidthType, pageWidthSelect.value); } }); + sidebarPageAi.installPageAiDelegates(); function initializePageUiSurfaces() { pageUiState.pageOptions = null; @@ -3243,6 +2890,14 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim } scheduleInitializePageUiSurfaces(); + function mnoteDevHotReloadEnabled() { + try { + return new URL(import.meta.url).searchParams.has('devHot'); + } catch (_) { + return false; + } + } + function installMnoteDevHotReload() { var bootId = ''; var failedOnce = false; @@ -3271,7 +2926,9 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim tick(); timer = window.setInterval(tick, 1000); } - installMnoteDevHotReload(); + if (mnoteDevHotReloadEnabled()) { + installMnoteDevHotReload(); + } document.addEventListener('dragstart', function(event) { var pageRow = closestAction(event.target, '.tree-row[data-shell-mode="page"][draggable="true"]'); diff --git a/rust/crates/mnote-web/src/acp_bridge.rs b/rust/crates/mnote-web/src/acp_bridge.rs index 3dc7346c..3ee73500 100644 --- a/rust/crates/mnote-web/src/acp_bridge.rs +++ b/rust/crates/mnote-web/src/acp_bridge.rs @@ -260,6 +260,20 @@ pub fn acp_event_to_sse(event: AcpSessionEvent) -> Option { event: "session.info.updated".into(), data: json!({ "title": title }), }), + AcpSessionEvent::ProviderConversationBound { + provider, + remote_conversation_id, + remote_url, + acp_session_id, + } => Some(SseEvent { + event: "provider.conversation.bound".into(), + data: json!({ + "provider": provider, + "remoteConversationId": remote_conversation_id, + "remoteUrl": remote_url, + "acpSessionId": acp_session_id, + }), + }), AcpSessionEvent::PlanUpdate { entries } => Some(SseEvent { event: "plan.updated".into(), data: json!({ "entries": entries }), diff --git a/rust/crates/mnote-web/src/acp_session_manager.rs b/rust/crates/mnote-web/src/acp_session_manager.rs index 83dd0689..e96d78ea 100644 --- a/rust/crates/mnote-web/src/acp_session_manager.rs +++ b/rust/crates/mnote-web/src/acp_session_manager.rs @@ -54,6 +54,13 @@ pub enum AcpSessionEvent { }, /// 会话元数据更新,例如自动标题。 SessionInfoUpdate { title: String }, + /// Provider 侧远端会话绑定,例如豆包 conversation_id。 + ProviderConversationBound { + provider: String, + remote_conversation_id: String, + remote_url: Option, + acp_session_id: Option, + }, /// 计划条目更新。 PlanUpdate { entries: Vec }, /// 连接关闭或异常。 @@ -765,6 +772,46 @@ impl AcpSessionManager { Some(AcpSessionEvent::PlanUpdate { entries: summaries }) } + SessionUpdate::Unknown { + session_update, + extra, + } if session_update == "provider.conversation.bound" => { + let provider = extra + .get("provider") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("doubao-web") + .to_string(); + let remote_conversation_id = extra + .get("remoteConversationId") + .or_else(|| extra.get("remote_conversation_id")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty())? + .to_string(); + let remote_url = extra + .get("remoteUrl") + .or_else(|| extra.get("remote_url")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + let acp_session_id = extra + .get("acpSessionId") + .or_else(|| extra.get("acp_session_id")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + Some(AcpSessionEvent::ProviderConversationBound { + provider, + remote_conversation_id, + remote_url, + acp_session_id, + }) + } + SessionUpdate::Unknown { .. } => { warn!("ACP unknown session/update variant"); None diff --git a/rust/crates/mnote-web/src/hermes_tools/manifest.rs b/rust/crates/mnote-web/src/hermes_tools/manifest.rs index 48d53838..78ce18e3 100644 --- a/rust/crates/mnote-web/src/hermes_tools/manifest.rs +++ b/rust/crates/mnote-web/src/hermes_tools/manifest.rs @@ -33,8 +33,44 @@ pub fn manifest() -> Value { available_tool("mnote.page.update_options", "更新当前页面设置", ["page.write"]), mindmap_fetch_tool(), mindmap_apply_ops_tool(), + mindmap_create_from_outline_tool(), office_fetch_summary_tool(), office_propose_changes_tool(), + onlyoffice_session_current_tool(), + onlyoffice_capabilities_tool(), + onlyoffice_selection_get_tool(), + onlyoffice_document_insert_text_tool(), + onlyoffice_document_replace_selection_tool(), + onlyoffice_document_insert_html_tool(), + onlyoffice_document_export_tool(), + onlyoffice_document_search_replace_tool(), + onlyoffice_document_insert_table_tool(), + onlyoffice_document_get_comments_tool(), + onlyoffice_document_add_comment_tool(), + onlyoffice_sheet_get_sheets_tool(), + onlyoffice_sheet_add_sheet_tool(), + onlyoffice_sheet_rename_sheet_tool(), + onlyoffice_sheet_get_range_tool(), + onlyoffice_sheet_get_range_values_tool(), + onlyoffice_sheet_get_values_tool(), + onlyoffice_sheet_set_value_tool(), + onlyoffice_sheet_set_formula_tool(), + onlyoffice_sheet_batch_set_values_tool(), + onlyoffice_sheet_set_range_values_tool(), + onlyoffice_sheet_format_range_tool(), + onlyoffice_sheet_set_dimensions_tool(), + onlyoffice_sheet_sort_range_tool(), + onlyoffice_sheet_add_chart_tool(), + onlyoffice_presentation_get_slides_tool(), + onlyoffice_presentation_get_slide_texts_tool(), + onlyoffice_presentation_get_shapes_tool(), + onlyoffice_presentation_add_text_slide_tool(), + onlyoffice_presentation_replace_text_tool(), + onlyoffice_presentation_set_shape_text_tool(), + onlyoffice_presentation_delete_slide_tool(), + onlyoffice_presentation_add_table_tool(), + onlyoffice_presentation_clear_slide_tool(), + onlyoffice_presentation_add_shape_tool(), available_tool("mnote.artifact.create_summary", "为当前页面创建或更新 AI Summary", ["artifact.write"]), available_tool("mnote.artifact.create_ai_note", "基于当前页面创建新的 AI Note", ["artifact.write"]) ] @@ -605,7 +641,7 @@ fn mindmap_fetch_tool() -> Value { if let Value::Object(map) = &mut properties { map.insert( "scope".into(), - json!({ "type": "string", "enum": ["tree", "subtree", "markdown_summary"], "default": "tree" }), + json!({ "type": "string", "enum": ["tree", "subtree", "markdown_summary", "full_envelope"], "default": "tree" }), ); map.insert("nodeId".into(), json!({ "type": "string" })); } @@ -645,6 +681,32 @@ fn mindmap_apply_ops_tool() -> Value { ) } +fn mindmap_create_from_outline_tool() -> Value { + write_tool( + "mnote.mindmap.create_from_outline", + "从 PDF、Office、Markdown 或页面摘要生成新的 MNote mindmap resource envelope", + ["resource.write", "mindmap.write"], + json!({ + "mindmapId": { "type": "string" }, + "rootUri": { "type": "string" }, + "sourceKind": { "type": "string" }, + "resourcePath": { "type": "string" }, + "title": { "type": "string" }, + "outline": { + "type": "array", + "items": { "type": "object" } + }, + "sourceRefs": { + "type": "array", + "items": { "type": "object" } + }, + "embedIntoPage": { "type": "boolean", "default": true }, + "aiAccessScope": { "type": "object" } + }), + ["mindmapId", "outline"], + ) +} + fn office_fetch_summary_tool() -> Value { let mut properties = resource_identity_properties("assetId"); if let Value::Object(map) = &mut properties { @@ -692,6 +754,1044 @@ fn office_propose_changes_tool() -> Value { }) } +fn onlyoffice_base_properties() -> Value { + let mut properties = base_identity_properties(); + if let Value::Object(map) = &mut properties { + map.insert("onlyofficeSessionId".into(), json!({ "type": "string" })); + map.insert("bridgeSessionId".into(), json!({ "type": "string" })); + map.insert( + "aiAccessScope".into(), + json!({ + "type": "object", + "required": ["allowedResourceIds"], + "properties": { + "permissionLevel": { "type": "string" }, + "allowedResourceIds": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + } + } + }), + ); + map.insert( + "timeoutMs".into(), + json!({ "type": "integer", "default": 25000 }), + ); + } + properties +} + +fn onlyoffice_session_current_tool() -> Value { + onlyoffice_tool( + "mnote.onlyoffice.session.current", + "返回显式授权的 ONLYOFFICE live bridge session。", + ["office.read"], + true, + onlyoffice_base_properties(), + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_capabilities_tool() -> Value { + onlyoffice_tool( + "mnote.onlyoffice.capabilities", + "返回当前 MNote ONLYOFFICE bridge 支持的白名单 action 和编辑器能力矩阵。", + ["office.read"], + true, + onlyoffice_base_properties(), + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_selection_get_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("options".into(), json!({ "type": "object" })); + } + onlyoffice_tool( + "mnote.onlyoffice.selection.get", + "读取当前 ONLYOFFICE 选区文本,支持文档、表格和演示。", + ["office.read"], + true, + properties, + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_document_insert_text_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("text".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.document.insert_text", + "向当前 ONLYOFFICE 文档/选区插入纯文本。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "text", + ], + ) +} + +fn onlyoffice_document_replace_selection_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("text".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.document.replace_selection", + "用纯文本替换当前 ONLYOFFICE 选区。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "text", + ], + ) +} + +fn onlyoffice_document_insert_html_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("html".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.document.insert_html", + "向当前 ONLYOFFICE 文档插入 HTML 片段。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "html", + ], + ) +} + +fn onlyoffice_document_export_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "format".into(), + json!({ "type": "string", "enum": ["markdown", "html"], "default": "markdown" }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.document.export", + "把当前 ONLYOFFICE 文档导出为 Markdown 或 HTML 文本,适合 agent 读取全文后再提出修改。", + ["office.read"], + true, + properties, + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_document_search_replace_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("search".into(), json!({ "type": "string" })); + map.insert("replace".into(), json!({ "type": "string", "default": "" })); + map.insert( + "matchCase".into(), + json!({ "type": "boolean", "default": false }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.document.search_replace", + "在当前 ONLYOFFICE Word 文档中执行文本查找替换。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "search", + ], + ) +} + +fn onlyoffice_document_insert_table_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("rows".into(), json!({ "type": "integer", "minimum": 1 })); + map.insert("cols".into(), json!({ "type": "integer", "minimum": 1 })); + map.insert("columns".into(), json!({ "type": "integer", "minimum": 1 })); + map.insert( + "data".into(), + json!({ + "type": "array", + "items": { + "type": "array", + "items": {} + } + }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.document.insert_table", + "向当前 ONLYOFFICE Word 文档末尾插入表格,可用二维 data 填充单元格。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + ], + ) +} + +fn onlyoffice_document_get_comments_tool() -> Value { + onlyoffice_tool( + "mnote.onlyoffice.document.get_comments", + "读取当前 ONLYOFFICE Word 文档中的评论列表、作者、正文和引用文本。", + ["office.read"], + true, + onlyoffice_base_properties(), + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_document_add_comment_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("text".into(), json!({ "type": "string" })); + map.insert( + "author".into(), + json!({ "type": "string", "default": "MNote AI" }), + ); + map.insert( + "target".into(), + json!({ "type": "string", "enum": ["selection", "document_start"], "default": "selection" }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.document.add_comment", + "给当前 ONLYOFFICE Word 文档选区或文档开头段落添加评论。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "text", + ], + ) +} + +fn onlyoffice_sheet_get_sheets_tool() -> Value { + onlyoffice_tool( + "mnote.onlyoffice.sheet.get_sheets", + "读取当前 ONLYOFFICE 表格工作簿的工作表列表。", + ["office.read"], + true, + onlyoffice_base_properties(), + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_sheet_add_sheet_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("name".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.add_sheet", + "在当前 ONLYOFFICE 表格工作簿中新增工作表,并切换为活动工作表。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "name", + ], + ) +} + +fn onlyoffice_sheet_rename_sheet_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("name".into(), json!({ "type": "string" })); + map.insert( + "sheetIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + map.insert("sheetName".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.rename_sheet", + "重命名当前或指定 ONLYOFFICE 表格工作表。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "name", + ], + ) +} + +fn onlyoffice_sheet_get_range_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("address".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.get_range", + "读取当前 ONLYOFFICE 表格的指定单元格或区域;address 为空时读取当前选区。", + ["office.read"], + true, + properties, + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_sheet_get_range_values_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("address".into(), json!({ "type": "string" })); + map.insert("range".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.get_range_values", + "按 A1 地址读取当前 ONLYOFFICE 表格 range 的二维值、显示值和公式。", + ["office.read"], + true, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "address", + ], + ) +} + +fn onlyoffice_sheet_get_values_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "startRow".into(), + json!({ "type": "integer", "default": 0 }), + ); + map.insert( + "startCol".into(), + json!({ "type": "integer", "default": 0 }), + ); + map.insert( + "rowCount".into(), + json!({ "type": "integer", "minimum": 1 }), + ); + map.insert( + "colCount".into(), + json!({ "type": "integer", "minimum": 1 }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.get_values", + "按零基行列坐标批量读取当前 ONLYOFFICE 表格单元格值、显示值和公式。", + ["office.read"], + true, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "rowCount", + "colCount", + ], + ) +} + +fn onlyoffice_sheet_set_value_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("address".into(), json!({ "type": "string" })); + map.insert("value".into(), json!({})); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.set_value", + "写入当前 ONLYOFFICE 表格的指定单元格或区域;address 为空时写入当前选区。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "value", + ], + ) +} + +fn onlyoffice_sheet_set_formula_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("address".into(), json!({ "type": "string" })); + map.insert("formula".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.set_formula", + "向当前 ONLYOFFICE 表格写入公式。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "formula", + ], + ) +} + +fn onlyoffice_sheet_batch_set_values_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "startRow".into(), + json!({ "type": "integer", "default": 0 }), + ); + map.insert( + "startCol".into(), + json!({ "type": "integer", "default": 0 }), + ); + map.insert( + "values".into(), + json!({ + "type": "array", + "items": { + "type": "array", + "items": {} + } + }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.batch_set_values", + "按零基行列坐标向当前 ONLYOFFICE 表格批量写入二维数组。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "values", + ], + ) +} + +fn onlyoffice_sheet_set_range_values_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("address".into(), json!({ "type": "string" })); + map.insert("range".into(), json!({ "type": "string" })); + map.insert( + "values".into(), + json!({ + "type": "array", + "items": { + "type": "array", + "items": {} + } + }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.set_range_values", + "按 A1 地址向当前 ONLYOFFICE 表格 range 写入二维数组。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "address", + "values", + ], + ) +} + +fn onlyoffice_sheet_format_range_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("address".into(), json!({ "type": "string" })); + map.insert("range".into(), json!({ "type": "string" })); + map.insert("bold".into(), json!({ "type": "boolean" })); + map.insert("italic".into(), json!({ "type": "boolean" })); + map.insert("underline".into(), json!({ "type": "boolean" })); + map.insert("fillColor".into(), json!({ "type": "string" })); + map.insert("fontColor".into(), json!({ "type": "string" })); + map.insert("fontSize".into(), json!({ "type": "number", "minimum": 0 })); + map.insert("fontName".into(), json!({ "type": "string" })); + map.insert( + "horizontalAlign".into(), + json!({ "type": "string", "enum": ["left", "center", "right", "justify"] }), + ); + map.insert( + "verticalAlign".into(), + json!({ "type": "string", "enum": ["top", "center", "bottom"] }), + ); + map.insert("numberFormat".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.format_range", + "按 A1 地址设置当前 ONLYOFFICE 表格 range 的字体、颜色、对齐和数字格式。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "address", + ], + ) +} + +fn onlyoffice_sheet_set_dimensions_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "columnIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + map.insert( + "columnWidth".into(), + json!({ "type": "number", "minimum": 0 }), + ); + map.insert( + "rowIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + map.insert( + "rowHeight".into(), + json!({ "type": "number", "minimum": 0 }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.set_dimensions", + "设置当前 ONLYOFFICE 表格的列宽或行高;行列索引为零基。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + ], + ) +} + +fn onlyoffice_sheet_sort_range_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("address".into(), json!({ "type": "string" })); + map.insert("range".into(), json!({ "type": "string" })); + map.insert( + "keyColumn".into(), + json!({ "type": "integer", "minimum": 1, "default": 1 }), + ); + map.insert( + "order".into(), + json!({ "type": "string", "enum": ["ascending", "descending", "asc", "desc"], "default": "ascending" }), + ); + map.insert( + "header".into(), + json!({ "type": "boolean", "default": true }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.sort_range", + "按 A1 range 对当前 ONLYOFFICE 表格区域排序;keyColumn 为 range 内从 1 开始的排序列。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "address", + ], + ) +} + +fn onlyoffice_sheet_add_chart_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("address".into(), json!({ "type": "string" })); + map.insert("range".into(), json!({ "type": "string" })); + map.insert( + "chartType".into(), + json!({ "type": "string", "default": "bar" }), + ); + map.insert( + "inColumns".into(), + json!({ "type": "boolean", "default": true }), + ); + map.insert("style".into(), json!({ "type": "integer", "default": 2 })); + map.insert( + "widthMm".into(), + json!({ "type": "number", "minimum": 0, "default": 120 }), + ); + map.insert( + "heightMm".into(), + json!({ "type": "number", "minimum": 0, "default": 80 }), + ); + map.insert( + "fromCol".into(), + json!({ "type": "integer", "minimum": 0, "default": 4 }), + ); + map.insert( + "fromRow".into(), + json!({ "type": "integer", "minimum": 0, "default": 1 }), + ); + map.insert( + "xOffsetMm".into(), + json!({ "type": "number", "minimum": 0, "default": 0 }), + ); + map.insert( + "yOffsetMm".into(), + json!({ "type": "number", "minimum": 0, "default": 0 }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.sheet.add_chart", + "基于当前 ONLYOFFICE 表格 A1 range 插入图表,默认生成柱状图。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "address", + ], + ) +} + +fn onlyoffice_presentation_get_slides_tool() -> Value { + onlyoffice_tool( + "mnote.onlyoffice.presentation.get_slides", + "读取当前 ONLYOFFICE 演示文稿的基础幻灯片信息。", + ["office.read"], + true, + onlyoffice_base_properties(), + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_presentation_get_slide_texts_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "slideIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.presentation.get_slide_texts", + "读取当前 ONLYOFFICE 演示文稿中每页文本框的纯文本。", + ["office.read"], + true, + properties, + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_presentation_get_shapes_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "slideIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.presentation.get_shapes", + "读取当前 ONLYOFFICE 演示文稿的 shape 列表、shapeId、shapeIndex 和文本。", + ["office.read"], + true, + properties, + &["sessionId", "runId", "toolCallId", "traceId", "actorId"], + ) +} + +fn onlyoffice_presentation_add_text_slide_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("title".into(), json!({ "type": "string" })); + map.insert("body".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.presentation.add_text_slide", + "向当前 ONLYOFFICE 演示文稿追加一个文本幻灯片。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + ], + ) +} + +fn onlyoffice_presentation_replace_text_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert("search".into(), json!({ "type": "string" })); + map.insert("replace".into(), json!({ "type": "string", "default": "" })); + map.insert( + "slideIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + map.insert( + "matchCase".into(), + json!({ "type": "boolean", "default": false }), + ); + map.insert( + "replaceAll".into(), + json!({ "type": "boolean", "default": true }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.presentation.replace_text", + "替换当前 ONLYOFFICE 演示文稿文本框中的纯文本;命中的文本框会按纯文本段落重建。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "search", + ], + ) +} + +fn onlyoffice_presentation_set_shape_text_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "slideIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + map.insert( + "shapeIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + map.insert("shapeId".into(), json!({ "type": "string" })); + map.insert("text".into(), json!({ "type": "string" })); + } + onlyoffice_tool( + "mnote.onlyoffice.presentation.set_shape_text", + "用 slideIndex + shapeIndex/shapeId 精确设置 PPT 文本 shape 的纯文本。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "slideIndex", + "text", + ], + ) +} + +fn onlyoffice_presentation_delete_slide_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "slideIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.presentation.delete_slide", + "删除当前 ONLYOFFICE 演示文稿中的指定幻灯片。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "slideIndex", + ], + ) +} + +fn onlyoffice_presentation_add_table_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "slideIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + map.insert("rows".into(), json!({ "type": "integer", "minimum": 1 })); + map.insert("cols".into(), json!({ "type": "integer", "minimum": 1 })); + map.insert("columns".into(), json!({ "type": "integer", "minimum": 1 })); + map.insert( + "data".into(), + json!({ + "type": "array", + "items": { + "type": "array", + "items": {} + } + }), + ); + map.insert("xMm".into(), json!({ "type": "number", "minimum": 0 })); + map.insert("yMm".into(), json!({ "type": "number", "minimum": 0 })); + map.insert( + "widthMm".into(), + json!({ "type": "number", "minimum": 0, "default": 190 }), + ); + map.insert( + "heightMm".into(), + json!({ "type": "number", "minimum": 0, "default": 90 }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.presentation.add_table", + "向当前 ONLYOFFICE PPT 指定幻灯片插入表格,可用二维 data 填充单元格。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + ], + ) +} + +fn onlyoffice_presentation_clear_slide_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "slideIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.presentation.clear_slide", + "清空当前 ONLYOFFICE PPT 指定幻灯片上的所有对象。调用前必须确认 slideIndex。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + "slideIndex", + ], + ) +} + +fn onlyoffice_presentation_add_shape_tool() -> Value { + let mut properties = onlyoffice_base_properties(); + if let Value::Object(map) = &mut properties { + map.insert( + "slideIndex".into(), + json!({ "type": "integer", "minimum": 0 }), + ); + map.insert( + "shapeType".into(), + json!({ "type": "string", "enum": ["rect", "roundRect", "ellipse", "triangle", "diamond", "cube", "cloud", "flowChartMagneticTape"], "default": "rect" }), + ); + map.insert("text".into(), json!({ "type": "string" })); + map.insert( + "fillColor".into(), + json!({ "type": "string", "default": "#4F81BD" }), + ); + map.insert("strokeColor".into(), json!({ "type": "string" })); + map.insert( + "strokeWidthMm".into(), + json!({ "type": "number", "minimum": 0, "default": 0 }), + ); + map.insert( + "xMm".into(), + json!({ "type": "number", "minimum": 0, "default": 20 }), + ); + map.insert( + "yMm".into(), + json!({ "type": "number", "minimum": 0, "default": 35 }), + ); + map.insert( + "widthMm".into(), + json!({ "type": "number", "minimum": 0, "default": 160 }), + ); + map.insert( + "heightMm".into(), + json!({ "type": "number", "minimum": 0, "default": 70 }), + ); + } + onlyoffice_tool( + "mnote.onlyoffice.presentation.add_shape", + "向当前 ONLYOFFICE PPT 指定幻灯片插入基础形状,可设置文字、填充色、位置和尺寸。", + ["office.write"], + false, + properties, + &[ + "sessionId", + "runId", + "toolCallId", + "traceId", + "actorId", + "idempotencyKey", + "dryRun", + ], + ) +} + +fn onlyoffice_tool( + name: &'static str, + description: &'static str, + capability_scope: impl IntoIterator, + readonly: bool, + properties: Value, + required: &[&'static str], +) -> Value { + let mut required_fields = required.to_vec(); + if name != "mnote.onlyoffice.capabilities" { + required_fields.push("aiAccessScope"); + } + let mut input_schema = json!({ + "type": "object", + "required": required_fields, + "properties": properties + }); + if name != "mnote.onlyoffice.capabilities" { + input_schema["anyOf"] = json!([ + { "required": ["onlyofficeSessionId"] }, + { "required": ["bridgeSessionId"] } + ]); + } + json!({ + "name": name, + "description": description, + "schemaVersion": TOOL_SCHEMA_VERSION, + "capabilityScope": capability_scope.into_iter().collect::>(), + "status": "available", + "annotations": tool_annotations(readonly, false, readonly, false), + "inputSchema": input_schema + }) +} + fn tool_annotations( readonly: bool, destructive: bool, diff --git a/rust/crates/mnote-web/src/hermes_tools/mod.rs b/rust/crates/mnote-web/src/hermes_tools/mod.rs index 43215a79..0be90a1e 100644 --- a/rust/crates/mnote-web/src/hermes_tools/mod.rs +++ b/rust/crates/mnote-web/src/hermes_tools/mod.rs @@ -3,6 +3,7 @@ pub mod block; pub mod context_tools; pub mod doc; pub mod manifest; +pub mod onlyoffice_live; pub mod page; pub mod resource; pub mod skill; diff --git a/rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rs b/rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rs new file mode 100644 index 00000000..507fec2a --- /dev/null +++ b/rust/crates/mnote-web/src/hermes_tools/onlyoffice_live.rs @@ -0,0 +1,1130 @@ +use crate::context::RequestContext; +use crate::error::WebError; +use crate::hermes_tools::{ensure_write_authorized, ToolCallInput}; +use crate::routes::onlyoffice_bridge::{self, BridgeResultWire, BridgeRunError}; +use axum::http::StatusCode; +use serde_json::{json, Value}; +use std::collections::HashSet; + +const DEFAULT_TIMEOUT_MS: u64 = 25_000; +const MAX_SHEET_BATCH_CELLS: u64 = 5_000; +const MAX_DOCUMENT_TABLE_CELLS: u64 = 1_000; +const MAX_PRESENTATION_TABLE_CELLS: u64 = 200; + +pub async fn session_current( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let session = current_session(context, input)?; + Ok(json!({ + "schema": "mnote.onlyoffice.session.v1", + "session": session + })) +} + +pub async fn capabilities( + _context: &RequestContext, + _input: &ToolCallInput, +) -> Result { + Ok(onlyoffice_bridge::capability_payload()) +} + +pub async fn selection_get( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + run_read_action( + context, + input, + "selection.get", + json!({ + "options": input.arg_value("options").unwrap_or_else(|| json!({})) + }), + ) + .await +} + +pub async fn sheet_get_range( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + run_read_action( + context, + input, + "sheet.get_range", + json!({ + "address": input.arg_string("address").unwrap_or_default() + }), + ) + .await +} + +pub async fn document_export( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let format = input + .arg_string("format") + .unwrap_or_else(|| "markdown".into()) + .to_ascii_lowercase(); + if !matches!(format.as_str(), "markdown" | "html") { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_export_format_invalid", + "format 仅支持 markdown 或 html", + ) + .with_context(context)); + } + run_read_action( + context, + input, + "document.export", + json!({ "format": format }), + ) + .await +} + +pub async fn document_search_replace( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let search = required_string(context, input, "search")?; + if search.is_empty() { + return Err( + WebError::bad_request_code("mnote_onlyoffice_search_empty", "search 不能为空") + .with_context(context), + ); + } + run_write_action( + context, + input, + "document.search_replace", + json!({ + "search": search, + "replace": input.arg_string("replace").unwrap_or_default(), + "matchCase": arg_bool(input, "matchCase").unwrap_or(false) + }), + ) + .await +} + +pub async fn document_insert_table( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let data = input.arg_value("data").unwrap_or_else(|| json!([])); + let rows_from_data = data.as_array().map(|rows| rows.len() as u64).unwrap_or(0); + let cols_from_data = data + .as_array() + .and_then(|rows| { + rows.iter() + .map(|row| row.as_array().map(|cells| cells.len() as u64).unwrap_or(1)) + .max() + }) + .unwrap_or(0); + let rows = arg_u64(input, "rows").unwrap_or(rows_from_data).max(1); + let cols = arg_u64(input, "cols") + .or_else(|| arg_u64(input, "columns")) + .unwrap_or(cols_from_data) + .max(1); + if rows.saturating_mul(cols) > MAX_DOCUMENT_TABLE_CELLS { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_table_too_large", + format!("单次 Word 表格最多支持 {MAX_DOCUMENT_TABLE_CELLS} 个单元格"), + ) + .with_context(context)); + } + run_write_action( + context, + input, + "document.insert_table", + json!({ + "rows": rows, + "cols": cols, + "data": data + }), + ) + .await +} + +pub async fn document_get_comments( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + run_read_action(context, input, "document.get_comments", json!({})).await +} + +pub async fn document_add_comment( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let text = required_string(context, input, "text")?; + run_write_action( + context, + input, + "document.add_comment", + json!({ + "text": text, + "author": input.arg_string("author").unwrap_or_else(|| "MNote AI".into()), + "target": input.arg_string("target").unwrap_or_else(|| "selection".into()) + }), + ) + .await +} + +pub async fn sheet_get_sheets( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + run_read_action(context, input, "sheet.get_sheets", json!({})).await +} + +pub async fn sheet_add_sheet( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let name = required_string(context, input, "name")?; + run_write_action(context, input, "sheet.add_sheet", json!({ "name": name })).await +} + +pub async fn sheet_rename_sheet( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let name = required_string(context, input, "name")?; + run_write_action( + context, + input, + "sheet.rename_sheet", + json!({ + "name": name, + "sheetIndex": arg_u64(input, "sheetIndex"), + "sheetName": input.arg_string("sheetName").unwrap_or_default() + }), + ) + .await +} + +pub async fn sheet_get_range_values( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let address = input + .arg_string("address") + .or_else(|| input.arg_string("range")) + .ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address") + .with_context(context) + })?; + let range = parse_a1_range(context, &address)?; + ensure_sheet_cell_limit(context, range.row_count, range.col_count)?; + run_read_action( + context, + input, + "sheet.get_range_values", + json!({ "address": address }), + ) + .await +} + +pub async fn sheet_get_values( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let start_row = arg_u64(input, "startRow").unwrap_or(0); + let start_col = arg_u64(input, "startCol").unwrap_or(0); + let row_count = required_u64(context, input, "rowCount")?; + let col_count = required_u64(context, input, "colCount")?; + ensure_sheet_cell_limit(context, row_count, col_count)?; + run_read_action( + context, + input, + "sheet.get_values", + json!({ + "startRow": start_row, + "startCol": start_col, + "rowCount": row_count, + "colCount": col_count + }), + ) + .await +} + +pub async fn presentation_get_slides( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + run_read_action(context, input, "presentation.get_slides", json!({})).await +} + +pub async fn presentation_get_slide_texts( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let mut payload = json!({}); + if let Some(slide_index) = arg_u64(input, "slideIndex") { + payload["slideIndex"] = json!(slide_index); + } + run_read_action(context, input, "presentation.get_slide_texts", payload).await +} + +pub async fn presentation_get_shapes( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let mut payload = json!({}); + if let Some(slide_index) = arg_u64(input, "slideIndex") { + payload["slideIndex"] = json!(slide_index); + } + run_read_action(context, input, "presentation.get_shapes", payload).await +} + +pub async fn document_insert_text( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let text = required_string(context, input, "text")?; + run_write_action( + context, + input, + "document.insert_text", + json!({ "text": text }), + ) + .await +} + +pub async fn document_replace_selection( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let text = required_string(context, input, "text")?; + run_write_action( + context, + input, + "document.replace_selection", + json!({ "text": text }), + ) + .await +} + +pub async fn document_insert_html( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let html = required_string(context, input, "html")?; + run_write_action( + context, + input, + "document.insert_html", + json!({ "html": html }), + ) + .await +} + +pub async fn sheet_set_value( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let value = input.arg_value("value").ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_value_required", "缺少 value") + .with_context(context) + })?; + run_write_action( + context, + input, + "sheet.set_value", + json!({ + "address": input.arg_string("address").unwrap_or_default(), + "value": value + }), + ) + .await +} + +pub async fn sheet_set_formula( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let formula = required_string(context, input, "formula")?; + run_write_action( + context, + input, + "sheet.set_formula", + json!({ + "address": input.arg_string("address").unwrap_or_default(), + "formula": formula + }), + ) + .await +} + +pub async fn sheet_batch_set_values( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let values = input.arg_value("values").ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_values_required", "缺少 values") + .with_context(context) + })?; + let rows = values.as_array().ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_values_invalid", "values 必须是二维数组") + .with_context(context) + })?; + let row_count = rows.len() as u64; + let col_count = rows + .iter() + .map(|row| row.as_array().map(|cells| cells.len()).unwrap_or(1)) + .max() + .unwrap_or(0) as u64; + if row_count == 0 || col_count == 0 { + return Err( + WebError::bad_request_code("mnote_onlyoffice_values_empty", "values 不能为空") + .with_context(context), + ); + } + ensure_sheet_cell_limit(context, row_count, col_count)?; + run_write_action( + context, + input, + "sheet.batch_set_values", + json!({ + "startRow": arg_u64(input, "startRow").unwrap_or(0), + "startCol": arg_u64(input, "startCol").unwrap_or(0), + "values": values + }), + ) + .await +} + +pub async fn sheet_set_range_values( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let address = input + .arg_string("address") + .or_else(|| input.arg_string("range")) + .ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address") + .with_context(context) + })?; + let range = parse_a1_range(context, &address)?; + ensure_sheet_cell_limit(context, range.row_count, range.col_count)?; + let values = input.arg_value("values").ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_values_required", "缺少 values") + .with_context(context) + })?; + let rows = values.as_array().ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_values_invalid", "values 必须是二维数组") + .with_context(context) + })?; + let row_count = rows.len() as u64; + let col_count = rows + .iter() + .map(|row| row.as_array().map(|cells| cells.len()).unwrap_or(1)) + .max() + .unwrap_or(0) as u64; + if row_count == 0 || col_count == 0 { + return Err( + WebError::bad_request_code("mnote_onlyoffice_values_empty", "values 不能为空") + .with_context(context), + ); + } + ensure_sheet_cell_limit(context, row_count, col_count)?; + run_write_action( + context, + input, + "sheet.set_range_values", + json!({ + "address": address, + "values": values + }), + ) + .await +} + +pub async fn sheet_format_range( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let address = input + .arg_string("address") + .or_else(|| input.arg_string("range")) + .ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address") + .with_context(context) + })?; + let range = parse_a1_range(context, &address)?; + ensure_sheet_cell_limit(context, range.row_count, range.col_count)?; + run_write_action( + context, + input, + "sheet.format_range", + json!({ + "address": address, + "bold": arg_bool(input, "bold"), + "italic": arg_bool(input, "italic"), + "underline": arg_bool(input, "underline"), + "fillColor": input.arg_string("fillColor").or_else(|| input.arg_string("fill_color")).unwrap_or_default(), + "fontColor": input.arg_string("fontColor").or_else(|| input.arg_string("font_color")).unwrap_or_default(), + "fontSize": arg_f64(input, "fontSize"), + "fontName": input.arg_string("fontName").or_else(|| input.arg_string("font_name")).unwrap_or_default(), + "horizontalAlign": input.arg_string("horizontalAlign").or_else(|| input.arg_string("horizontal_align")).unwrap_or_default(), + "verticalAlign": input.arg_string("verticalAlign").or_else(|| input.arg_string("vertical_align")).unwrap_or_default(), + "numberFormat": input.arg_string("numberFormat").or_else(|| input.arg_string("number_format")).unwrap_or_default() + }), + ) + .await +} + +pub async fn sheet_set_dimensions( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let column_index = arg_u64(input, "columnIndex"); + let column_width = arg_f64(input, "columnWidth"); + let row_index = arg_u64(input, "rowIndex"); + let row_height = arg_f64(input, "rowHeight"); + let has_column = column_index.is_some() && column_width.is_some(); + let has_row = row_index.is_some() && row_height.is_some(); + if !has_column && !has_row { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_dimensions_required", + "缺少 columnIndex/columnWidth 或 rowIndex/rowHeight", + ) + .with_context(context)); + } + run_write_action( + context, + input, + "sheet.set_dimensions", + json!({ + "columnIndex": column_index, + "columnWidth": column_width, + "rowIndex": row_index, + "rowHeight": row_height + }), + ) + .await +} + +pub async fn sheet_sort_range( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let address = input + .arg_string("address") + .or_else(|| input.arg_string("range")) + .ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address") + .with_context(context) + })?; + let range = parse_a1_range(context, &address)?; + ensure_sheet_cell_limit(context, range.row_count, range.col_count)?; + let key_column = arg_u64(input, "keyColumn").unwrap_or(1).max(1); + if key_column > range.col_count { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_sort_key_out_of_range", + "keyColumn 必须落在排序 range 内,且从 1 开始", + ) + .with_context(context)); + } + let order = input + .arg_string("order") + .unwrap_or_else(|| "ascending".into()) + .to_ascii_lowercase(); + if !matches!( + order.as_str(), + "ascending" | "descending" | "asc" | "desc" | "xlascending" | "xldescending" + ) { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_sort_order_invalid", + "order 仅支持 ascending/descending", + ) + .with_context(context)); + } + run_write_action( + context, + input, + "sheet.sort_range", + json!({ + "address": address, + "keyColumn": key_column, + "order": order, + "header": arg_bool(input, "header").unwrap_or(true) + }), + ) + .await +} + +pub async fn sheet_add_chart( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let address = input + .arg_string("address") + .or_else(|| input.arg_string("range")) + .ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_range_required", "缺少 address") + .with_context(context) + })?; + let range = parse_a1_range(context, &address)?; + ensure_sheet_cell_limit(context, range.row_count, range.col_count)?; + let chart_type = input + .arg_string("chartType") + .or_else(|| input.arg_string("chart_type")) + .unwrap_or_else(|| "bar".into()); + run_write_action( + context, + input, + "sheet.add_chart", + json!({ + "address": address, + "chartType": chart_type, + "inColumns": arg_bool(input, "inColumns").unwrap_or(true), + "style": arg_u64(input, "style").unwrap_or(2), + "widthMm": arg_f64(input, "widthMm").unwrap_or(120.0), + "heightMm": arg_f64(input, "heightMm").unwrap_or(80.0), + "fromCol": arg_u64(input, "fromCol").unwrap_or(4), + "xOffsetMm": arg_f64(input, "xOffsetMm").unwrap_or(0.0), + "fromRow": arg_u64(input, "fromRow").unwrap_or(1), + "yOffsetMm": arg_f64(input, "yOffsetMm").unwrap_or(0.0) + }), + ) + .await +} + +pub async fn presentation_add_text_slide( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + run_write_action( + context, + input, + "presentation.add_text_slide", + json!({ + "title": input.arg_string("title").unwrap_or_default(), + "body": input.arg_string("body").unwrap_or_default() + }), + ) + .await +} + +pub async fn presentation_add_table( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let data = input.arg_value("data").unwrap_or_else(|| json!([])); + let rows_from_data = data.as_array().map(|rows| rows.len() as u64).unwrap_or(0); + let cols_from_data = data + .as_array() + .and_then(|rows| { + rows.iter() + .map(|row| row.as_array().map(|cells| cells.len() as u64).unwrap_or(1)) + .max() + }) + .unwrap_or(0); + let rows = arg_u64(input, "rows").unwrap_or(rows_from_data).max(1); + let cols = arg_u64(input, "cols") + .or_else(|| arg_u64(input, "columns")) + .unwrap_or(cols_from_data) + .max(1); + if rows.saturating_mul(cols) > MAX_PRESENTATION_TABLE_CELLS { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_presentation_table_too_large", + format!("单次 PPT 表格最多支持 {MAX_PRESENTATION_TABLE_CELLS} 个单元格"), + ) + .with_context(context)); + } + let mut payload = json!({ + "rows": rows, + "cols": cols, + "data": data, + "widthMm": arg_f64(input, "widthMm").unwrap_or(190.0), + "heightMm": arg_f64(input, "heightMm").unwrap_or(90.0) + }); + if let Some(slide_index) = arg_u64(input, "slideIndex") { + payload["slideIndex"] = json!(slide_index); + } + if let Some(x_mm) = arg_f64(input, "xMm") { + payload["xMm"] = json!(x_mm); + } + if let Some(y_mm) = arg_f64(input, "yMm") { + payload["yMm"] = json!(y_mm); + } + run_write_action(context, input, "presentation.add_table", payload).await +} + +pub async fn presentation_clear_slide( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let slide_index = required_u64(context, input, "slideIndex")?; + run_write_action( + context, + input, + "presentation.clear_slide", + json!({ "slideIndex": slide_index }), + ) + .await +} + +pub async fn presentation_add_shape( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let shape_type = input + .arg_string("shapeType") + .or_else(|| input.arg_string("shape_type")) + .unwrap_or_else(|| "rect".into()); + let allowed_shapes = [ + "rect", + "roundRect", + "ellipse", + "triangle", + "diamond", + "cube", + "cloud", + "flowChartMagneticTape", + ]; + if !allowed_shapes.contains(&shape_type.as_str()) { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_shape_type_invalid", + "shapeType 不在允许列表中", + ) + .with_context(context)); + } + let mut payload = json!({ + "shapeType": shape_type, + "text": input.arg_string("text").unwrap_or_default(), + "fillColor": input.arg_string("fillColor").or_else(|| input.arg_string("fill_color")).unwrap_or_else(|| "#4F81BD".into()), + "strokeColor": input.arg_string("strokeColor").or_else(|| input.arg_string("stroke_color")).unwrap_or_default(), + "strokeWidthMm": arg_f64(input, "strokeWidthMm").unwrap_or(0.0), + "xMm": arg_f64(input, "xMm").unwrap_or(20.0), + "yMm": arg_f64(input, "yMm").unwrap_or(35.0), + "widthMm": arg_f64(input, "widthMm").unwrap_or(160.0), + "heightMm": arg_f64(input, "heightMm").unwrap_or(70.0) + }); + if let Some(slide_index) = arg_u64(input, "slideIndex") { + payload["slideIndex"] = json!(slide_index); + } + run_write_action(context, input, "presentation.add_shape", payload).await +} + +pub async fn presentation_replace_text( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let search = required_string(context, input, "search")?; + if search.is_empty() { + return Err( + WebError::bad_request_code("mnote_onlyoffice_search_empty", "search 不能为空") + .with_context(context), + ); + } + let mut payload = json!({ + "search": search, + "replace": input.arg_string("replace").unwrap_or_default(), + "matchCase": arg_bool(input, "matchCase").unwrap_or(false), + "replaceAll": arg_bool(input, "replaceAll").unwrap_or(true) + }); + if let Some(slide_index) = arg_u64(input, "slideIndex") { + payload["slideIndex"] = json!(slide_index); + } + run_write_action(context, input, "presentation.replace_text", payload).await +} + +pub async fn presentation_set_shape_text( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let slide_index = required_u64(context, input, "slideIndex")?; + let text = required_string(context, input, "text")?; + let shape_index = arg_u64(input, "shapeIndex"); + let shape_id = input.arg_string("shapeId"); + if shape_index.is_none() && shape_id.as_deref().unwrap_or_default().trim().is_empty() { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_shape_locator_required", + "缺少 shapeIndex 或 shapeId", + ) + .with_context(context)); + } + run_write_action( + context, + input, + "presentation.set_shape_text", + json!({ + "slideIndex": slide_index, + "shapeIndex": shape_index, + "shapeId": shape_id.unwrap_or_default(), + "text": text + }), + ) + .await +} + +pub async fn presentation_delete_slide( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + let slide_index = required_u64(context, input, "slideIndex")?; + run_write_action( + context, + input, + "presentation.delete_slide", + json!({ "slideIndex": slide_index }), + ) + .await +} + +async fn run_read_action( + context: &RequestContext, + input: &ToolCallInput, + action: &'static str, + payload: Value, +) -> Result { + let session_id = resolve_explicit_session_id(context, input)?; + ensure_onlyoffice_resource_scope_allowed(context, input, &session_id)?; + let result = run_bridge(context, input, &session_id, action, payload).await?; + Ok(action_result(action, &session_id, result)) +} + +async fn run_write_action( + context: &RequestContext, + input: &ToolCallInput, + action: &'static str, + payload: Value, +) -> Result { + ensure_write_authorized(context, input)?; + let session_id = resolve_explicit_session_id(context, input)?; + ensure_onlyoffice_resource_scope_allowed(context, input, &session_id)?; + if input.dry_run.unwrap_or(false) { + return Ok(json!({ + "schema": "mnote.onlyoffice.action_plan.v1", + "sessionId": session_id, + "action": action, + "payload": payload, + "dryRun": true, + "wouldWrite": true + })); + } + let result = run_bridge(context, input, &session_id, action, payload).await?; + Ok(action_result(action, &session_id, result)) +} + +async fn run_bridge( + context: &RequestContext, + input: &ToolCallInput, + session_id: &str, + action: &'static str, + payload: Value, +) -> Result { + let timeout_ms = input + .arg_value("timeoutMs") + .or_else(|| input.arg_value("timeout_ms")) + .and_then(|value| value.as_u64()) + .unwrap_or(DEFAULT_TIMEOUT_MS); + let result = onlyoffice_bridge::run_bridge_command(session_id, action, payload, timeout_ms) + .await + .map_err(|error| bridge_run_error(context, error))?; + if !result.ok { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_bridge_failed", + result + .error + .clone() + .unwrap_or_else(|| "ONLYOFFICE bridge 命令执行失败".into()), + ) + .with_context(context)); + } + Ok(result) +} + +fn action_result(action: &str, session_id: &str, result: BridgeResultWire) -> Value { + json!({ + "schema": "mnote.onlyoffice.action_result.v1", + "sessionId": session_id, + "action": action, + "commandId": result.id, + "result": result.result + }) +} + +fn resolve_explicit_session_id( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + input + .arg_string("onlyofficeSessionId") + .or_else(|| input.arg_string("bridgeSessionId")) + .ok_or_else(|| { + WebError::bad_request_code( + "mnote_onlyoffice_session_explicit_required", + "ONLYOFFICE 工具必须显式携带 onlyofficeSessionId", + ) + .with_context(context) + }) +} + +fn ensure_onlyoffice_resource_scope_allowed( + context: &RequestContext, + input: &ToolCallInput, + session_id: &str, +) -> Result<(), WebError> { + let Some(scope) = input.arg_value("aiAccessScope") else { + return Err(WebError::new( + StatusCode::FORBIDDEN, + "mnote_onlyoffice_resource_scope_required", + "ONLYOFFICE 工具必须携带 aiAccessScope.allowedResourceIds", + ) + .with_context(context)); + }; + let allowed = scope + .get("allowedResourceIds") + .or_else(|| scope.get("allowed_resource_ids")) + .and_then(Value::as_array) + .map(|values| { + values + .iter() + .filter_map(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) + .collect::>() + }) + .unwrap_or_default(); + if allowed.is_empty() { + return Err(WebError::new( + StatusCode::FORBIDDEN, + "mnote_onlyoffice_resource_scope_required", + "ONLYOFFICE 工具必须携带非空 aiAccessScope.allowedResourceIds", + ) + .with_context(context)); + } + let Some(session) = onlyoffice_bridge::session_info(session_id) else { + return Err(WebError::new( + StatusCode::FORBIDDEN, + "mnote_onlyoffice_resource_scope_forbidden", + "ONLYOFFICE session 未注册,无法校验资源授权", + ) + .with_context(context)); + }; + let mut candidates = HashSet::new(); + candidates.insert(session.session_id); + let document_id = session.document_id; + let asset_id = session.asset_id; + if let Some(document_id) = document_id.as_deref() { + candidates.insert(document_id.to_string()); + } + if let Some(asset_id) = asset_id.as_deref() { + candidates.insert(asset_id.to_string()); + } + if let (Some(document_id), Some(asset_id)) = (document_id.as_deref(), asset_id.as_deref()) { + candidates.insert(format!("resource:office:{document_id}:{asset_id}")); + candidates.insert(format!("resource:onlyoffice:{document_id}:{asset_id}")); + } + if candidates + .iter() + .any(|candidate| allowed.contains(candidate)) + { + return Ok(()); + } + Err(WebError::new( + StatusCode::FORBIDDEN, + "mnote_onlyoffice_resource_scope_forbidden", + "ONLYOFFICE session 不在当前 AI resource 授权范围内", + ) + .with_context(context)) +} + +fn current_session(context: &RequestContext, input: &ToolCallInput) -> Result { + let session_id = resolve_explicit_session_id(context, input)?; + ensure_onlyoffice_resource_scope_allowed(context, input, &session_id)?; + let session = onlyoffice_bridge::session_info(&session_id).ok_or_else(|| { + WebError::new( + StatusCode::NOT_FOUND, + "mnote_onlyoffice_session_not_found", + "指定的 ONLYOFFICE bridge session 不存在,请先打开 ONLYOFFICE 文档", + ) + .with_context(context) + })?; + Ok(json!({ + "sessionId": session.session_id, + "editorType": session.editor_type.unwrap_or_default(), + "documentId": session.document_id.unwrap_or_default(), + "assetId": session.asset_id.unwrap_or_default(), + "fileType": session.file_type.unwrap_or_default(), + "lastSeenMillis": session.last_seen_millis, + "pendingCommands": session.pending_commands, + "pendingResults": session.pending_results + })) +} + +fn required_string( + context: &RequestContext, + input: &ToolCallInput, + key: &'static str, +) -> Result { + input.arg_string(key).ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_argument_required", format!("缺少 {key}")) + .with_context(context) + }) +} + +fn required_u64( + context: &RequestContext, + input: &ToolCallInput, + key: &'static str, +) -> Result { + arg_u64(input, key).ok_or_else(|| { + WebError::bad_request_code("mnote_onlyoffice_argument_required", format!("缺少 {key}")) + .with_context(context) + }) +} + +fn arg_u64(input: &ToolCallInput, key: &'static str) -> Option { + input + .arg_value(key) + .or_else(|| match key { + "startRow" => input.arg_value("start_row"), + "startCol" => input.arg_value("start_col"), + "rowCount" => input.arg_value("row_count"), + "colCount" => input.arg_value("col_count"), + "slideIndex" => input.arg_value("slide_index"), + "sheetIndex" => input.arg_value("sheet_index"), + "shapeIndex" => input.arg_value("shape_index"), + "columnIndex" => input.arg_value("column_index"), + "rowIndex" => input.arg_value("row_index"), + "keyColumn" => input.arg_value("key_column"), + "fromCol" => input.arg_value("from_col"), + "fromRow" => input.arg_value("from_row"), + "cols" => input.arg_value("columns"), + _ => None, + }) + .and_then(|value| value.as_u64()) +} + +fn arg_f64(input: &ToolCallInput, key: &'static str) -> Option { + input + .arg_value(key) + .or_else(|| match key { + "columnWidth" => input.arg_value("column_width"), + "rowHeight" => input.arg_value("row_height"), + "fontSize" => input.arg_value("font_size"), + "widthMm" => input.arg_value("width_mm"), + "heightMm" => input.arg_value("height_mm"), + "xOffsetMm" => input.arg_value("x_offset_mm"), + "yOffsetMm" => input.arg_value("y_offset_mm"), + "xMm" => input.arg_value("x_mm"), + "yMm" => input.arg_value("y_mm"), + "strokeWidthMm" => input.arg_value("stroke_width_mm"), + _ => None, + }) + .and_then(|value| value.as_f64()) + .filter(|value| value.is_finite() && *value >= 0.0) +} + +fn arg_bool(input: &ToolCallInput, key: &'static str) -> Option { + input + .arg_value(key) + .or_else(|| match key { + "matchCase" => input.arg_value("match_case"), + "replaceAll" => input.arg_value("replace_all"), + "inColumns" => input.arg_value("in_columns"), + _ => None, + }) + .and_then(|value| value.as_bool()) +} + +fn ensure_sheet_cell_limit( + context: &RequestContext, + row_count: u64, + col_count: u64, +) -> Result<(), WebError> { + if row_count == 0 || col_count == 0 { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_sheet_range_empty", + "rowCount 和 colCount 必须大于 0", + ) + .with_context(context)); + } + if row_count.saturating_mul(col_count) > MAX_SHEET_BATCH_CELLS { + return Err(WebError::bad_request_code( + "mnote_onlyoffice_sheet_range_too_large", + format!("单次表格批量操作最多支持 {MAX_SHEET_BATCH_CELLS} 个单元格"), + ) + .with_context(context)); + } + Ok(()) +} + +#[derive(Debug)] +struct A1Range { + row_count: u64, + col_count: u64, +} + +fn parse_a1_range(context: &RequestContext, address: &str) -> Result { + let value = address.trim(); + let mut parts = value.split(':'); + let start = parts.next().unwrap_or_default(); + let end = parts.next().unwrap_or(start); + if parts.next().is_some() { + return Err(invalid_a1_range(context)); + } + let (start_row, start_col) = parse_a1_cell(start).ok_or_else(|| invalid_a1_range(context))?; + let (end_row, end_col) = parse_a1_cell(end).ok_or_else(|| invalid_a1_range(context))?; + Ok(A1Range { + row_count: start_row.abs_diff(end_row) + 1, + col_count: start_col.abs_diff(end_col) + 1, + }) +} + +fn parse_a1_cell(value: &str) -> Option<(u64, u64)> { + let value = value.trim(); + let split_at = value + .char_indices() + .find_map(|(index, ch)| ch.is_ascii_digit().then_some(index))?; + let (letters, digits) = value.split_at(split_at); + if letters.is_empty() + || digits.is_empty() + || !letters.chars().all(|ch| ch.is_ascii_alphabetic()) + || !digits.chars().all(|ch| ch.is_ascii_digit()) + { + return None; + } + let mut col = 0_u64; + for ch in letters.chars() { + col = col + .saturating_mul(26) + .saturating_add((ch.to_ascii_uppercase() as u8 - b'A' + 1) as u64); + } + let row = digits.parse::().ok()?; + if row == 0 || col == 0 { + return None; + } + Some((row - 1, col - 1)) +} + +fn invalid_a1_range(context: &RequestContext) -> WebError { + WebError::bad_request_code( + "mnote_onlyoffice_range_invalid", + "address 必须是 A1 或 A1:B2", + ) + .with_context(context) +} + +fn bridge_run_error(context: &RequestContext, error: BridgeRunError) -> WebError { + match error { + BridgeRunError::BadRequest(message) => { + WebError::bad_request_code("mnote_onlyoffice_bridge_bad_request", message) + } + BridgeRunError::Timeout { + session_id, + command_id, + } => WebError::new( + StatusCode::GATEWAY_TIMEOUT, + "mnote_onlyoffice_bridge_timeout", + format!("ONLYOFFICE bridge 命令超时,sessionId={session_id}, commandId={command_id}"), + ), + } + .with_context(context) +} diff --git a/rust/crates/mnote-web/src/hermes_tools/resource.rs b/rust/crates/mnote-web/src/hermes_tools/resource.rs index fd9cc215..d97a2937 100644 --- a/rust/crates/mnote-web/src/hermes_tools/resource.rs +++ b/rust/crates/mnote-web/src/hermes_tools/resource.rs @@ -23,11 +23,17 @@ pub async fn mindmap_fetch( })?; let data = serde_json::from_str::(&content).unwrap_or_else(|_| json!({ "raw": content })); - let nodes = collect_mindmap_nodes(&data); let scope = input .arg_string("scope") .unwrap_or_else(|| "tree".into()) .to_ascii_lowercase(); + let root = mindmap_root_value(&data).clone(); + let nodes = collect_mindmap_nodes(&root); + let envelope = if scope == "full_envelope" { + data + } else { + Value::Null + }; Ok(json!({ "objectIdentity": target.object_identity, "resourceKind": "mindmap", @@ -35,6 +41,8 @@ pub async fn mindmap_fetch( "mindmapId": target.resource_id, "resourcePath": target.resource_path, "scope": scope, + "root": root, + "envelope": envelope, "nodes": nodes, "edges": [], "markdownSummary": mindmap_markdown_summary(&nodes), @@ -67,14 +75,150 @@ pub async fn mindmap_apply_ops( "diff": [{"op": "mindmap.apply_ops", "ops": ops}] })); } - Err(WebError::bad_request_code( - "mnote_resource_native_patch_required", - format!( - "本地 mindmap resource 写入请使用 agent 原生 patch 编辑授权文件;已校验可写资源路径 {}", - path.display() - ), - ) - .with_context(context)) + ensure_mindmap_revision_precondition(context, input, &path)?; + let content = fs::read_to_string(&path).map_err(|error| { + WebError::bad_request_code( + "mnote_resource_read_failed", + format!("无法读取 mindmap resource: {error}"), + ) + .with_context(context) + })?; + let mut envelope = serde_json::from_str::(&content).map_err(|error| { + WebError::bad_request_code( + "mnote_resource_json_invalid", + format!("mindmap resource 不是有效 JSON: {error}"), + ) + .with_context(context) + })?; + apply_mindmap_ops(context, &mut envelope, &ops)?; + let serialized = serde_json::to_string_pretty(&envelope).map_err(|error| { + WebError::bad_request_code( + "mnote_resource_serialize_failed", + format!("无法序列化 mindmap JSON: {error}"), + ) + .with_context(context) + })?; + fs::write(&path, serialized).map_err(|error| { + WebError::bad_request_code( + "mnote_resource_write_failed", + format!("无法写入 mindmap resource: {error}"), + ) + .with_context(context) + })?; + let root = mindmap_root_value(&envelope).clone(); + let nodes = collect_mindmap_nodes(&root); + Ok(json!({ + "dryRun": false, + "commandName": "mnote.mindmap.apply_ops", + "objectIdentity": target.object_identity, + "resourceKind": "mindmap", + "documentId": target.document_id, + "mindmapId": target.resource_id, + "resourcePath": target.resource_path, + "root": root, + "nodes": nodes, + "edges": [], + "markdownSummary": mindmap_markdown_summary(&nodes), + "revision": file_revision(&path), + "changedFiles": [target.relative_path()], + "source": "local_folder" + })) +} + +pub async fn mindmap_create_from_outline( + context: &RequestContext, + input: &ToolCallInput, +) -> Result { + ensure_resource_write_contract(context, input)?; + let target = ResourceToolTarget::from_input(context, input, "mindmap", "mindmapId")?; + ensure_resource_scope_allowed(context, input, &target)?; + let path = target.resolve_create_path(context, input)?; + let title = input + .arg_string("title") + .unwrap_or_else(|| "KMIND".to_string()); + let outline = input.arg_value("outline").ok_or_else(|| { + WebError::bad_request_code("mnote_mindmap_outline_required", "创建思维导图缺少 outline") + .with_context(context) + })?; + let source_refs = input.arg_value("sourceRefs").unwrap_or_else(|| json!([])); + let envelope = mindmap_envelope_from_outline(&title, &outline, source_refs, context)?; + let root = mindmap_root_value(&envelope).clone(); + let nodes = collect_mindmap_nodes(&root); + let resource_relative_path = target.relative_path(); + let mut changed_files = if input.dry_run.unwrap_or(false) { + Vec::new() + } else { + vec![resource_relative_path.clone()] + }; + let mut embed_result = Value::Null; + + if !input.dry_run.unwrap_or(false) { + let parent = path.parent().ok_or_else(|| { + WebError::bad_request_code("mnote_resource_bad_path", "无法解析 mindmap 父目录") + .with_context(context) + })?; + fs::create_dir_all(parent).map_err(|error| { + WebError::bad_request_code( + "mnote_resource_create_dir_failed", + format!("无法创建 mindmap 目录: {error}"), + ) + .with_context(context) + })?; + let content = serde_json::to_string_pretty(&envelope).map_err(|error| { + WebError::bad_request_code( + "mnote_resource_serialize_failed", + format!("无法序列化 mindmap JSON: {error}"), + ) + .with_context(context) + })?; + fs::write(&path, content).map_err(|error| { + WebError::bad_request_code( + "mnote_resource_write_failed", + format!("无法写入 mindmap resource: {error}"), + ) + .with_context(context) + })?; + if input + .arg_value("embedIntoPage") + .and_then(|value| value.as_bool()) + .unwrap_or(true) + { + embed_result = embed_mindmap_into_local_markdown_page( + context, + input, + &target.document_id, + &resource_relative_path, + &title, + )?; + if let Some(changed_file) = embed_result + .get("changedFile") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + { + changed_files.push(changed_file.to_string()); + } + } + } + + Ok(json!({ + "dryRun": input.dry_run.unwrap_or(false), + "commandName": "mnote.mindmap.create_from_outline", + "objectIdentity": target.object_identity, + "resourceKind": "mindmap", + "documentId": target.document_id, + "mindmapId": target.resource_id, + "resourcePath": target.resource_path, + "envelope": envelope, + "root": root, + "nodes": nodes, + "edges": [], + "markdownSummary": mindmap_markdown_summary(&nodes), + "revision": if input.dry_run.unwrap_or(false) { Value::Null } else { file_revision(&path) }, + "changedFiles": changed_files, + "embedResult": embed_result, + "source": "local_folder" + })) } pub async fn office_fetch_summary( @@ -235,6 +379,45 @@ impl ResourceToolTarget { Ok(canonical) } + fn resolve_create_path( + &self, + context: &RequestContext, + input: &ToolCallInput, + ) -> Result { + let root_uri = local_root_uri_for_resource(input).ok_or_else(|| { + WebError::new( + StatusCode::FORBIDDEN, + "mnote_resource_root_uri_required", + "资源工具需要授权 rootUri", + ) + .with_context(context) + })?; + let root = crate::routes::ensure_local_workspace_access(context, &root_uri) + .map_err(|error| error.with_context(context))?; + let relative = self.relative_path(); + let relative_path = Path::new(&relative); + if relative_path.is_absolute() + || relative_path + .components() + .any(|component| matches!(component, std::path::Component::ParentDir)) + { + return Err(WebError::bad_request_code( + "mnote_resource_root_escape", + "资源工具不能越过授权目录", + ) + .with_context(context)); + } + let path = root.join(relative_path); + if !path.starts_with(&root) { + return Err(WebError::bad_request_code( + "mnote_resource_root_escape", + "资源工具不能越过授权目录", + ) + .with_context(context)); + } + Ok(path) + } + fn relative_path(&self) -> String { self.resource_path .as_deref() @@ -315,9 +498,508 @@ fn local_root_uri_for_resource(input: &ToolCallInput) -> Option { }) } +fn embed_mindmap_into_local_markdown_page( + context: &RequestContext, + input: &ToolCallInput, + document_id: &str, + resource_relative_path: &str, + title: &str, +) -> Result { + let root_uri = local_root_uri_for_resource(input).ok_or_else(|| { + WebError::new( + StatusCode::FORBIDDEN, + "mnote_resource_root_uri_required", + "资源工具需要授权 rootUri", + ) + .with_context(context) + })?; + let root = crate::routes::ensure_local_workspace_access(context, &root_uri) + .map_err(|error| error.with_context(context))?; + let page_relative_path = local_markdown_relative_path_from_document_id(context, document_id)?; + let page_path = root.join(&page_relative_path); + if !page_path.starts_with(&root) { + return Err(WebError::bad_request_code( + "mnote_resource_root_escape", + "资源工具不能越过授权目录", + ) + .with_context(context)); + } + if !page_path.is_file() { + return Err(WebError::bad_request_code( + "mnote_resource_page_not_found", + "找不到要绑定 mindmap 的本地 Markdown 页面", + ) + .with_context(context)); + } + let href = markdown_href_from_page(&root, &page_path, resource_relative_path); + let mut markdown = fs::read_to_string(&page_path).map_err(|error| { + WebError::bad_request_code( + "mnote_resource_page_read_failed", + format!("无法读取本地 Markdown 页面: {error}"), + ) + .with_context(context) + })?; + let link = format!("[{}]({href})", markdown_link_label(title)); + if markdown.contains(&link) || markdown.contains(&format!("]({href})")) { + return Ok(json!({ + "status": "already_present", + "documentId": document_id, + "changedFile": page_relative_path, + "href": href + })); + } + if !markdown.ends_with('\n') { + markdown.push('\n'); + } + if !markdown.ends_with("\n\n") { + markdown.push('\n'); + } + markdown.push_str(&link); + markdown.push('\n'); + fs::write(&page_path, markdown).map_err(|error| { + WebError::bad_request_code( + "mnote_resource_page_write_failed", + format!("无法写入本地 Markdown 页面: {error}"), + ) + .with_context(context) + })?; + Ok(json!({ + "status": "embedded", + "documentId": document_id, + "changedFile": page_relative_path, + "href": href + })) +} + +fn local_markdown_relative_path_from_document_id( + context: &RequestContext, + document_id: &str, +) -> Result { + let encoded = document_id + .trim() + .strip_prefix("local-md:") + .ok_or_else(|| { + WebError::bad_request_code( + "mnote_resource_local_markdown_required", + "绑定 mindmap 需要 local-md 页面", + ) + .with_context(context) + })?; + let relative = crate::routes::decode_local_id_segment(encoded) + .map_err(|error| error.with_context(context))?; + let path = Path::new(&relative); + if path.is_absolute() + || path + .components() + .any(|component| matches!(component, std::path::Component::ParentDir)) + { + return Err(WebError::bad_request_code( + "mnote_resource_root_escape", + "资源工具不能越过授权目录", + ) + .with_context(context)); + } + Ok(relative) +} + +fn markdown_href_from_page(root: &Path, page_path: &Path, resource_relative_path: &str) -> String { + let page_dir = page_path.parent().unwrap_or(root); + let target = root.join(resource_relative_path); + if let Ok(relative) = target.strip_prefix(page_dir) { + return path_to_markdown_href(relative); + } + let page_dir_relative = page_dir.strip_prefix(root).unwrap_or(Path::new("")); + let depth = page_dir_relative + .components() + .filter(|component| matches!(component, std::path::Component::Normal(_))) + .count(); + let mut href = String::new(); + for _ in 0..depth { + href.push_str("../"); + } + href.push_str(&resource_relative_path.replace('\\', "/")); + href +} + +fn path_to_markdown_href(path: &Path) -> String { + path.to_string_lossy().replace('\\', "/") +} + +fn markdown_link_label(value: &str) -> String { + value + .trim() + .replace('\\', "\\\\") + .replace('[', "\\[") + .replace(']', "\\]") + .replace('\n', " ") +} + +fn default_mindmap_view() -> Value { + json!({ + "state": { + "scale": 1, + "sx": 0, + "sy": 0, + "x": -44.99991989135742_f64, + "y": -15.500006675720217_f64 + }, + "transform": { + "a": 1, + "b": 0, + "c": 0, + "d": 1, + "e": -44.99991989135742_f64, + "f": -15.500006675720217_f64, + "originX": 0, + "originY": 0, + "rotate": 0, + "scaleX": 1, + "scaleY": 1, + "shear": 0, + "translateX": -44.99991989135742_f64, + "translateY": -15.500006675720217_f64 + } + }) +} + +fn mindmap_envelope_from_outline( + title: &str, + outline: &Value, + source_refs: Value, + context: &RequestContext, +) -> Result { + let outline_items = outline.as_array().ok_or_else(|| { + WebError::bad_request_code( + "mnote_mindmap_outline_invalid", + "mindmap outline 必须是数组", + ) + .with_context(context) + })?; + let title = title.trim(); + let root_title = if title.is_empty() { "KMIND" } else { title }; + Ok(json!({ + "data": { + "children": mindmap_outline_items_to_children(outline_items, "node"), + "data": { + "expand": true, + "isActive": false, + "text": root_title, + "uid": "root" + } + }, + "view": default_mindmap_view(), + "metadata": { + "sourceRefs": source_refs + } + })) +} + +fn mindmap_outline_items_to_children(items: &[Value], prefix: &str) -> Vec { + items + .iter() + .enumerate() + .map(|(index, item)| { + let ordinal = index + 1; + let uid = format!("{prefix}_{ordinal}"); + let text = item + .get("text") + .or_else(|| item.get("title")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("未命名节点"); + let children = item + .get("children") + .and_then(Value::as_array) + .map(|children| mindmap_outline_items_to_children(children, &uid)) + .unwrap_or_default(); + json!({ + "data": { + "expand": true, + "isActive": false, + "text": text, + "uid": uid + }, + "children": children + }) + }) + .collect() +} + +fn mindmap_root_value(value: &Value) -> &Value { + value + .get("data") + .filter(|data| data.get("data").is_some() || data.get("children").is_some()) + .unwrap_or(value) +} + +fn mindmap_root_value_mut(value: &mut Value) -> Option<&mut Value> { + let has_envelope_root = value + .get("data") + .map(|data| data.get("data").is_some() || data.get("children").is_some()) + .unwrap_or(false); + if has_envelope_root { + return value.get_mut("data"); + } + Some(value) +} + +fn apply_mindmap_ops( + context: &RequestContext, + envelope: &mut Value, + ops: &Value, +) -> Result<(), WebError> { + let ops = ops.as_array().ok_or_else(|| { + WebError::bad_request_code("mnote_resource_ops_invalid", "mindmap ops 必须是数组") + .with_context(context) + })?; + let root = mindmap_root_value_mut(envelope).ok_or_else(|| { + WebError::bad_request_code("mnote_resource_json_invalid", "mindmap 缺少 root") + .with_context(context) + })?; + for op in ops { + apply_mindmap_op(context, root, op)?; + } + Ok(()) +} + +fn apply_mindmap_op( + context: &RequestContext, + root: &mut Value, + op: &Value, +) -> Result<(), WebError> { + let op_name = op + .get("op") + .or_else(|| op.get("type")) + .or_else(|| op.get("action")) + .and_then(Value::as_str) + .map(normalize_mindmap_op_name) + .ok_or_else(|| { + WebError::bad_request_code("mnote_resource_op_required", "mindmap op 缺少 op") + .with_context(context) + })?; + match op_name.as_str() { + "updatetext" | "updatenode" => { + let node_id = mindmap_op_string(op, &["nodeId", "node_id", "id"]).ok_or_else(|| { + WebError::bad_request_code("mnote_resource_node_required", "更新节点缺少 nodeId") + .with_context(context) + })?; + let text = mindmap_op_string(op, &["text", "title"]).ok_or_else(|| { + WebError::bad_request_code("mnote_resource_text_required", "更新节点缺少 text") + .with_context(context) + })?; + let node = find_mindmap_node_mut(root, &node_id).ok_or_else(|| { + WebError::bad_request_code("mnote_resource_node_not_found", "找不到要更新的节点") + .with_context(context) + })?; + set_mindmap_node_text(node, &text); + } + "insertchild" | "addchild" => { + let parent_id = mindmap_op_string(op, &["parentId", "parent_id", "nodeId"]) + .ok_or_else(|| { + WebError::bad_request_code( + "mnote_resource_parent_required", + "插入子节点缺少 parentId", + ) + .with_context(context) + })?; + let parent = find_mindmap_node_mut(root, &parent_id).ok_or_else(|| { + WebError::bad_request_code("mnote_resource_node_not_found", "找不到父节点") + .with_context(context) + })?; + let child_input = op.get("node").unwrap_or(op); + let child = mindmap_node_from_input(context, child_input)?; + ensure_mindmap_children_array(context, parent)?.push(child); + } + "deletenode" => { + let node_id = mindmap_op_string(op, &["nodeId", "node_id", "id"]).ok_or_else(|| { + WebError::bad_request_code("mnote_resource_node_required", "删除节点缺少 nodeId") + .with_context(context) + })?; + if mindmap_node_matches(root, &node_id) { + return Err(WebError::bad_request_code( + "mnote_resource_root_delete_forbidden", + "不能删除 mindmap 根节点", + ) + .with_context(context)); + } + remove_mindmap_node(root, &node_id).ok_or_else(|| { + WebError::bad_request_code("mnote_resource_node_not_found", "找不到要删除的节点") + .with_context(context) + })?; + } + _ => { + return Err(WebError::bad_request_code( + "mnote_resource_op_unsupported", + format!("暂不支持 mindmap op: {op_name}"), + ) + .with_context(context)); + } + } + Ok(()) +} + +fn normalize_mindmap_op_name(value: &str) -> String { + value + .chars() + .filter(|ch| *ch != '_' && *ch != '-' && !ch.is_whitespace()) + .flat_map(char::to_lowercase) + .collect() +} + +fn mindmap_op_string(op: &Value, keys: &[&str]) -> Option { + keys.iter().find_map(|key| { + op.get(*key) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) + }) +} + +fn find_mindmap_node_mut<'a>(node: &'a mut Value, node_id: &str) -> Option<&'a mut Value> { + if mindmap_node_matches(node, node_id) { + return Some(node); + } + let children = node.get_mut("children").and_then(Value::as_array_mut)?; + for child in children { + if let Some(found) = find_mindmap_node_mut(child, node_id) { + return Some(found); + } + } + None +} + +fn mindmap_node_matches(node: &Value, node_id: &str) -> bool { + mindmap_node_id(node) + .as_deref() + .map(|value| value == node_id) + .unwrap_or(false) +} + +fn mindmap_node_id(node: &Value) -> Option { + node.pointer("/data/uid") + .or_else(|| node.pointer("/data/id")) + .or_else(|| node.get("uid")) + .or_else(|| node.get("id")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) +} + +fn set_mindmap_node_text(node: &mut Value, text: &str) { + if let Some(data) = node.get_mut("data").and_then(Value::as_object_mut) { + data.insert("text".into(), json!(text)); + return; + } + if let Some(object) = node.as_object_mut() { + object.insert("text".into(), json!(text)); + } +} + +fn ensure_mindmap_children_array<'a>( + context: &RequestContext, + node: &'a mut Value, +) -> Result<&'a mut Vec, WebError> { + let object = node.as_object_mut().ok_or_else(|| { + WebError::bad_request_code("mnote_resource_node_invalid", "mindmap 节点必须是对象") + .with_context(context) + })?; + let children = object + .entry("children") + .or_insert_with(|| Value::Array(Vec::new())); + if !children.is_array() { + *children = Value::Array(Vec::new()); + } + Ok(children.as_array_mut().expect("children 已归一为数组")) +} + +fn mindmap_node_from_input(context: &RequestContext, input: &Value) -> Result { + if input.get("data").is_some() || input.get("children").is_some() { + let mut node = input.clone(); + ensure_mindmap_children_array(context, &mut node)?; + return Ok(node); + } + let text = input + .get("text") + .or_else(|| input.get("title")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("未命名节点"); + let uid = input + .get("uid") + .or_else(|| input.get("id")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or(text); + let mut node = json!({ + "data": { + "expand": true, + "isActive": false, + "text": text, + "uid": uid + }, + "children": [] + }); + if let Some(object) = node.as_object_mut() { + for key in ["metadata", "sourceRefs", "refs", "note", "hyperlink"] { + if let Some(value) = input.get(key) { + object.insert(key.into(), value.clone()); + } + } + } + Ok(node) +} + +fn remove_mindmap_node(parent: &mut Value, node_id: &str) -> Option { + let children = parent.get_mut("children").and_then(Value::as_array_mut)?; + if let Some(index) = children + .iter() + .position(|child| mindmap_node_matches(child, node_id)) + { + return Some(children.remove(index)); + } + for child in children { + if let Some(removed) = remove_mindmap_node(child, node_id) { + return Some(removed); + } + } + None +} + +fn ensure_mindmap_revision_precondition( + context: &RequestContext, + input: &ToolCallInput, + path: &Path, +) -> Result<(), WebError> { + let Some(expected) = input.arg_value("expectedRevision") else { + return Ok(()); + }; + let current = file_revision(path); + if revision_label(&expected).as_deref() != revision_label(¤t).as_deref() { + return Err(WebError::bad_request_code( + "mnote_resource_revision_conflict", + "mindmap resource revision 已变化,请重新读取后再写入", + ) + .with_context(context)); + } + Ok(()) +} + +fn revision_label(value: &Value) -> Option { + match value { + Value::String(value) => Some(value.trim().to_string()).filter(|value| !value.is_empty()), + Value::Number(value) => Some(value.to_string()), + _ => None, + } +} + fn collect_mindmap_nodes(value: &Value) -> Vec { let mut nodes = Vec::new(); - collect_mindmap_nodes_inner(value, &mut nodes); + collect_mindmap_nodes_inner(mindmap_root_value(value), &mut nodes); nodes } diff --git a/rust/crates/mnote-web/src/hermes_tools/skill.rs b/rust/crates/mnote-web/src/hermes_tools/skill.rs index 3eb90585..2aaa8d9a 100644 --- a/rust/crates/mnote-web/src/hermes_tools/skill.rs +++ b/rust/crates/mnote-web/src/hermes_tools/skill.rs @@ -41,6 +41,68 @@ const SKILLS: &[MnoteSkill] = &[ tool_names: &["mnote.context.snapshot", "mnote.context.resolve_target"], content: include_str!("../../../../../skills/mnote-local-file/SKILL.md"), }, + MnoteSkill { + id: "mnote-onlyoffice-live", + title: "MNote ONLYOFFICE live bridge", + description: "Operate the currently open ONLYOFFICE editor session for Word, Excel, and PPT.", + agent_ids: &["hermes", "reasonix"], + read_only: false, + requires_context_refs: &["onlyoffice"], + tool_names: &[ + "mnote.onlyoffice.session.current", + "mnote.onlyoffice.capabilities", + "mnote.onlyoffice.selection.get", + "mnote.onlyoffice.document.insert_text", + "mnote.onlyoffice.document.replace_selection", + "mnote.onlyoffice.document.insert_html", + "mnote.onlyoffice.document.export", + "mnote.onlyoffice.document.search_replace", + "mnote.onlyoffice.document.insert_table", + "mnote.onlyoffice.document.get_comments", + "mnote.onlyoffice.document.add_comment", + "mnote.onlyoffice.sheet.get_sheets", + "mnote.onlyoffice.sheet.add_sheet", + "mnote.onlyoffice.sheet.rename_sheet", + "mnote.onlyoffice.sheet.get_range", + "mnote.onlyoffice.sheet.get_range_values", + "mnote.onlyoffice.sheet.get_values", + "mnote.onlyoffice.sheet.set_value", + "mnote.onlyoffice.sheet.set_formula", + "mnote.onlyoffice.sheet.batch_set_values", + "mnote.onlyoffice.sheet.set_range_values", + "mnote.onlyoffice.sheet.format_range", + "mnote.onlyoffice.sheet.set_dimensions", + "mnote.onlyoffice.sheet.sort_range", + "mnote.onlyoffice.sheet.add_chart", + "mnote.onlyoffice.presentation.get_slides", + "mnote.onlyoffice.presentation.get_slide_texts", + "mnote.onlyoffice.presentation.get_shapes", + "mnote.onlyoffice.presentation.add_text_slide", + "mnote.onlyoffice.presentation.replace_text", + "mnote.onlyoffice.presentation.set_shape_text", + "mnote.onlyoffice.presentation.delete_slide", + "mnote.onlyoffice.presentation.add_table", + "mnote.onlyoffice.presentation.clear_slide", + "mnote.onlyoffice.presentation.add_shape", + ], + content: include_str!("../../../../../skills/mnote-onlyoffice-live/SKILL.md"), + }, + MnoteSkill { + id: "mnote-mindmap", + title: "MNote mindmap editing", + description: "Read, update, summarize, or create MNote mindmap resources, including generating a new mindmap from PDF or document outlines.", + agent_ids: &["hermes", "reasonix"], + read_only: false, + requires_context_refs: &["current_page", "file", "folder", "resource"], + tool_names: &[ + "mnote.context.snapshot", + "mnote.context.resolve_target", + "mnote.mindmap.fetch", + "mnote.mindmap.apply_ops", + "mnote.mindmap.create_from_outline", + ], + content: include_str!("../../../../../skills/mnote-mindmap/SKILL.md"), + }, MnoteSkill { id: "mnote-chat-only", title: "MNote chat only", @@ -146,4 +208,108 @@ mod tests { assert!(find_skill("mnote-local-file", Some("chat_only")).is_none()); assert!(find_skill("missing", Some("reasonix")).is_none()); } + + #[test] + fn skill_registry_exposes_onlyoffice_live_skill_to_agents() { + let reasonix_skills = skill_summaries_for_agent(Some("reasonix")); + let skill = reasonix_skills + .iter() + .find(|skill| skill["id"] == "mnote-onlyoffice-live") + .expect("reasonix should see live ONLYOFFICE skill"); + assert_eq!(skill["readOnly"], false); + assert_eq!( + skill["toolNames"] + .as_array() + .expect("tool names") + .iter() + .any(|name| name == "mnote.onlyoffice.session.current"), + true + ); + assert_eq!( + skill["toolNames"] + .as_array() + .expect("tool names") + .iter() + .any(|name| name == "mnote.onlyoffice.sheet.batch_set_values"), + true + ); + assert_eq!( + skill["toolNames"] + .as_array() + .expect("tool names") + .iter() + .any(|name| name == "mnote.onlyoffice.sheet.add_chart"), + true + ); + assert_eq!( + skill["toolNames"] + .as_array() + .expect("tool names") + .iter() + .any(|name| name == "mnote.onlyoffice.presentation.add_shape"), + true + ); + assert_eq!( + skill["toolNames"] + .as_array() + .expect("tool names") + .iter() + .any(|name| name == "mnote.onlyoffice.presentation.replace_text"), + true + ); + } + + #[test] + fn skill_registry_exposes_mindmap_skill_to_agents() { + let reasonix_skills = skill_summaries_for_agent(Some("reasonix")); + let skill = reasonix_skills + .iter() + .find(|skill| skill["id"] == "mnote-mindmap") + .expect("reasonix should see mindmap skill"); + assert_eq!(skill["readOnly"], false); + assert!(skill["requiresContextRefs"] + .as_array() + .expect("context refs") + .iter() + .any(|value| value == "resource")); + assert!(skill["toolNames"] + .as_array() + .expect("tool names") + .iter() + .any(|name| name == "mnote.mindmap.create_from_outline")); + } + + #[tokio::test] + async fn skill_read_returns_mindmap_skill_content() { + let context = RequestContext::from_http_parts( + &axum::http::Method::POST, + &"/api/hermes/tools/execute".parse().expect("uri"), + &axum::http::HeaderMap::new(), + ); + let input: ToolCallInput = serde_json::from_value(json!({ + "toolName": "mnote.skill.read", + "args": { + "skillId": "mnote-mindmap", + "agentId": "reasonix" + } + })) + .expect("input"); + + let payload = skill_read(&context, &input).await.expect("skill read"); + + assert_eq!(payload["skill"]["id"], "mnote-mindmap"); + assert!(payload["content"] + .as_str() + .unwrap_or_default() + .contains("mnote.mindmap.create_from_outline")); + let content = payload["content"].as_str().unwrap_or_default(); + assert!(content.contains("Mindmap outline format")); + assert!(content.contains("Use concise keywords or short phrases")); + assert!(content.contains("Avoid long paragraphs")); + assert!(payload["tools"] + .as_array() + .expect("tools") + .iter() + .any(|tool| tool == "mnote.mindmap.create_from_outline")); + } } diff --git a/rust/crates/mnote-web/src/page_aggregate/builder.rs b/rust/crates/mnote-web/src/page_aggregate/builder.rs index 836a4424..8b7d8640 100644 --- a/rust/crates/mnote-web/src/page_aggregate/builder.rs +++ b/rust/crates/mnote-web/src/page_aggregate/builder.rs @@ -65,7 +65,7 @@ impl PageAggregateBuilder { Self { document_id: String::new(), workspace_id: "default".to_string(), - source: PageAggregateSource::CompatMetaContentJoin, + source: PageAggregateSource::KernelProjection, parent_id: None, path: Vec::new(), sidebar_tree_membership: vec!["page-tree".into(), "file-tree".into()], @@ -372,3 +372,29 @@ impl Default for PageAggregateBuilder { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_defaults_to_kernel_projection() { + let aggregate = PageAggregateBuilder::new() + .document_id("doc_1") + .workspace_id("ws_demo") + .build(); + + assert_eq!(aggregate.source, PageAggregateSource::KernelProjection); + } + + #[test] + fn explicit_compat_source_is_still_supported() { + let aggregate = PageAggregateBuilder::new() + .document_id("doc_1") + .workspace_id("ws_demo") + .source(PageAggregateSource::CompatMetaContentJoin) + .build(); + + assert_eq!(aggregate.source, PageAggregateSource::CompatMetaContentJoin); + } +} diff --git a/rust/crates/mnote-web/src/routes/hermes_client.rs b/rust/crates/mnote-web/src/routes/hermes_client.rs index 0f931b14..d404b76c 100644 --- a/rust/crates/mnote-web/src/routes/hermes_client.rs +++ b/rust/crates/mnote-web/src/routes/hermes_client.rs @@ -9,7 +9,10 @@ use axum::extract::{Extension, Path, Query, State}; use axum::http::{header, HeaderMap, HeaderName, HeaderValue, StatusCode}; use axum::response::Response; use axum::Json; -use control_plane::{AppendAiRuntimeEventInput, UpsertAiRuntimeRunInput}; +use control_plane::{ + AiAgentProfileAccessRecord, AiExternalConversationBindingRecord, AppendAiRuntimeEventInput, + UpsertAiExternalConversationBindingInput, UpsertAiRuntimeRunInput, UpsertUserInput, +}; use futures_util::TryStreamExt; use serde::Deserialize; use serde_json::{json, Value}; @@ -32,6 +35,26 @@ const ACP_RUNTIME_SQLITE_STORE: &str = "sqlite_acp_runtime_store"; const ACP_ABORT_NOTIFICATION_TIMEOUT_MS: u64 = 2_500; const LOCAL_SHARE_GRANTS_JSON: &str = "/mnt/Data1T/Mnote_data/control-plane/share-grants.json"; const ENV_LOCAL_SHARE_GRANTS_FILE: &str = "MNOTE_SHARE_GRANTS_FILE"; +const MNOTE_PERSONAL_SKILL_BASELINE_MARKER: &str = "mnotePersonalSkillBaseline: v1"; +const MNOTE_PERSONAL_SKILL_ALLOWLIST: &[&str] = &["global-search", "vpn", "zhihu-search"]; +const ENV_DOUBAO_CONVERSATION_DELETE_URL: &str = "MNOTE_WEB_DOUBAO_CONVERSATION_DELETE_URL"; +const ENV_DOUBAO_CONVERSATION_LOOKUP_URL: &str = "MNOTE_WEB_DOUBAO_CONVERSATION_LOOKUP_URL"; +const ENV_DEEPSEEK_CONVERSATION_DELETE_URL: &str = "MNOTE_WEB_DEEPSEEK_CONVERSATION_DELETE_URL"; +const ENV_DEEPSEEK_CONVERSATION_LOOKUP_URL: &str = "MNOTE_WEB_DEEPSEEK_CONVERSATION_LOOKUP_URL"; +const ENV_GEMINI_CONVERSATION_DELETE_URL: &str = "MNOTE_WEB_GEMINI_CONVERSATION_DELETE_URL"; +const ENV_GEMINI_CONVERSATION_LOOKUP_URL: &str = "MNOTE_WEB_GEMINI_CONVERSATION_LOOKUP_URL"; +const DEFAULT_DOUBAO_CONVERSATION_DELETE_URL: &str = + "http://127.0.0.1:30343/mnote/provider-conversations/doubao-web/{conversationId}"; +const DEFAULT_DOUBAO_CONVERSATION_LOOKUP_URL: &str = + "http://127.0.0.1:30343/mnote/provider-conversations/doubao-web/mnote-session/{sessionId}"; +const DEFAULT_DEEPSEEK_CONVERSATION_DELETE_URL: &str = + "http://127.0.0.1:30341/mnote/provider-conversations/deepseek-web/{conversationId}"; +const DEFAULT_DEEPSEEK_CONVERSATION_LOOKUP_URL: &str = + "http://127.0.0.1:30341/mnote/provider-conversations/deepseek-web/mnote-session/{sessionId}"; +const DEFAULT_GEMINI_CONVERSATION_DELETE_URL: &str = + "http://127.0.0.1:30342/mnote/provider-conversations/gemini-web/{conversationId}"; +const DEFAULT_GEMINI_CONVERSATION_LOOKUP_URL: &str = + "http://127.0.0.1:30342/mnote/provider-conversations/gemini-web/mnote-session/{sessionId}"; static HERMES_RUNTIME_REGISTRY: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); @@ -42,6 +65,8 @@ static ACP_RUN_PAYLOADS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static ACP_ACTIVE_RUNS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); +static ACP_FINISHED_RUNS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); static ACP_LOCAL_AUDIT_SNAPSHOTS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); @@ -367,11 +392,48 @@ async fn list_acp_sessions( state, context, root_uri, ) .map_err(|error| error.with_context(context))?; + let user_id = effective_session_store_user_id(state, context).await?; let limit = query .get("limit") .and_then(|value| value.parse::().ok()) .unwrap_or(50) .clamp(1, 100); + let workspace_id = query + .get("workspaceId") + .map(String::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()); + let document_id = query + .get("documentId") + .map(String::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()); + let session_id = query + .get("sessionId") + .map(String::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()); + let sqlite_runs = state + .control_plane() + .list_ai_runtime_runs(&user_id, workspace_id, document_id, session_id, limit) + .map_err(|error| { + WebError::internal(format!("SQLite ACP session 列表读取失败: {error}")) + .with_context(context) + })?; + if !sqlite_runs.is_empty() { + return Ok(( + StatusCode::OK, + stamp_client_headers(), + Json(json!({ + "ok": true, + "traceId": context.trace.trace_id, + "persistence": ACP_RUNTIME_SQLITE_STORE, + "sessionStorage": "sqlite_control_plane", + "legacyPersistence": "local_ai_session_jsonl", + "sessions": sqlite_runs.iter().map(ai_runtime_run_to_json).collect::>() + })), + )); + } let sessions = list_local_ai_sessions(root_uri, limit)?; return Ok(( StatusCode::OK, @@ -601,6 +663,52 @@ pub async fn create_session( append_local_ai_session_event(root_uri, &session_id, &audit_event, share_id) .map_err(|error| error.with_context(&context))?; } + let session_index_run_id = format!("{}_session", session_id); + let runtime_state = json!({ + "sessionId": session_id, + "runId": session_index_run_id, + "profile": profile, + "documentId": document_id, + "traceId": trace_id, + "status": "session.created" + }); + let runtime_payload = json!({ + "workspaceId": payload.workspace_id.clone(), + "documentId": payload.document_id.clone(), + "sessionId": session_id, + "sourceKind": "local_folder", + "rootUri": root_uri, + "profile": profile, + "title": payload.title.clone(), + "actorId": session_store_user_id.clone(), + "actorType": "user", + "traceId": trace_id, + "permissionLevel": permission_level, + "shareId": share_id, + }); + let session_acp_runtime = + acp_runtime_for_payload(&runtime_payload, profile).unwrap_or_else(|| "hermes".into()); + state + .control_plane() + .upsert_ai_runtime_run(UpsertAiRuntimeRunInput { + id: None, + user_id: session_store_user_id.clone(), + workspace_id: payload.workspace_id.clone(), + document_id: payload.document_id.clone(), + session_id: session_id.clone(), + run_id: session_index_run_id, + title: payload.title.clone(), + profile: profile.to_string(), + acp_runtime: session_acp_runtime, + trace_id: Some(trace_id.clone()), + status: "session.created".to_string(), + runtime_json: json_string(&runtime_state), + payload_json: json_string(&runtime_payload), + }) + .map_err(|error| { + WebError::internal(format!("SQLite ACP local session 写入失败: {error}")) + .with_context(&context) + })?; return Ok(( StatusCode::OK, stamp_client_headers(), @@ -612,8 +720,10 @@ pub async fn create_session( "profile": profile, "title": payload.title.unwrap_or_else(|| "当前页问答".into()), "traceId": trace_id, - "persistence": "local_ai_session_jsonl", - "sessionStorage": session_storage, + "persistence": ACP_RUNTIME_SQLITE_STORE, + "sessionStorage": "sqlite_control_plane", + "legacyPersistence": "local_ai_session_jsonl", + "legacySessionStorage": session_storage, "permissionLevel": permission_level, "shareId": share_id })), @@ -827,14 +937,16 @@ pub async fn switch_active_profile( } pub async fn get_profile_memory( + State(state): State, Extension(context): Extension, Query(query): Query>, ) -> Result<(StatusCode, HeaderMap, Json), WebError> { ensure_authenticated(&context)?; let fallback_profile = active_profile_name().unwrap_or_else(|| "default".into()); - let profile = query - .get("profile") - .map(String::as_str) + let profile_access = resolve_hermes_profile_from_query(&state, &context, &query)?; + let profile = profile_access + .as_ref() + .map(|access| access.profile.isolated_profile_name.as_str()) .unwrap_or(fallback_profile.as_str()); Ok(( StatusCode::OK, @@ -844,15 +956,35 @@ pub async fn get_profile_memory( } pub async fn save_profile_memory( + State(state): State, Extension(context): Extension, Json(payload): Json, ) -> Result<(StatusCode, HeaderMap, Json), WebError> { ensure_authenticated(&context)?; let fallback_profile = active_profile_name().unwrap_or_else(|| "default".into()); - let profile = payload - .get("profile") - .and_then(Value::as_str) + let profile_access = resolve_hermes_profile_from_payload(&state, &context, &payload)?; + let profile = profile_access + .as_ref() + .map(|access| access.profile.isolated_profile_name.as_str()) .unwrap_or(fallback_profile.as_str()); + if let Some(access) = &profile_access { + if !access.grant.can_manage_config { + return Err(ai_profile_forbidden( + &context, + "ai_profile_readonly", + "当前用户不能修改该 Hermes profile 的记忆配置", + )); + } + ensure_managed_profile_home(&access.profile.isolated_profile_name).map_err(|error| { + WebError::bad_gateway_code( + "hermes_profile_provision_failed", + format!("初始化 Hermes profile 目录失败: {error}"), + ) + .with_context(&context) + .with_header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .with_header(HEADER_HERMES_CLIENT_OWNER, "mnote-web-hermes-client") + })?; + } let section = payload .get("section") .and_then(Value::as_str) @@ -887,29 +1019,108 @@ pub async fn save_profile_memory( )) } +pub async fn list_agent_profiles( + State(state): State, + Extension(context): Extension, + Query(query): Query>, +) -> Result<(StatusCode, HeaderMap, Json), WebError> { + ensure_authenticated(&context)?; + let agent_id = query + .get("agentId") + .or_else(|| query.get("agent_id")) + .map(String::as_str) + .unwrap_or("hermes") + .trim(); + if agent_id != "hermes" { + return Ok(( + StatusCode::OK, + stamp_client_headers(), + Json(json!({"agentId": agent_id, "profiles": []})), + )); + } + let actor_id = page_ai_actor_id(&state, &context)?; + let is_admin = page_ai_actor_is_admin(&context); + ensure_page_ai_actor_user(&state, &actor_id, is_admin)?; + let profiles = state + .control_plane() + .ensure_ai_agent_profile_policy(&actor_id, is_admin) + .map_err(|error| { + WebError::internal(format!("SQLite AI profile policy 读取失败: {error}")) + })?; + Ok(( + StatusCode::OK, + stamp_client_headers(), + Json(json!({ + "agentId": "hermes", + "profiles": profiles.into_iter().map(agent_profile_access_json).collect::>() + })), + )) +} + pub async fn list_skills( + State(state): State, Extension(context): Extension, Query(query): Query>, ) -> Result<(StatusCode, HeaderMap, Json), WebError> { ensure_authenticated(&context)?; let fallback_profile = active_profile_name().unwrap_or_else(|| "default".into()); - let profile = query - .get("profile") - .map(String::as_str) + let requested_profile = query + .get("profileId") + .or_else(|| query.get("profile_id")) + .or_else(|| query.get("profile")) + .map(String::as_str); + let profile_access = resolve_hermes_profile_from_query(&state, &context, &query)?; + let profile = profile_access + .as_ref() + .map(|access| access.profile.isolated_profile_name.as_str()) .unwrap_or(fallback_profile.as_str()); let runtime = query.get("runtime").map(String::as_str).unwrap_or(profile); + if runtime != "mnote" + && runtime != "reasonix" + && requested_profile.is_some() + && profile_access.is_none() + { + return Err(ai_profile_forbidden( + &context, + "ai_profile_forbidden", + "当前用户无权访问该 Hermes profile", + )); + } Ok(( StatusCode::OK, stamp_client_headers(), Json(match runtime { - "mnote" => mnote_builtin_skills_payload(query.get("agentId").map(String::as_str)), + "mnote" => { + let mut payload = + mnote_builtin_skills_payload(query.get("agentId").map(String::as_str)); + stamp_mnote_builtin_skill_payload_policy(&state, &context, &mut payload)?; + payload + } "reasonix" => reasonix_skills_payload(), - _ => skills_payload(profile), + _ => { + if let Some(access) = profile_access.as_ref() { + ensure_personal_profile_skill_baseline(access).map_err(|error| { + WebError::bad_gateway_code( + "hermes_profile_skill_baseline_failed", + format!("初始化个人 Hermes profile 默认技能失败: {error}"), + ) + .with_context(&context) + .with_header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .with_header(HEADER_HERMES_CLIENT_OWNER, "mnote-web-hermes-client") + })?; + } + let mut payload = skills_payload(profile); + if let Some(access) = profile_access { + stamp_hermes_skill_payload_profile_policy(&mut payload, &access); + } + payload + } }), )) } pub async fn toggle_skill( + State(state): State, Extension(context): Extension, Json(payload): Json, ) -> Result<(StatusCode, HeaderMap, Json), WebError> { @@ -934,12 +1145,63 @@ pub async fn toggle_skill( .with_header(HEADER_MNOTE_WEB_OWNER, "mnote-web") .with_header(HEADER_HERMES_CLIENT_OWNER, "mnote-web-hermes-client") })?; - let fallback_profile = active_profile_name().unwrap_or_else(|| "default".into()); - let profile = payload - .get("profile") + let skill_kind = payload + .get("skillKind") + .or_else(|| payload.get("kind")) .and_then(Value::as_str) - .unwrap_or(fallback_profile.as_str()); - set_skill_enabled(profile, name, enabled).map_err(|error| { + .map(str::trim) + .unwrap_or("hermes_profile"); + if skill_kind == "mnote_builtin" { + let actor_id = page_ai_actor_id(&state, &context)?; + ensure_page_ai_actor_user(&state, &actor_id, page_ai_actor_is_admin(&context))?; + let key = format!("ai.agent.mnote_builtin.skill.{name}.enabled"); + state + .control_plane() + .upsert_user_ui_preference(control_plane::UpsertUserUiPreferenceInput { + id: None, + user_id: actor_id, + workspace_id: None, + source_kind: None, + scope_kind: "page_ai_skill".to_string(), + scope_id: "mnote_builtin".to_string(), + key, + value_json: Value::Bool(enabled).to_string(), + }) + .map_err(|error| { + WebError::internal(format!("SQLite MNote 内置 skill 偏好写入失败: {error}")) + .with_context(&context) + })?; + return Ok(( + StatusCode::OK, + stamp_client_headers(), + Json(json!({"ok": true, "skillKind": "mnote_builtin", "configScope": "user_sqlite"})), + )); + } + let access = + resolve_hermes_profile_from_payload(&state, &context, &payload)?.ok_or_else(|| { + ai_profile_forbidden( + &context, + "ai_profile_not_found", + "未找到可访问的 Hermes profile", + ) + })?; + if !access.grant.can_manage_skills { + return Err(ai_profile_forbidden( + &context, + "ai_profile_readonly", + "当前用户不能修改该 Hermes profile 的 skills", + )); + } + ensure_personal_profile_skill_baseline(&access).map_err(|error| { + WebError::bad_gateway_code( + "hermes_profile_provision_failed", + format!("初始化 Hermes profile 目录失败: {error}"), + ) + .with_context(&context) + .with_header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .with_header(HEADER_HERMES_CLIENT_OWNER, "mnote-web-hermes-client") + })?; + set_skill_enabled(&access.profile.isolated_profile_name, name, enabled).map_err(|error| { WebError::bad_gateway_code( "hermes_client_skill_toggle_failed", format!("更新 Hermes skill 设置失败: {error}"), @@ -951,7 +1213,7 @@ pub async fn toggle_skill( Ok(( StatusCode::OK, stamp_client_headers(), - Json(json!({"ok": true})), + Json(json!({"ok": true, "skillKind": "hermes_profile", "profileId": access.profile.id})), )) } @@ -1089,6 +1351,44 @@ pub async fn delete_session( WebError::internal(format!("SQLite ACP session 删除失败: {error}")) .with_context(&context) })?; + let provider_binding = find_provider_conversation_binding_for_session( + state.control_plane(), + &user_id, + workspace_id.as_deref(), + &session_id, + ) + .map_err(|error| { + WebError::internal(format!( + "SQLite provider conversation 绑定读取失败: {error}" + )) + .with_context(&context) + })?; + let provider_binding_local_deleted = mark_provider_conversation_local_deleted_for_session( + state.control_plane(), + &user_id, + workspace_id.as_deref(), + &session_id, + ) + .map_err(|error| { + WebError::internal(format!( + "SQLite provider conversation 删除标记失败: {error}" + )) + .with_context(&context) + })?; + let provider_conversation_delete = delete_provider_conversation_for_session( + state.control_plane(), + &user_id, + workspace_id.as_deref(), + &session_id, + provider_binding.as_ref(), + ) + .await + .map_err(|error| { + WebError::internal(format!( + "SQLite provider conversation 远端删除状态写入失败: {error}" + )) + .with_context(&context) + })?; return Ok(( StatusCode::OK, stamp_client_headers(), @@ -1098,7 +1398,9 @@ pub async fn delete_session( "result": { "ok": true, "sessionId": session_id, - "deleted": deleted + "deleted": deleted, + "providerBindingLocalDeleted": provider_binding_local_deleted, + "providerConversationDelete": provider_conversation_delete } })), )); @@ -1320,6 +1622,61 @@ async fn get_acp_session( state, context, root_uri, ) .map_err(|error| error.with_context(context))?; + let user_id = effective_session_store_user_id(state, context).await?; + let workspace_id = query + .get("workspaceId") + .map(String::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) + .or_else(|| context.workspace.workspace_id.clone()); + let runs = state + .control_plane() + .list_ai_runtime_runs( + &user_id, + workspace_id.as_deref(), + None, + Some(session_id), + 20, + ) + .map_err(|error| { + WebError::internal(format!("SQLite ACP session 详情读取失败: {error}")) + .with_context(context) + })?; + if let Some(latest_run) = runs.first() { + let mut events = Vec::new(); + for run in runs.iter().rev() { + let run_events = state + .control_plane() + .list_ai_runtime_events(&user_id, &run.run_id, 200) + .map_err(|error| { + WebError::internal(format!("SQLite ACP runtime events 读取失败: {error}")) + .with_context(context) + })?; + events.extend(run_events); + } + let runtime = serde_json::from_str::(&latest_run.runtime_json) + .unwrap_or_else(|_| runtime_state_for_session(session_id)); + return Ok(( + StatusCode::OK, + stamp_client_headers(), + Json(json!({ + "ok": true, + "traceId": context.trace.trace_id, + "persistence": ACP_RUNTIME_SQLITE_STORE, + "sessionStorage": "sqlite_control_plane", + "legacyPersistence": "local_ai_session_jsonl", + "sessionId": session_id, + "session": { + "sessionId": session_id, + "messages": [], + "runs": runs.iter().map(ai_runtime_run_to_json).collect::>() + }, + "runtime": runtime, + "events": events.iter().map(ai_runtime_event_to_json).collect::>() + })), + )); + } let events = read_local_ai_session_events(root_uri, session_id, None)?; return Ok(( StatusCode::OK, @@ -1363,17 +1720,17 @@ async fn get_acp_session( .with_context(context) })?; let latest_run = runs.first().cloned(); - let events = if let Some(run) = latest_run.as_ref() { - state + let mut events = Vec::new(); + for run in runs.iter().rev() { + let run_events = state .control_plane() .list_ai_runtime_events(&user_id, &run.run_id, 200) .map_err(|error| { WebError::internal(format!("SQLite ACP runtime events 读取失败: {error}")) .with_context(context) - })? - } else { - Vec::new() - }; + })?; + events.extend(run_events); + } let runtime = latest_run .as_ref() .and_then(|run| serde_json::from_str::(&run.runtime_json).ok()) @@ -1495,7 +1852,25 @@ pub async fn create_run( let (actor_id, actor_type) = resolve_run_actor(&state, &context).await; stamp_run_actor(&mut payload, &actor_id, &actor_type); enforce_local_ai_run_access(&state, &context, &actor_id, &mut payload)?; + apply_mnote_builtin_skill_policy_to_payload(&state, &actor_id, &mut payload)?; + if payload_requests_hermes_profile(&payload) { + let access = + resolve_hermes_profile_from_payload(&state, &context, &payload)?.ok_or_else(|| { + ai_profile_forbidden( + &context, + "ai_profile_forbidden", + "当前用户无权访问该 Hermes profile", + ) + })?; + stamp_agent_profile_ref(&mut payload, &access); + } let registration = run_registration_from_payload(&context, &payload); + inject_provider_conversation_binding_for_run( + state.control_plane(), + &context, + ®istration, + &mut payload, + )?; // ACP path: skip the HTTP proxy, just register and return run info if let Some(acp_runtime) = acp_runtime_for_payload(&payload, ®istration.profile) { @@ -1552,6 +1927,14 @@ pub async fn create_run( .get("sessionStorage") .cloned() .unwrap_or(Value::Null), + "legacyPersistence": persistence_result + .get("legacyPersistence") + .cloned() + .unwrap_or(Value::Null), + "legacySessionStorage": persistence_result + .get("legacySessionStorage") + .cloned() + .unwrap_or(Value::Null), }); return Ok((StatusCode::OK, stamp_client_headers(), Json(response))); } @@ -1601,6 +1984,70 @@ pub async fn cancel_queued_run( )) } +fn acp_sse_bytes(event: &crate::acp_bridge::SseEvent) -> axum::body::Bytes { + let json_str = serde_json::to_string(&event.data).unwrap_or_else(|_| "{}".into()); + axum::body::Bytes::from(format!("event: {}\ndata: {}\n\n", event.event, json_str)) +} + +fn acp_single_event_response( + context: &RequestContext, + event: crate::acp_bridge::SseEvent, +) -> Result { + use tokio::sync::mpsc; + let (tx, rx) = mpsc::channel::>(1); + tokio::spawn(async move { + let _ = tx.send(Ok(acp_sse_bytes(&event))).await; + }); + let stream = tokio_stream::wrappers::ReceiverStream::new(rx); + let mut response = Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "text/event-stream; charset=utf-8") + .header(header::CACHE_CONTROL, "no-cache, no-transform") + .header("x-accel-buffering", "no") + .body(Body::from_stream(stream)) + .map_err(|e| { + WebError::internal(format!("SSE response build failed: {e}")).with_context(context) + })?; + stamp_client_headers_into(response.headers_mut()); + Ok(response) +} + +fn acp_existing_run_event_response( + context: &RequestContext, + event_tx: broadcast::Sender, +) -> Result { + use tokio::sync::mpsc; + let (tx, rx) = mpsc::channel::>(256); + let mut event_rx = event_tx.subscribe(); + tokio::spawn(async move { + loop { + match event_rx.recv().await { + Ok(event) => { + if tx.send(Ok(acp_sse_bytes(&event))).await.is_err() { + break; + } + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => { + warn!("ACP duplicate SSE subscriber lagged: {n} events dropped"); + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, + } + } + }); + let stream = tokio_stream::wrappers::ReceiverStream::new(rx); + let mut response = Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "text/event-stream; charset=utf-8") + .header(header::CACHE_CONTROL, "no-cache, no-transform") + .header("x-accel-buffering", "no") + .body(Body::from_stream(stream)) + .map_err(|e| { + WebError::internal(format!("SSE response build failed: {e}")).with_context(context) + })?; + stamp_client_headers_into(response.headers_mut()); + Ok(response) +} + /// ACP variant of stream_events: creates an ACP session, runs the prompt, /// and returns an SSE stream of events. async fn acp_stream_events( @@ -1618,6 +2065,30 @@ async fn acp_stream_events( ) .with_context(&context) })?; + if let Some(active) = ACP_ACTIVE_RUNS + .lock() + .expect("acp active runs") + .get(run_id) + .cloned() + { + return acp_existing_run_event_response(&context, active.event_tx); + } + if ACP_FINISHED_RUNS + .lock() + .expect("acp finished runs") + .contains(run_id) + { + return acp_single_event_response( + &context, + crate::acp_bridge::SseEvent { + event: "run.completed".into(), + data: json!({ + "replayed": true, + "message": "ACP run already completed; not starting duplicate prompt." + }), + }, + ); + } // Build prompt from the message field (frontend sends "message", not "input") let input = payload @@ -1626,10 +2097,7 @@ async fn acp_stream_events( .and_then(Value::as_str) .unwrap_or("请读取当前文档内容"); let capability_policy = page_ai_capability_policy(&payload, input); - - let prompt_blocks = vec![ContentBlock::Text { - text: input.to_string(), - }]; + let registration = run_registration_from_payload(&context, &payload); let runtime_name = acp_runtime_name; @@ -1651,7 +2119,15 @@ async fn acp_stream_events( } else { match state.acp_runtime.get_config(runtime_name).cloned() { Some(mut config) => { - config.env = merge_acp_runtime_env(config.env, access_env); + let memory_env = if runtime_name == "reasonix" { + reasonix_memory_env_for_payload(&state, &context, &payload)? + } else { + None + }; + config.env = merge_acp_runtime_env( + merge_acp_runtime_env(config.env, access_env), + memory_env, + ); state.acp_runtime.switch_to_config(config).await } None => Err(crate::acp_client::AcpError::Internal(format!( @@ -1695,15 +2171,24 @@ async fn acp_stream_events( .with_context(&context) })?; let mnote_session_id = session_id_for_run(run_id).unwrap_or_else(|| run_id.to_string()); + let prompt_text = + chatonly_provider_prompt_text(input, &payload, ®istration, &mnote_session_id, run_id); + let prompt_blocks = vec![ContentBlock::Text { text: prompt_text }]; ACP_ACTIVE_RUNS.lock().expect("acp active runs").insert( run_id.to_string(), AcpActiveRun { manager: Arc::clone(&mgr), mnote_session_id: mnote_session_id.clone(), - acp_session_id, + acp_session_id: acp_session_id.clone(), event_tx: event_tx.clone(), }, ); + let mut payload_with_acp_session = payload.clone(); + payload_with_acp_session["acpSessionId"] = Value::String(acp_session_id.clone()); + ACP_RUN_PAYLOADS + .lock() + .expect("acp run payloads") + .insert(run_id.to_string(), payload_with_acp_session); // Build SSE response from event channel FIRST (before running prompt), // so that if the prompt fails quickly, events are not lost. @@ -1712,10 +2197,12 @@ async fn acp_stream_events( let mut event_rx = event_tx.subscribe(); let state_for_events = state.clone(); let context_for_events = context.clone(); - let event_registration = run_registration_from_payload(&context, &payload); + let event_registration = registration.clone(); let event_payload = payload.clone(); let run_id_for_events = run_id.to_string(); let acp_runtime_for_events = acp_runtime_name.to_string(); + let acp_session_id_for_binding = acp_session_id.clone(); + let binding_registration = event_registration.clone(); tokio::spawn(async move { loop { @@ -1752,6 +2239,13 @@ async fn acp_stream_events( } } }); + let _ = event_tx.send(crate::acp_bridge::SseEvent { + event: "session.info.updated".into(), + data: json!({ + "sessionId": mnote_session_id.clone(), + "acpSessionId": acp_session_id, + }), + }); // Run prompt in background let run_id_owned = run_id.to_string(); @@ -1760,6 +2254,11 @@ async fn acp_stream_events( let audit_context = context.clone(); let audit_payload = payload.clone(); let audit_runtime = acp_runtime_name.to_string(); + let binding_context = context.clone(); + let binding_payload = payload.clone(); + let binding_state = state.clone(); + let binding_run_id = run_id.to_string(); + let binding_runtime = acp_runtime_name.to_string(); let mnote_tool_context = capability_policy.attach_mnote_capabilities.then(|| { crate::acp_session_manager::AcpMnoteToolContext { mnote_session_id: Some(mnote_session_id.clone()), @@ -1788,6 +2287,19 @@ async fn acp_stream_events( { Ok(result) => { info!("ACP prompt completed: stop_reason={:?}", result.stop_reason); + if let Err(error) = persist_provider_conversation_from_proxy( + &binding_state, + &binding_context, + &binding_registration, + &binding_run_id, + &binding_runtime, + &acp_session_id_for_binding, + &binding_payload, + ) + .await + { + warn!(error = ?error, run_id = %binding_run_id, "Provider 远端会话绑定同步失败"); + } let prompt_cancelled = matches!(result.stop_reason, crate::acp_types::StopReason::Cancelled); let runtime_aborted = matches!( @@ -1862,6 +2374,10 @@ async fn acp_stream_events( runtime_status_for_run(&run_id_owned).as_deref(), Some("aborting" | "aborted") ); + ACP_FINISHED_RUNS + .lock() + .expect("acp finished runs") + .insert(run_id_owned.clone()); mgr_clone.close().await; ACP_ACTIVE_RUNS .lock() @@ -2016,6 +2532,10 @@ pub async fn abort_run( } }; update_runtime_by_run_id(&run_id, "aborted", Some("abort.completed"), None); + ACP_FINISHED_RUNS + .lock() + .expect("acp finished runs") + .insert(run_id.clone()); let _ = active.event_tx.send(crate::acp_bridge::SseEvent { event: "run.aborted".into(), data: json!({ @@ -2239,6 +2759,472 @@ fn profile_config_path(profile: &str) -> PathBuf { profile_home(profile).join("config.yaml") } +fn ensure_managed_profile_home(profile: &str) -> std::io::Result<()> { + let profile = profile.trim(); + if profile.is_empty() || profile == "default" { + return Ok(()); + } + let home = hermes_home(); + let dir = home.join("profiles").join(profile); + fs::create_dir_all(&dir)?; + let config_path = dir.join("config.yaml"); + if !config_path.exists() { + let root_config = home.join("config.yaml"); + if root_config.exists() { + let _ = fs::copy(root_config, &config_path)?; + } else { + fs::write(&config_path, "{}\n")?; + } + } + Ok(()) +} + +fn available_hermes_skill_names(skills_dir: &FsPath) -> Vec { + let Ok(entries) = fs::read_dir(skills_dir) else { + return Vec::new(); + }; + let mut names = Vec::new(); + for entry in entries.filter_map(Result::ok) { + if !entry.file_type().map(|kind| kind.is_dir()).unwrap_or(false) { + continue; + } + let name = entry.file_name().to_string_lossy().to_string(); + if name.starts_with('.') { + continue; + } + let dir = entry.path(); + if dir.join("SKILL.md").exists() { + names.push(name); + continue; + } + if let Ok(children) = fs::read_dir(&dir) { + names.extend( + children + .filter_map(Result::ok) + .filter(|child| child.file_type().map(|kind| kind.is_dir()).unwrap_or(false)) + .filter_map(|child| { + if child.path().join("SKILL.md").exists() { + Some(child.file_name().to_string_lossy().to_string()) + } else { + None + } + }), + ); + } + } + names.sort(); + names.dedup(); + names +} + +fn is_mnote_managed_personal_profile(access: &AiAgentProfileAccessRecord) -> bool { + access.profile.profile_kind == "personal" + && access.profile.isolated_profile_name.starts_with("mnote-u-") +} + +fn ensure_personal_profile_skill_baseline( + access: &AiAgentProfileAccessRecord, +) -> std::io::Result<()> { + ensure_managed_profile_home(&access.profile.isolated_profile_name)?; + if !is_mnote_managed_personal_profile(access) { + return Ok(()); + } + let config_path = profile_config_path(&access.profile.isolated_profile_name); + let existing = fs::read_to_string(&config_path).unwrap_or_default(); + if existing.contains(MNOTE_PERSONAL_SKILL_BASELINE_MARKER) { + copy_personal_profile_initial_skills(&access.profile.isolated_profile_name)?; + prune_personal_profile_disabled_skills(&access.profile.isolated_profile_name)?; + return Ok(()); + } + copy_personal_profile_initial_skills(&access.profile.isolated_profile_name)?; + write_disabled_skills_config(&config_path, &[], true) +} + +fn copy_personal_profile_initial_skills(profile: &str) -> std::io::Result<()> { + let source_root = hermes_home().join("skills"); + let target_root = profile_home(profile).join("skills"); + fs::create_dir_all(&target_root)?; + for skill in MNOTE_PERSONAL_SKILL_ALLOWLIST { + let Some(source) = find_skill_dir(&source_root, skill) else { + continue; + }; + let target = target_root.join(skill); + if target.exists() { + continue; + } + copy_dir_recursive(&source, &target)?; + } + Ok(()) +} + +fn prune_personal_profile_disabled_skills(profile: &str) -> std::io::Result<()> { + let profile_skills = available_hermes_skill_names(&profile_home(profile).join("skills")) + .into_iter() + .collect::>(); + let disabled = disabled_skills(profile) + .into_iter() + .filter(|skill| profile_skills.contains(skill.as_str())) + .collect::>(); + write_disabled_skills_config(&profile_config_path(profile), &disabled, false) +} + +fn find_skill_dir(skills_root: &FsPath, skill: &str) -> Option { + let direct = skills_root.join(skill); + if direct.join("SKILL.md").exists() { + return Some(direct); + } + let entries = fs::read_dir(skills_root).ok()?; + for entry in entries.filter_map(Result::ok) { + if !entry.file_type().map(|kind| kind.is_dir()).unwrap_or(false) { + continue; + } + let child = entry.path().join(skill); + if child.join("SKILL.md").exists() { + return Some(child); + } + } + None +} + +fn copy_dir_recursive(source: &FsPath, target: &FsPath) -> std::io::Result<()> { + fs::create_dir_all(target)?; + for entry in fs::read_dir(source)? { + let entry = entry?; + let source_path = entry.path(); + let target_path = target.join(entry.file_name()); + let file_type = entry.file_type()?; + if file_type.is_dir() { + copy_dir_recursive(&source_path, &target_path)?; + } else if file_type.is_file() { + fs::copy(&source_path, &target_path)?; + } + } + Ok(()) +} + +fn page_ai_actor_id(state: &AppState, context: &RequestContext) -> Result { + crate::routes::gateway::current_actor_id(state, context) + .filter(|actor_id| !actor_id.trim().is_empty() && actor_id.trim() != "anonymous") + .ok_or_else(|| { + WebError::new( + StatusCode::UNAUTHORIZED, + "ai_profile_auth_required", + "AI profile 需要登录用户", + ) + .with_context(context) + }) +} + +fn page_ai_actor_is_admin(context: &RequestContext) -> bool { + crate::routes::local_folder_source::is_local_access_policy_admin_context(context) +} + +fn ensure_page_ai_actor_user( + state: &AppState, + actor_id: &str, + is_admin: bool, +) -> Result<(), WebError> { + state + .control_plane() + .upsert_user(UpsertUserInput { + id: Some(actor_id.to_string()), + email: None, + username: actor_id.to_string(), + display_name: actor_id.to_string(), + role: Some(if is_admin { "admin" } else { "user" }.to_string()), + password_hash: None, + }) + .map(|_| ()) + .map_err(|error| WebError::internal(format!("SQLite AI profile 用户初始化失败: {error}"))) +} + +fn agent_profile_access_json(access: AiAgentProfileAccessRecord) -> Value { + json!({ + "profileId": access.profile.id, + "agentId": access.profile.agent_id, + "kind": access.profile.profile_kind, + "ownerUserId": access.profile.owner_user_id, + "baseProfile": access.profile.base_profile_name, + "isolatedProfile": access.profile.isolated_profile_name, + "displayName": access.profile.display_name, + "canRun": access.grant.can_run, + "canManageSkills": access.grant.can_manage_skills, + "canManageConfig": access.grant.can_manage_config, + "readonly": !access.grant.can_manage_skills, + "grantRole": access.grant.role + }) +} + +fn ai_profile_forbidden(context: &RequestContext, code: &'static str, message: &str) -> WebError { + WebError::new(StatusCode::FORBIDDEN, code, message) + .with_context(context) + .with_header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .with_header(HEADER_HERMES_CLIENT_OWNER, "mnote-web-hermes-client") +} + +fn select_profile_access( + accesses: Vec, + requested: Option<&str>, +) -> Option { + let requested = requested.map(str::trim).filter(|value| !value.is_empty()); + if let Some(value) = requested { + let normalized = if value == "lite" { + "shared_lite" + } else { + value + }; + return accesses.into_iter().find(|access| { + access.profile.id == normalized + || access.profile.isolated_profile_name == normalized + || access.profile.display_name == normalized + || ((normalized == "default" || normalized == "mnoteai") + && access.profile.profile_kind == "personal") + }); + } + accesses + .iter() + .find(|access| access.profile.profile_kind == "personal") + .cloned() + .or_else(|| { + accesses + .into_iter() + .find(|access| access.profile.id == "shared_lite") + }) +} + +fn resolve_hermes_profile_access( + state: &AppState, + context: &RequestContext, + requested: Option<&str>, +) -> Result, WebError> { + let actor_id = page_ai_actor_id(state, context)?; + let is_admin = page_ai_actor_is_admin(context); + ensure_page_ai_actor_user(state, &actor_id, is_admin)?; + let accesses = state + .control_plane() + .ensure_ai_agent_profile_policy(&actor_id, is_admin) + .map_err(|error| { + WebError::internal(format!("SQLite AI profile policy 解析失败: {error}")) + })?; + Ok(select_profile_access(accesses, requested)) +} + +fn resolve_hermes_profile_from_query( + state: &AppState, + context: &RequestContext, + query: &HashMap, +) -> Result, WebError> { + let requested = query + .get("profileId") + .or_else(|| query.get("profile_id")) + .or_else(|| query.get("profile")) + .map(String::as_str); + resolve_hermes_profile_access(state, context, requested) +} + +fn resolve_hermes_profile_from_payload( + state: &AppState, + context: &RequestContext, + payload: &Value, +) -> Result, WebError> { + let requested = payload + .get("profileId") + .or_else(|| payload.get("profile_id")) + .or_else(|| payload.get("profile")) + .and_then(Value::as_str); + resolve_hermes_profile_access(state, context, requested) +} + +fn stamp_agent_profile_ref(payload: &mut Value, access: &AiAgentProfileAccessRecord) { + payload["profile"] = Value::String(access.profile.isolated_profile_name.clone()); + payload["profileId"] = Value::String(access.profile.id.clone()); + payload["agentProfileRef"] = json!({ + "kind": access.profile.profile_kind, + "profileId": access.profile.id, + "ownerUserId": access.profile.owner_user_id, + "baseProfile": access.profile.base_profile_name, + "isolatedProfile": access.profile.isolated_profile_name, + "displayName": access.profile.display_name, + "canRun": access.grant.can_run, + "canManageSkills": access.grant.can_manage_skills, + "canManageConfig": access.grant.can_manage_config, + "readonly": !access.grant.can_manage_skills + }); +} + +fn payload_requests_hermes_profile(payload: &Value) -> bool { + let agent_id = payload + .get("agentId") + .and_then(Value::as_str) + .map(str::trim) + .unwrap_or_default(); + let acp_runtime = payload + .get("acpRuntime") + .and_then(Value::as_str) + .map(str::trim) + .unwrap_or_default(); + agent_id == "hermes" + || acp_runtime == "hermes" + || payload.get("profileId").is_some() + || payload.get("profile_id").is_some() + || payload.get("agentProfileRef").is_some() +} + +fn ai_preference_bool( + state: &AppState, + user_id: &str, + key: &str, + default_value: bool, +) -> Result { + let preferences = state + .control_plane() + .list_user_ui_preferences(user_id, None, None) + .map_err(|error| WebError::internal(format!("SQLite AI 偏好读取失败: {error}")))?; + for preference in preferences { + if preference.key == key { + return Ok(serde_json::from_str::(&preference.value_json) + .ok() + .and_then(|value| value.as_bool()) + .unwrap_or(default_value)); + } + } + Ok(default_value) +} + +fn apply_mnote_builtin_skill_policy_to_payload( + state: &AppState, + actor_id: &str, + payload: &mut Value, +) -> Result<(), WebError> { + let agent_id = payload + .get("agentId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()); + let mut policy = serde_json::Map::new(); + for skill in crate::hermes_tools::skill::skill_summaries_for_agent(agent_id) { + let Some(skill_id) = skill.get("id").and_then(Value::as_str) else { + continue; + }; + let key = format!("ai.agent.mnote_builtin.skill.{skill_id}.enabled"); + policy.insert( + skill_id.to_string(), + Value::Bool(ai_preference_bool(state, actor_id, &key, true)?), + ); + } + let skill_preferences = payload + .as_object_mut() + .expect("page ai run payload object") + .entry("skillPreferences") + .or_insert_with(|| json!({})); + if !skill_preferences.is_object() { + *skill_preferences = json!({}); + } + skill_preferences + .as_object_mut() + .expect("skillPreferences object") + .insert("mnote".to_string(), Value::Object(policy)); + Ok(()) +} + +fn reasonix_memory_enabled( + state: &AppState, + context: &RequestContext, + payload: &Value, +) -> Result { + let actor_id = payload + .get("actorId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty() && *value != "anonymous") + .map(ToOwned::to_owned) + .or_else(|| crate::routes::gateway::current_actor_id(state, context)) + .unwrap_or_else(|| context.auth.actor_id.trim().to_string()); + if actor_id.is_empty() || actor_id == "anonymous" { + return Ok(false); + } + ai_preference_bool(state, &actor_id, "ai.agent.reasonix.memory_enabled", false) +} + +fn reasonix_memory_env_for_payload( + state: &AppState, + context: &RequestContext, + payload: &Value, +) -> Result>, WebError> { + if reasonix_memory_enabled(state, context, payload)? { + return Ok(Some(HashMap::from([( + "REASONIX_MEMORY".to_string(), + "on".to_string(), + )]))); + } + Ok(Some(HashMap::from([( + "REASONIX_MEMORY".to_string(), + "off".to_string(), + )]))) +} + +fn stamp_hermes_skill_payload_profile_policy( + payload: &mut Value, + access: &AiAgentProfileAccessRecord, +) { + payload["profileId"] = Value::String(access.profile.id.clone()); + payload["agentProfileRef"] = agent_profile_access_json(access.clone()); + if let Some(categories) = payload.get_mut("categories").and_then(Value::as_array_mut) { + for category in categories { + if let Some(skills) = category.get_mut("skills").and_then(Value::as_array_mut) { + for skill in skills { + skill["profileId"] = Value::String(access.profile.id.clone()); + skill["skillKind"] = Value::String("hermes_profile".to_string()); + skill["builtin"] = Value::Bool(false); + skill["configurable"] = Value::Bool(access.grant.can_manage_skills); + skill["readonly"] = Value::Bool(!access.grant.can_manage_skills); + skill["configScope"] = Value::String( + if access.profile.profile_kind == "shared" { + "hermes_shared_profile" + } else { + "hermes_personal_profile" + } + .to_string(), + ); + } + } + } + } +} + +fn stamp_mnote_builtin_skill_payload_policy( + state: &AppState, + context: &RequestContext, + payload: &mut Value, +) -> Result<(), WebError> { + let actor_id = page_ai_actor_id(state, context)?; + if let Some(categories) = payload.get_mut("categories").and_then(Value::as_array_mut) { + for category in categories { + if let Some(skills) = category.get_mut("skills").and_then(Value::as_array_mut) { + for skill in skills { + let skill_id = skill + .get("id") + .or_else(|| skill.get("name")) + .and_then(Value::as_str) + .map(str::trim) + .unwrap_or_default(); + if skill_id.is_empty() { + continue; + } + let key = format!("ai.agent.mnote_builtin.skill.{skill_id}.enabled"); + skill["enabled"] = + Value::Bool(ai_preference_bool(state, &actor_id, &key, true)?); + skill["builtin"] = Value::Bool(true); + skill["configurable"] = Value::Bool(true); + skill["configScope"] = Value::String("user_sqlite".to_string()); + skill["skillKind"] = Value::String("mnote_builtin".to_string()); + } + } + } + } + Ok(()) +} + fn parse_profile_list(stdout: &str) -> Vec { let active = active_profile_name().unwrap_or_else(|| "default".into()); stdout @@ -2720,7 +3706,14 @@ fn skill_entry( } fn skills_payload(profile: &str) -> Value { - let skills_dir = hermes_home().join("skills"); + let profile_skills_dir = profile_home(profile).join("skills"); + let skills_dir = if profile_skills_dir.exists() + && !available_hermes_skill_names(&profile_skills_dir).is_empty() + { + profile_skills_dir + } else { + hermes_home().join("skills") + }; let disabled = disabled_skills(profile); let meta = SkillCatalogMeta::read(&skills_dir); let mut categories = Vec::new(); @@ -2933,6 +3926,10 @@ fn mnote_builtin_skills_payload(agent_id: Option<&str>) -> Value { "description": skill.get("description").cloned().unwrap_or(Value::Null), "enabled": true, "toggleable": true, + "builtin": true, + "configurable": true, + "configScope": "user_sqlite", + "skillKind": "mnote_builtin", "source": "mnote", "origin": "builtin", "agentIds": skill.get("agentIds").cloned().unwrap_or(Value::Null), @@ -2982,31 +3979,39 @@ fn merge_misc_categories(categories: Vec) -> Vec { fn set_skill_enabled(profile: &str, name: &str, enabled: bool) -> std::io::Result<()> { let path = profile_config_path(profile); let mut disabled = disabled_skills(profile); + let name = name.trim(); if enabled { - disabled.retain(|item| item != name); + disabled.retain(|item| item.trim() != name); } else if !disabled.iter().any(|item| item == name) { disabled.push(name.to_string()); } disabled.sort(); - let existing = fs::read_to_string(&path).unwrap_or_default(); + write_disabled_skills_config(&path, &disabled, false) +} + +fn write_disabled_skills_config( + path: &FsPath, + disabled: &[String], + mark_personal_baseline: bool, +) -> std::io::Result<()> { + let existing = fs::read_to_string(path).unwrap_or_default(); + let has_personal_baseline_marker = existing.contains(MNOTE_PERSONAL_SKILL_BASELINE_MARKER); let mut kept = Vec::new(); - let mut in_skills = false; - let mut in_disabled = false; + let mut skipping_skills = false; for line in existing.lines() { let trimmed = line.trim(); - if !line.starts_with(' ') && !trimmed.is_empty() { - in_skills = trimmed == "skills:"; - in_disabled = false; - } - if in_skills && trimmed == "disabled:" { - in_disabled = true; + if trimmed == MNOTE_PERSONAL_SKILL_BASELINE_MARKER { continue; } - if in_disabled { - if trimmed.starts_with("- ") || trimmed.is_empty() { + if skipping_skills { + if line.starts_with(' ') || trimmed.is_empty() { continue; } - in_disabled = false; + skipping_skills = false; + } + if !line.starts_with(' ') && trimmed == "skills:" { + skipping_skills = true; + continue; } kept.push(line.to_string()); } @@ -3017,12 +4022,18 @@ fn set_skill_enabled(profile: &str, name: &str, enabled: bool) -> std::io::Resul if !output.ends_with('\n') && !output.is_empty() { output.push('\n'); } - output.push_str("skills:\n disabled:\n"); - for skill in disabled { - output.push_str(" - "); - output.push_str(&skill); + if mark_personal_baseline || has_personal_baseline_marker { + output.push_str(MNOTE_PERSONAL_SKILL_BASELINE_MARKER); output.push('\n'); } + if !disabled.is_empty() { + output.push_str("skills:\n disabled:\n"); + for skill in disabled { + output.push_str(" - "); + output.push_str(&skill); + output.push('\n'); + } + } fs::write(path, output) } @@ -3726,7 +4737,14 @@ fn acp_allowed_roots_env_for_payload(payload: &Value) -> Option PageAiCapabilit profile: payload.get("profile").cloned().unwrap_or(Value::Null), allowed_roots: payload.get("allowedRoots").cloned().unwrap_or(Value::Null), ai_access_scope: page_ai_capability_scope_for_tools(payload), + agent_run_envelope: page_ai_capability_agent_run_envelope(payload), } } +fn page_ai_capability_agent_run_envelope(payload: &Value) -> Value { + let document_id = payload + .get("documentId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("current"); + let workspace_id = payload + .get("workspaceId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("default"); + let source_kind = payload + .get("sourceKind") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("convex_workspace"); + let root_uri = payload + .get("rootUri") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()); + let profile = payload + .get("profile") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("default"); + let actor_id = payload + .get("actorId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("anonymous"); + let actor_type = payload + .get("actorType") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("anonymous"); + let session_id = payload + .get("sessionId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("page-ai"); + let run_id = payload + .get("runId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or(session_id); + let trace_id = payload + .get("traceId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("page-ai"); + let editor_target = payload + .get("editorTarget") + .map(sanitize_run_editor_target) + .unwrap_or(Value::Null); + let run_target_snapshot = payload + .get("runTargetSnapshot") + .map(sanitize_run_target_snapshot) + .unwrap_or(Value::Null); + let agent_target_package = if source_kind == "local_folder" { + Some(build_local_agent_target_package( + payload, + document_id, + workspace_id, + source_kind, + root_uri, + &editor_target, + &run_target_snapshot, + )) + } else { + None + }; + build_agent_run_envelope( + payload, + document_id, + workspace_id, + source_kind, + root_uri, + profile, + actor_id, + actor_type, + session_id, + run_id, + trace_id, + &editor_target, + &run_target_snapshot, + agent_target_package.as_ref(), + source_kind == "local_folder", + ) +} + fn page_ai_skill_enabled_by_payload(payload: &Value, skill: &Value) -> bool { let Some(skill_id) = skill.get("id").and_then(Value::as_str) else { return true; @@ -4205,6 +5326,12 @@ fn page_ai_capability_scope_for_tools(payload: &Value) -> Value { .get("allowedRoots") .cloned() .unwrap_or_else(|| json!([])); + let allowed_resource_ids = local_agent_target_allowed_resource_ids( + payload + .get("targetPackage") + .or_else(|| payload.get("agentTargetPackage")), + document_id, + ); json!({ "permissionLevel": if allowed_roots.as_array().map(|roots| { roots.iter().any(|root| { @@ -4215,7 +5342,7 @@ fn page_ai_capability_scope_for_tools(payload: &Value) -> Value { }) }).unwrap_or(false) { "read_write" } else { "read" }, "allowedRoots": allowed_roots, - "allowedResourceIds": [document_id] + "allowedResourceIds": allowed_resource_ids }) } @@ -4303,6 +5430,9 @@ fn build_run_upstream_body(context: &RequestContext, payload: Value) -> Result Result Result Result Result Result Result Value { "schema", "source", "objectIdentity", + "targetId", "paneRole", "documentId", "workspaceId", "editorKind", + "resourceKind", "active", "dirtyState", "preview", @@ -4656,6 +5827,9 @@ fn sanitize_run_editor_target(value: &Value) -> Value { "lastActiveAt", "assetId", "path", + "onlyofficeSessionId", + "bridgeSessionId", + "bridgeSessionReady", ] { if let Some(value) = source.get(key) { sanitized.insert(key.to_string(), value.clone()); @@ -4876,6 +6050,433 @@ fn sanitize_context_refs(value: Option<&Value>) -> Value { Value::Array(refs) } +fn sanitize_relative_path(value: &str) -> Option { + let normalized = value + .trim() + .replace('\\', "/") + .trim_start_matches('/') + .to_string(); + if normalized.is_empty() + || normalized.starts_with('/') + || normalized + .split('/') + .any(|part| part.is_empty() || part == "..") + { + None + } else { + Some(normalized) + } +} + +fn target_workspace_path(value: &Value) -> Option<&Value> { + value.get("workspacePath").filter(|path| path.is_object()) +} + +fn target_relative_path(value: &Value) -> Option { + value + .get("currentFile") + .and_then(|file| file.get("relativePath")) + .and_then(Value::as_str) + .and_then(sanitize_relative_path) + .or_else(|| { + target_workspace_path(value) + .and_then(|path| path.get("relativePath")) + .and_then(Value::as_str) + .and_then(sanitize_relative_path) + }) + .or_else(|| { + value + .get("relativePath") + .and_then(Value::as_str) + .and_then(sanitize_relative_path) + }) +} + +fn local_agent_target_allowed_files(target_package: &Value) -> Vec { + let mut files = target_package + .get("allowedFiles") + .and_then(Value::as_array) + .map(|items| { + items + .iter() + .filter_map(Value::as_str) + .filter_map(sanitize_relative_path) + .collect::>() + }) + .unwrap_or_default(); + if let Some(relative_path) = target_relative_path(target_package) { + files.push(relative_path); + } + files.sort(); + files.dedup(); + files +} + +fn local_agent_target_allowed_file_paths(root_uri: &str, target_package: &Value) -> Vec { + let Ok(root) = local_ai_session_root_dir(root_uri) else { + return Vec::new(); + }; + let files = local_agent_target_allowed_files(target_package); + files + .into_iter() + .map(|relative_path| root.join(relative_path).to_string_lossy().to_string()) + .collect() +} + +fn push_local_agent_resource_id(ids: &mut Vec, value: Option<&Value>) { + let Some(value) = value.and_then(Value::as_str).map(str::trim) else { + return; + }; + if value.is_empty() { + return; + } + let owned = value.to_string(); + if !ids.contains(&owned) { + ids.push(owned); + } +} + +fn push_local_agent_resource_id_string(ids: &mut Vec, value: String) { + let normalized = value.trim(); + if normalized.is_empty() { + return; + } + let owned = normalized.to_string(); + if !ids.contains(&owned) { + ids.push(owned); + } +} + +fn push_local_agent_target_resource_ids(ids: &mut Vec, target: &Value) { + push_local_agent_resource_id(ids, target.get("targetId")); + push_local_agent_resource_id(ids, target.get("objectIdentity")); + push_local_agent_resource_id(ids, target.get("documentId")); + push_local_agent_resource_id(ids, target.get("assetId")); + push_local_agent_resource_id(ids, target.get("onlyofficeSessionId")); + push_local_agent_resource_id(ids, target.get("bridgeSessionId")); + if let Some(workspace_path) = target.get("workspacePath") { + push_local_agent_resource_id(ids, workspace_path.get("objectIdentity")); + push_local_agent_resource_id(ids, workspace_path.get("documentId")); + push_local_agent_resource_id(ids, workspace_path.get("assetId")); + } + if let Some(current_file) = target.get("currentFile") { + push_local_agent_resource_id(ids, current_file.get("objectIdentity")); + push_local_agent_resource_id(ids, current_file.get("documentId")); + push_local_agent_resource_id(ids, current_file.get("assetId")); + } + let document_id = target + .get("documentId") + .or_else(|| { + target + .get("workspacePath") + .and_then(|path| path.get("documentId")) + }) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()); + let asset_id = target + .get("assetId") + .or_else(|| { + target + .get("workspacePath") + .and_then(|path| path.get("assetId")) + }) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()); + if let (Some(document_id), Some(asset_id)) = (document_id, asset_id) { + push_local_agent_resource_id_string( + ids, + format!("resource:office:{document_id}:{asset_id}"), + ); + push_local_agent_resource_id_string( + ids, + format!("resource:onlyoffice:{document_id}:{asset_id}"), + ); + } +} + +fn local_agent_target_allowed_resource_ids( + target_package: Option<&Value>, + fallback_document_id: &str, +) -> Vec { + let mut ids = Vec::new(); + if let Some(target_package) = target_package { + push_local_agent_target_resource_ids(&mut ids, target_package); + if let Some(targets) = target_package.get("targets").and_then(Value::as_array) { + for target in targets { + push_local_agent_target_resource_ids(&mut ids, target); + } + } + } + if ids.is_empty() { + ids.push(fallback_document_id.to_string()); + } + ids +} + +fn build_local_agent_target_package( + payload: &Value, + document_id: &str, + workspace_id: &str, + source_kind: &str, + root_uri: Option<&str>, + editor_target: &Value, + run_target_snapshot: &Value, +) -> Value { + let payload_package = payload + .get("targetPackage") + .or_else(|| payload.get("agentTargetPackage")) + .map(sanitize_local_agent_target_package) + .filter(|value| !value.is_null()); + let target_package = payload_package.unwrap_or_else(|| { + derive_local_agent_target_package( + document_id, + workspace_id, + source_kind, + root_uri, + editor_target, + run_target_snapshot, + ) + }); + let allowed_files = local_agent_target_allowed_files(&target_package); + let mut next = target_package.as_object().cloned().unwrap_or_default(); + normalize_local_target_resource_kind(&mut next); + next.insert( + "allowedFiles".to_string(), + Value::Array(allowed_files.into_iter().map(Value::String).collect()), + ); + Value::Object(next) +} + +fn normalize_local_target_resource_kind(target: &mut serde_json::Map) { + let fallback_identity = target + .get("documentId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + normalize_local_target_object_identity(target, fallback_identity.as_deref()); + if matches!( + target.get("resourceKind").and_then(Value::as_str), + Some("page") | None + ) { + target.insert( + "resourceKind".to_string(), + Value::String("markdown_page".to_string()), + ); + } + for key in ["workspacePath", "currentFile"] { + if let Some(Value::Object(object)) = target.get_mut(key) { + normalize_local_target_object_identity(object, fallback_identity.as_deref()); + if matches!( + object.get("resourceKind").and_then(Value::as_str), + Some("page") | None + ) { + object.insert( + "resourceKind".to_string(), + Value::String("markdown_page".to_string()), + ); + } + } + } +} + +fn normalize_local_target_object_identity( + target: &mut serde_json::Map, + fallback_identity: Option<&str>, +) { + let current = target + .get("objectIdentity") + .and_then(Value::as_str) + .map(str::trim) + .unwrap_or_default(); + if (current.is_empty() || current == "page:primary") && fallback_identity.is_some() { + target.insert( + "objectIdentity".to_string(), + Value::String(fallback_identity.unwrap().to_string()), + ); + } +} + +fn sanitize_local_agent_target_package(value: &Value) -> Value { + let Some(source) = value.as_object() else { + return Value::Null; + }; + let mut sanitized = serde_json::Map::new(); + for key in [ + "schema", + "source", + "frozenAt", + "primaryTargetId", + "workspaceId", + "sourceKind", + "rootUri", + "documentId", + "objectIdentity", + "resourceKind", + "assetId", + "onlyofficeSessionId", + "bridgeSessionId", + ] { + if let Some(value) = source.get(key) { + sanitized.insert(key.to_string(), value.clone()); + } + } + if let Some(workspace_path) = source.get("workspacePath") { + let sanitized_path = sanitize_workspace_path(workspace_path); + if !sanitized_path.is_null() { + sanitized.insert("workspacePath".to_string(), sanitized_path); + } + } + if let Some(current_file) = source.get("currentFile").and_then(Value::as_object) { + let mut file = serde_json::Map::new(); + for key in [ + "rootUri", + "relativePath", + "documentId", + "objectIdentity", + "resourceKind", + "assetId", + "onlyofficeSessionId", + "bridgeSessionId", + ] { + if let Some(value) = current_file.get(key) { + file.insert(key.to_string(), value.clone()); + } + } + if !file.is_empty() { + sanitized.insert("currentFile".to_string(), Value::Object(file)); + } + } + if let Some(files) = source.get("allowedFiles").and_then(Value::as_array) { + let allowed = files + .iter() + .filter_map(Value::as_str) + .filter_map(sanitize_relative_path) + .map(Value::String) + .collect::>(); + sanitized.insert("allowedFiles".to_string(), Value::Array(allowed)); + } + if let Some(targets) = source.get("targets").and_then(Value::as_array) { + let sanitized_targets = targets + .iter() + .map(sanitize_local_agent_target_entry) + .filter(|value| !value.is_null()) + .collect::>(); + if !sanitized_targets.is_empty() { + sanitized.insert("targets".to_string(), Value::Array(sanitized_targets)); + } + } + if sanitized.is_empty() { + Value::Null + } else { + sanitized + .entry("schema".to_string()) + .or_insert_with(|| Value::String("mnote.agent_target_package.v1".to_string())); + Value::Object(sanitized) + } +} + +fn sanitize_local_agent_target_entry(value: &Value) -> Value { + let Some(source) = value.as_object() else { + return Value::Null; + }; + let mut sanitized = serde_json::Map::new(); + for key in [ + "targetId", + "objectIdentity", + "documentId", + "workspaceId", + "sourceKind", + "rootUri", + "relativePath", + "resourceKind", + "assetId", + "paneRole", + "title", + "onlyofficeSessionId", + "bridgeSessionId", + ] { + if let Some(value) = source.get(key) { + sanitized.insert(key.to_string(), value.clone()); + } + } + if let Some(workspace_path) = source.get("workspacePath") { + let sanitized_path = sanitize_workspace_path(workspace_path); + if !sanitized_path.is_null() { + sanitized.insert("workspacePath".to_string(), sanitized_path); + } + } + if sanitized.is_empty() { + Value::Null + } else { + Value::Object(sanitized) + } +} + +fn derive_local_agent_target_package( + document_id: &str, + workspace_id: &str, + source_kind: &str, + root_uri: Option<&str>, + editor_target: &Value, + run_target_snapshot: &Value, +) -> Value { + let target = if !editor_target.is_null() { + editor_target + } else { + run_target_snapshot + .get("editorTarget") + .unwrap_or(&Value::Null) + }; + let workspace_path = target_workspace_path(target) + .map(sanitize_workspace_path) + .filter(|value| !value.is_null()) + .unwrap_or_else(|| { + let relative_path = local_resource_id_to_relative_path(document_id).unwrap_or_default(); + json!({ + "schema": "mnote.workspace_path.v1", + "workspaceId": workspace_id, + "sourceKind": source_kind, + "rootUri": root_uri.map(Value::from).unwrap_or(Value::Null), + "relativePath": relative_path, + "documentId": document_id, + "objectIdentity": document_id, + "assetId": "", + "resourceKind": "markdown_page" + }) + }); + let relative_path = target_relative_path(&workspace_path) + .or_else(|| target_relative_path(target)) + .or_else(|| local_resource_id_to_relative_path(document_id)); + let object_identity = workspace_path + .get("objectIdentity") + .and_then(Value::as_str) + .filter(|value| !value.trim().is_empty()) + .unwrap_or(document_id) + .to_string(); + json!({ + "schema": "mnote.agent_target_package.v1", + "source": "server_derived", + "workspaceId": workspace_id, + "sourceKind": source_kind, + "rootUri": root_uri.map(Value::from).unwrap_or(Value::Null), + "documentId": document_id, + "objectIdentity": object_identity, + "resourceKind": workspace_path.get("resourceKind").cloned().unwrap_or_else(|| Value::String("markdown_page".to_string())), + "workspacePath": workspace_path, + "currentFile": relative_path.as_ref().map(|relative_path| json!({ + "rootUri": root_uri.unwrap_or_default(), + "relativePath": relative_path, + "documentId": document_id, + "objectIdentity": object_identity, + "resourceKind": "markdown_page" + })).unwrap_or(Value::Null), + "allowedFiles": relative_path.into_iter().collect::>() + }) +} + fn sanitize_payload_allowed_roots(value: Option<&Value>) -> Value { let roots = value .and_then(Value::as_array) @@ -4963,6 +6564,7 @@ fn build_agent_run_envelope( trace_id: &str, editor_target: &Value, run_target_snapshot: &Value, + agent_target_package: Option<&Value>, is_local_source: bool, ) -> Value { let agent_id = payload @@ -4977,6 +6579,7 @@ fn build_agent_run_envelope( .map(str::trim) .filter(|value| !value.is_empty()) .unwrap_or(profile); + let target_package = agent_target_package.cloned().unwrap_or(Value::Null); json!({ "schema": "mnote.agent_run_envelope.v1", "agentId": agent_id, @@ -4993,6 +6596,11 @@ fn build_agent_run_envelope( "rootUri": root_uri.map(Value::from).unwrap_or(Value::Null), "contextRefs": sanitize_context_refs(payload.get("contextRefs")), "allowedRoots": build_agent_run_allowed_roots(payload, root_uri, is_local_source), + "allowedFiles": target_package + .get("allowedFiles") + .cloned() + .unwrap_or_else(|| Value::Array(Vec::new())), + "targetPackage": target_package, "primaryTarget": build_agent_run_primary_target( document_id, workspace_id, @@ -5232,6 +6840,579 @@ fn json_string(value: &Value) -> String { serde_json::to_string(value).unwrap_or_else(|_| "{}".to_string()) } +#[derive(Debug, Clone, Copy)] +struct ChatonlyProviderConfig { + provider: &'static str, + schema: &'static str, + profiles: &'static [&'static str], + base_profiles: &'static [&'static str], + isolated_profiles: &'static [&'static str], + profile_ids: &'static [&'static str], + delete_url_env: &'static str, + lookup_url_env: &'static str, + default_delete_url: &'static str, + default_lookup_url: &'static str, +} + +const CHATONLY_PROVIDER_CONFIGS: &[ChatonlyProviderConfig] = &[ + ChatonlyProviderConfig { + provider: "doubao-web", + schema: "mnote.provider_chat_context.v1", + profiles: &["openclaw-doubao-chat", "doubao-chat"], + base_profiles: &["doubao-chat"], + isolated_profiles: &["openclaw-doubao-chat"], + profile_ids: &["shared_doubao_chat"], + delete_url_env: ENV_DOUBAO_CONVERSATION_DELETE_URL, + lookup_url_env: ENV_DOUBAO_CONVERSATION_LOOKUP_URL, + default_delete_url: DEFAULT_DOUBAO_CONVERSATION_DELETE_URL, + default_lookup_url: DEFAULT_DOUBAO_CONVERSATION_LOOKUP_URL, + }, + ChatonlyProviderConfig { + provider: "deepseek-web", + schema: "mnote.provider_chat_context.v1", + profiles: &["openclaw-deepseek-chat", "deepseek-chat"], + base_profiles: &["deepseek-chat"], + isolated_profiles: &["openclaw-deepseek-chat"], + profile_ids: &["shared_deepseek_chat"], + delete_url_env: ENV_DEEPSEEK_CONVERSATION_DELETE_URL, + lookup_url_env: ENV_DEEPSEEK_CONVERSATION_LOOKUP_URL, + default_delete_url: DEFAULT_DEEPSEEK_CONVERSATION_DELETE_URL, + default_lookup_url: DEFAULT_DEEPSEEK_CONVERSATION_LOOKUP_URL, + }, + ChatonlyProviderConfig { + provider: "gemini-web", + schema: "mnote.provider_chat_context.v1", + profiles: &["openclaw-gemini-chat", "gemini-chat"], + base_profiles: &["gemini-chat"], + isolated_profiles: &["openclaw-gemini-chat"], + profile_ids: &["shared_gemini_chat"], + delete_url_env: ENV_GEMINI_CONVERSATION_DELETE_URL, + lookup_url_env: ENV_GEMINI_CONVERSATION_LOOKUP_URL, + default_delete_url: DEFAULT_GEMINI_CONVERSATION_DELETE_URL, + default_lookup_url: DEFAULT_GEMINI_CONVERSATION_LOOKUP_URL, + }, +]; + +fn chatonly_provider_by_name(provider: &str) -> Option<&'static ChatonlyProviderConfig> { + let provider = provider.trim(); + CHATONLY_PROVIDER_CONFIGS + .iter() + .find(|config| config.provider == provider) +} + +fn chatonly_profile_matches(value: Option<&str>, candidates: &[&str]) -> bool { + let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) else { + return false; + }; + candidates.iter().any(|candidate| *candidate == value) +} + +fn chatonly_provider_for_run( + payload: &Value, + registration: &HermesRunRegistration, +) -> Option<&'static ChatonlyProviderConfig> { + let agent_id = payload + .get("agentId") + .and_then(Value::as_str) + .map(str::trim) + .unwrap_or_default(); + if agent_id != "chat_only" { + return None; + } + let profile = registration.profile.trim(); + let payload_profile = payload.get("profile").and_then(Value::as_str); + let agent_profile = payload.get("agentProfileRef"); + let base_profile = agent_profile + .and_then(|value| value.get("baseProfile")) + .and_then(Value::as_str); + let isolated_profile = agent_profile + .and_then(|value| value.get("isolatedProfile")) + .and_then(Value::as_str); + let profile_id = payload.get("profileId").and_then(Value::as_str); + CHATONLY_PROVIDER_CONFIGS.iter().find(|config| { + chatonly_profile_matches(Some(profile), config.profiles) + || chatonly_profile_matches(payload_profile, config.profiles) + || chatonly_profile_matches(base_profile, config.base_profiles) + || chatonly_profile_matches(isolated_profile, config.isolated_profiles) + || chatonly_profile_matches(profile_id, config.profile_ids) + }) +} + +fn provider_default_remote_url(provider: &str, remote_conversation_id: &str) -> Option { + let id = remote_conversation_id.trim(); + if id.is_empty() { + return None; + } + if id.starts_with("http://") || id.starts_with("https://") { + return Some(id.to_string()); + } + match provider { + "doubao-web" => Some(format!("https://www.doubao.com/chat/{id}")), + "deepseek-web" => Some(format!("https://chat.deepseek.com/a/chat/s/{id}")), + "gemini-web" => Some(format!("https://gemini.google.com/app/{id}")), + _ => None, + } +} + +fn chatonly_provider_prompt_text( + input: &str, + payload: &Value, + registration: &HermesRunRegistration, + mnote_session_id: &str, + run_id: &str, +) -> String { + let Some(config) = chatonly_provider_for_run(payload, registration) else { + return input.to_string(); + }; + let metadata = json!({ + "schema": config.schema, + "provider": config.provider, + "mnoteSessionId": mnote_session_id, + "mnoteRunId": run_id, + "actorId": payload.get("actorId").and_then(Value::as_str).unwrap_or_default(), + "workspaceId": payload.get("workspaceId").and_then(Value::as_str).unwrap_or_default(), + "providerConversation": payload.get("providerConversation").cloned().unwrap_or(Value::Null) + }); + format!( + "Conversation info (untrusted metadata):\n```json\n{}\n```\n\n{}", + json_string(&metadata), + input + ) +} + +fn payload_has_remote_provider_conversation(payload: &Value) -> bool { + payload + .get("providerConversation") + .and_then(|value| value.get("remoteConversationId")) + .and_then(Value::as_str) + .map(str::trim) + .is_some_and(|value| !value.is_empty()) +} + +fn inject_provider_conversation_binding_for_run( + store: &dyn control_plane::ControlPlaneStore, + context: &RequestContext, + registration: &HermesRunRegistration, + payload: &mut Value, +) -> Result<(), WebError> { + let Some(config) = chatonly_provider_for_run(payload, registration) else { + return Ok(()); + }; + if payload_has_remote_provider_conversation(payload) { + return Ok(()); + } + let user_id = runtime_store_user_id(context, payload); + let workspace_id = payload + .get("workspaceId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .or(context.workspace.workspace_id.as_deref()); + let Some(binding) = store + .find_ai_external_conversation_binding( + &user_id, + workspace_id, + ®istration.session_id, + config.provider, + ) + .map_err(|error| { + WebError::internal(format!("SQLite provider 会话绑定读取失败: {error}")) + .with_context(context) + })? + else { + return Ok(()); + }; + if binding.status != "active" || binding.remote_conversation_id.trim().is_empty() { + return Ok(()); + } + payload["providerConversation"] = json!({ + "provider": binding.provider, + "remoteConversationId": binding.remote_conversation_id, + "remoteUrl": binding.remote_url + }); + Ok(()) +} + +fn persist_provider_conversation_event( + state: &AppState, + context: &RequestContext, + registration: &HermesRunRegistration, + event_type: &str, + event_payload: &Value, + run_payload: &Value, +) -> Result<(), WebError> { + if event_type != "provider.conversation.bound" { + return Ok(()); + } + let payload = event_payload.get("data").unwrap_or(event_payload); + let provider = payload + .get("provider") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .or_else(|| { + chatonly_provider_for_run(run_payload, registration).map(|config| config.provider) + }) + .unwrap_or("doubao-web"); + if chatonly_provider_by_name(provider).is_none() { + return Ok(()); + } + let Some(remote_conversation_id) = payload + .get("remoteConversationId") + .or_else(|| payload.get("remote_conversation_id")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + else { + return Ok(()); + }; + let user_id = runtime_store_user_id(context, run_payload); + let workspace_id = run_payload + .get("workspaceId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) + .or_else(|| context.workspace.workspace_id.clone()); + let remote_url = payload + .get("remoteUrl") + .or_else(|| payload.get("remote_url")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) + .or_else(|| provider_default_remote_url(provider, remote_conversation_id)); + let acp_session_id = payload + .get("acpSessionId") + .or_else(|| payload.get("acp_session_id")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + let agent_id = run_payload + .get("agentId") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("chat_only") + .to_string(); + state + .control_plane() + .upsert_ai_external_conversation_binding(UpsertAiExternalConversationBindingInput { + id: None, + user_id, + workspace_id, + mnote_session_id: registration.session_id.clone(), + acp_session_id, + agent_id, + profile: registration.profile.clone(), + provider: provider.to_string(), + remote_conversation_id: remote_conversation_id.to_string(), + remote_url, + status: "active".to_string(), + metadata_json: json_string(&json!({ + "source": event_type, + "payload": event_payload + })), + }) + .map(|_| ()) + .map_err(|error| { + WebError::internal(format!( + "SQLite provider conversation 绑定写入失败: {error}" + )) + .with_context(context) + }) +} + +fn mark_provider_conversation_local_deleted_for_session( + store: &dyn control_plane::ControlPlaneStore, + user_id: &str, + workspace_id: Option<&str>, + session_id: &str, +) -> Result { + let metadata = json_string(&json!({ + "reason": "mnote_session_deleted", + "updatedAt": now_ms() + })); + let mut changed = 0; + for config in CHATONLY_PROVIDER_CONFIGS { + changed += store.mark_ai_external_conversation_binding_status( + user_id, + workspace_id, + session_id, + config.provider, + "local_deleted", + Some(&metadata), + )?; + } + Ok(changed) +} + +fn find_provider_conversation_binding_for_session( + store: &dyn control_plane::ControlPlaneStore, + user_id: &str, + workspace_id: Option<&str>, + session_id: &str, +) -> Result, control_plane::ControlPlaneError> { + for config in CHATONLY_PROVIDER_CONFIGS { + let binding = store.find_ai_external_conversation_binding( + user_id, + workspace_id, + session_id, + config.provider, + )?; + if binding.is_some() { + return Ok(binding); + } + } + Ok(None) +} + +fn provider_conversation_delete_url( + config: &ChatonlyProviderConfig, + conversation_id: &str, +) -> String { + let template = env_or_dotenv(config.delete_url_env) + .unwrap_or_else(|| config.default_delete_url.to_string()); + if template.contains("{conversationId}") { + return template.replace("{conversationId}", conversation_id); + } + format!( + "{}/{}", + template.trim_end_matches('/'), + conversation_id.trim_start_matches('/') + ) +} + +fn provider_conversation_lookup_url(config: &ChatonlyProviderConfig, session_id: &str) -> String { + let template = env_or_dotenv(config.lookup_url_env) + .unwrap_or_else(|| config.default_lookup_url.to_string()); + if template.contains("{sessionId}") { + return template.replace("{sessionId}", session_id); + } + format!( + "{}/{}", + template.trim_end_matches('/'), + session_id.trim_start_matches('/') + ) +} + +async fn request_provider_conversation_lookup( + config: &ChatonlyProviderConfig, + session_id: &str, +) -> Result, WebError> { + if session_id.trim().is_empty() { + return Ok(None); + } + let url = provider_conversation_lookup_url(config, session_id); + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(10)) + .build() + .map_err(|error| { + WebError::internal(format!("Provider 会话绑定查询 client 创建失败: {error}")) + })?; + let response = client.get(&url).send().await.map_err(|error| { + WebError::bad_gateway_code( + "provider_conversation_lookup_failed", + format!("Provider 会话绑定查询失败: {error}"), + ) + })?; + if response.status() == StatusCode::NOT_FOUND { + return Ok(None); + } + if !response.status().is_success() { + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + return Err(WebError::bad_gateway_code( + "provider_conversation_lookup_http_failed", + format!( + "Provider 会话绑定查询返回 HTTP {}: {}", + status.as_u16(), + body + ), + )); + } + let payload = response.json::().await.map_err(|error| { + WebError::bad_gateway_code( + "provider_conversation_lookup_json_failed", + format!("Provider 会话绑定查询 JSON 解析失败: {error}"), + ) + })?; + let has_remote_id = payload + .get("remoteConversationId") + .or_else(|| payload.get("remote_conversation_id")) + .and_then(Value::as_str) + .map(str::trim) + .is_some_and(|value| !value.is_empty()); + Ok(has_remote_id.then_some(payload)) +} + +async fn persist_provider_conversation_from_proxy( + state: &AppState, + context: &RequestContext, + registration: &HermesRunRegistration, + run_id: &str, + acp_runtime: &str, + acp_session_id: &str, + run_payload: &Value, +) -> Result<(), WebError> { + let Some(config) = chatonly_provider_for_run(run_payload, registration) else { + return Ok(()); + }; + let Some(mut provider_payload) = + request_provider_conversation_lookup(config, ®istration.session_id).await? + else { + return Ok(()); + }; + if provider_payload.get("provider").is_none() { + provider_payload["provider"] = Value::String(config.provider.to_string()); + } + if provider_payload.get("acpSessionId").is_none() && !acp_session_id.trim().is_empty() { + provider_payload["acpSessionId"] = Value::String(acp_session_id.to_string()); + } + persist_acp_runtime_event( + state, + context, + registration, + run_id, + acp_runtime, + "provider.conversation.bound", + &provider_payload, + run_payload, + ) + .await + .map(|_| ()) +} + +async fn request_provider_conversation_delete( + binding: &AiExternalConversationBindingRecord, +) -> Value { + let Some(config) = chatonly_provider_by_name(&binding.provider) else { + return json!({ + "attempted": false, + "status": "remote_delete_failed", + "provider": binding.provider, + "remoteConversationId": binding.remote_conversation_id, + "error": "不支持的 provider" + }); + }; + let remote_conversation_id = binding.remote_conversation_id.trim(); + if remote_conversation_id.is_empty() { + return json!({ + "attempted": false, + "status": "remote_delete_failed", + "provider": binding.provider, + "error": "remoteConversationId 为空" + }); + } + if binding.status != "active" { + return json!({ + "attempted": false, + "status": binding.status, + "provider": binding.provider, + "remoteConversationId": remote_conversation_id + }); + } + + let url = provider_conversation_delete_url(config, remote_conversation_id); + let client = match reqwest::Client::builder() + .timeout(Duration::from_secs(30)) + .build() + { + Ok(client) => client, + Err(error) => { + return json!({ + "attempted": false, + "status": "remote_delete_failed", + "provider": binding.provider, + "remoteConversationId": remote_conversation_id, + "error": error.to_string() + }); + } + }; + match client + .post(&url) + .json(&json!({ + "provider": binding.provider, + "remoteConversationId": remote_conversation_id + })) + .send() + .await + { + Ok(response) if response.status().is_success() => { + let http_status = response.status().as_u16(); + let body = response.text().await.unwrap_or_default(); + let response_json = serde_json::from_str::(&body).unwrap_or_else(|_| { + json!({ + "raw": body + }) + }); + json!({ + "attempted": true, + "status": "remote_deleted", + "provider": binding.provider, + "remoteConversationId": remote_conversation_id, + "httpStatus": http_status, + "url": url, + "response": response_json + }) + } + Ok(response) => { + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + json!({ + "attempted": true, + "status": "remote_delete_failed", + "provider": binding.provider, + "remoteConversationId": remote_conversation_id, + "httpStatus": status.as_u16(), + "url": url, + "error": body + }) + } + Err(error) => json!({ + "attempted": true, + "status": "remote_delete_failed", + "provider": binding.provider, + "remoteConversationId": remote_conversation_id, + "url": url, + "error": error.to_string() + }), + } +} + +async fn delete_provider_conversation_for_session( + store: &dyn control_plane::ControlPlaneStore, + user_id: &str, + workspace_id: Option<&str>, + session_id: &str, + binding: Option<&AiExternalConversationBindingRecord>, +) -> Result { + let Some(binding) = binding else { + return Ok(Value::Null); + }; + let result = request_provider_conversation_delete(binding).await; + let status = result + .get("status") + .and_then(Value::as_str) + .unwrap_or("remote_delete_failed"); + if matches!(status, "remote_deleted" | "remote_delete_failed") { + store.mark_ai_external_conversation_binding_status( + user_id, + workspace_id, + session_id, + &binding.provider, + status, + Some(&json_string(&json!({ + "reason": "mnote_session_deleted", + "providerDelete": result, + "updatedAt": now_ms() + }))), + )?; + } + Ok(result) +} + fn ai_runtime_run_to_json(record: &control_plane::AiRuntimeRunRecord) -> Value { let runtime = serde_json::from_str::(&record.runtime_json).unwrap_or(Value::Null); let payload = serde_json::from_str::(&record.payload_json).unwrap_or(Value::Null); @@ -6010,6 +8191,8 @@ async fn persist_acp_runtime_run( runtime_state, payload, ); + let mut legacy_session_storage: Option = None; + let mut legacy_persistence: Option<&'static str> = None; if payload .get("sourceKind") .and_then(Value::as_str) @@ -6054,13 +8237,12 @@ async fn persist_acp_runtime_run( share_id.as_deref(), ) .map_err(|error| error.with_context(context))?; - return Ok(json!({ - "ok": true, - "persistence": "local_ai_session_jsonl", - "sessionStorage": if share_id.is_some() { "local_shared" } else { "local_private" }, - "sessionId": registration.session_id, - "runId": run_id - })); + legacy_persistence = Some("local_ai_session_jsonl"); + legacy_session_storage = Some(if share_id.is_some() { + "local_shared".to_string() + } else { + "local_private".to_string() + }); } let workspace_id = args .get("workspaceId") @@ -6110,6 +8292,8 @@ async fn persist_acp_runtime_run( "ok": true, "persistence": ACP_RUNTIME_SQLITE_STORE, "sessionStorage": "sqlite_control_plane", + "legacyPersistence": legacy_persistence, + "legacySessionStorage": legacy_session_storage, "sessionId": record.session_id, "runId": record.run_id })) @@ -6173,6 +8357,8 @@ async fn persist_acp_runtime_event( event_payload, run_payload, ); + let mut legacy_session_storage: Option = None; + let mut legacy_persistence: Option<&'static str> = None; if run_payload .get("sourceKind") .and_then(Value::as_str) @@ -6216,13 +8402,12 @@ async fn persist_acp_runtime_event( share_id.as_deref(), ) .map_err(|error| error.with_context(context))?; - return Ok(json!({ - "ok": true, - "persistence": "local_ai_session_jsonl", - "sessionStorage": if share_id.is_some() { "local_shared" } else { "local_private" }, - "sessionId": registration.session_id, - "runId": run_id - })); + legacy_persistence = Some("local_ai_session_jsonl"); + legacy_session_storage = Some(if share_id.is_some() { + "local_shared".to_string() + } else { + "local_private".to_string() + }); } state .control_plane() @@ -6252,10 +8437,20 @@ async fn persist_acp_runtime_event( WebError::internal(format!("SQLite ACP runtime event 写入失败: {error}")) .with_context(context) })?; + persist_provider_conversation_event( + state, + context, + registration, + event_type, + event_payload, + run_payload, + )?; Ok(json!({ "ok": true, "persistence": ACP_RUNTIME_SQLITE_STORE, "sessionStorage": "sqlite_control_plane", + "legacyPersistence": legacy_persistence, + "legacySessionStorage": legacy_session_storage, "sessionId": registration.session_id, "runId": run_id })) @@ -6908,7 +9103,10 @@ mod tests { use axum::body::{to_bytes, Body}; use axum::http::Request; use axum::routing::{get, post}; - use control_plane::{DirectoryGrantInput, UpsertAiRuntimeRunInput, UpsertUserInput}; + use control_plane::{ + DirectoryGrantInput, UpsertAiExternalConversationBindingInput, UpsertAiRuntimeRunInput, + UpsertUserInput, + }; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, OnceLock}; use tower::util::ServiceExt; @@ -6935,6 +9133,410 @@ mod tests { fn clear_acp_run_payloads() { ACP_RUN_PAYLOADS.lock().expect("acp run payloads").clear(); + ACP_FINISHED_RUNS.lock().expect("acp finished runs").clear(); + } + + #[tokio::test] + async fn page_ai_agent_profiles_are_sqlite_user_scoped() { + let response = app() + .oneshot( + Request::builder() + .method("GET") + .uri("/api/ai/agent-profiles?agentId=hermes") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("response"); + assert_eq!(response.status(), StatusCode::OK); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + let profiles = payload["profiles"].as_array().expect("profiles"); + assert!(profiles.iter().any(|profile| profile["kind"] == "personal" + && profile["canManageSkills"] == true + && profile["ownerUserId"] == "user_1")); + let shared = profiles + .iter() + .find(|profile| profile["profileId"] == "shared_lite") + .expect("shared lite"); + assert_eq!(shared["kind"], "shared"); + assert_eq!(shared["canRun"], true); + assert_eq!(shared["canManageSkills"], false); + } + + #[tokio::test] + async fn page_ai_skill_toggle_respects_builtin_user_policy_and_shared_readonly() { + let _env_guard = env_lock().lock().expect("env lock"); + let hermes_home = std::env::temp_dir().join(format!( + "mnote-web-ai-profile-policy-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&hermes_home); + fs::create_dir_all(&hermes_home).expect("hermes home"); + for skill in [ + "global-search", + "officecli", + "vpn", + "writer", + "zhihu-search", + ] { + let skill_dir = hermes_home.join("skills").join(skill); + fs::create_dir_all(&skill_dir).expect("skill dir"); + fs::write( + skill_dir.join("SKILL.md"), + format!("---\ndescription: {skill}\n---\n"), + ) + .expect("skill"); + } + std::env::set_var("HERMES_HOME", &hermes_home); + let app = build_app(test_state()); + + let disable_builtin = app + .clone() + .oneshot( + Request::builder() + .method("PUT") + .uri("/api/hermes/client/skills/toggle") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "skillKind": "mnote_builtin", + "name": "mnote-current-page", + "enabled": false + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("disable builtin"); + assert_eq!(disable_builtin.status(), StatusCode::OK); + + let mnote_skills = app + .clone() + .oneshot( + Request::builder() + .method("GET") + .uri("/api/hermes/client/skills?runtime=mnote&agentId=hermes") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("mnote skills"); + assert_eq!(mnote_skills.status(), StatusCode::OK); + let body = to_bytes(mnote_skills.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + let current_page = payload["categories"] + .as_array() + .expect("categories") + .iter() + .flat_map(|category| category["skills"].as_array().into_iter().flatten()) + .find(|skill| skill["id"] == "mnote-current-page") + .expect("current page skill"); + assert_eq!(current_page["enabled"], false); + assert_eq!(current_page["configScope"], "user_sqlite"); + let mindmap_skill = payload["categories"] + .as_array() + .expect("categories") + .iter() + .flat_map(|category| category["skills"].as_array().into_iter().flatten()) + .find(|skill| skill["id"] == "mnote-mindmap") + .expect("mindmap skill"); + assert_eq!(mindmap_skill["enabled"], true); + assert!(mindmap_skill["toolNames"] + .as_array() + .expect("mindmap tool names") + .iter() + .any(|name| name == "mnote.mindmap.create_from_outline")); + + let shared_toggle = app + .clone() + .oneshot( + Request::builder() + .method("PUT") + .uri("/api/hermes/client/skills/toggle") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "profileId": "shared_lite", + "skillKind": "hermes_profile", + "name": "writer", + "enabled": false + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("shared toggle"); + assert_eq!(shared_toggle.status(), StatusCode::FORBIDDEN); + + let profiles = app + .clone() + .oneshot( + Request::builder() + .method("GET") + .uri("/api/ai/agent-profiles?agentId=hermes") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("profiles"); + let body = to_bytes(profiles.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("profiles json"); + let personal_id = payload["profiles"] + .as_array() + .expect("profiles") + .iter() + .find(|profile| profile["kind"] == "personal") + .and_then(|profile| profile["profileId"].as_str()) + .expect("personal profile") + .to_string(); + + for legacy_profile in ["mnoteai", "%E6%88%91%E7%9A%84%20Hermes"] { + let legacy_skills = app + .clone() + .oneshot( + Request::builder() + .method("GET") + .uri(format!( + "/api/hermes/client/skills?runtime=hermes&profileId={}", + legacy_profile + )) + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("legacy skills"); + assert_eq!(legacy_skills.status(), StatusCode::OK); + } + + let personal_skills = app + .clone() + .oneshot( + Request::builder() + .method("GET") + .uri(format!( + "/api/hermes/client/skills?runtime=hermes&profileId={personal_id}" + )) + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("personal skills"); + assert_eq!(personal_skills.status(), StatusCode::OK); + let body = to_bytes(personal_skills.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("personal skills json"); + let skills = payload["categories"] + .as_array() + .expect("categories") + .iter() + .flat_map(|category| category["skills"].as_array().into_iter().flatten()) + .map(|skill| { + ( + skill["name"].as_str().unwrap_or_default().to_string(), + skill["enabled"].as_bool().unwrap_or(false), + ) + }) + .collect::>(); + assert_eq!(skills.get("global-search"), Some(&true)); + assert_eq!(skills.get("vpn"), Some(&true)); + assert_eq!(skills.get("zhihu-search"), Some(&true)); + assert_eq!(skills.get("officecli"), None); + assert_eq!(skills.get("writer"), None); + assert_eq!(skills.len(), 3); + + let personal_config = fs::read_to_string( + hermes_home + .join("profiles") + .join("mnote-u-user-1-default") + .join("config.yaml"), + ) + .expect("personal config"); + assert!(personal_config.contains(MNOTE_PERSONAL_SKILL_BASELINE_MARKER)); + assert!( + !personal_config.contains("writer"), + "初始模板范围不应写入 skills.disabled" + ); + assert!( + !personal_config.contains("officecli"), + "初始模板范围不应写入 skills.disabled" + ); + + let personal_skill_dir = hermes_home + .join("profiles") + .join("mnote-u-user-1-default") + .join("skills"); + assert!(personal_skill_dir + .join("global-search") + .join("SKILL.md") + .exists()); + assert!(personal_skill_dir.join("vpn").join("SKILL.md").exists()); + assert!(personal_skill_dir + .join("zhihu-search") + .join("SKILL.md") + .exists()); + assert!(!personal_skill_dir.join("writer").join("SKILL.md").exists()); + fs::create_dir_all(personal_skill_dir.join("writer")).expect("writer dir"); + fs::write( + personal_skill_dir.join("writer").join("SKILL.md"), + "---\ndescription: writer\n---\n", + ) + .expect("profile writer skill"); + + let hidden_enable = app + .clone() + .oneshot( + Request::builder() + .method("PUT") + .uri("/api/hermes/client/skills/toggle") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "profileId": personal_id, + "skillKind": "hermes_profile", + "name": "writer", + "enabled": true + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("hidden skill enable"); + assert_eq!(hidden_enable.status(), StatusCode::OK); + + let customized_skills = app + .oneshot( + Request::builder() + .method("GET") + .uri(format!( + "/api/hermes/client/skills?runtime=hermes&profileId={personal_id}" + )) + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("customized skills"); + assert_eq!(customized_skills.status(), StatusCode::OK); + let body = to_bytes(customized_skills.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("customized skills json"); + let skills = payload["categories"] + .as_array() + .expect("categories") + .iter() + .flat_map(|category| category["skills"].as_array().into_iter().flatten()) + .map(|skill| { + ( + skill["name"].as_str().unwrap_or_default().to_string(), + skill["enabled"].as_bool().unwrap_or(false), + ) + }) + .collect::>(); + assert_eq!(skills.get("writer"), Some(&true)); + assert_eq!(skills.len(), 4); + + std::env::remove_var("HERMES_HOME"); + let _ = fs::remove_dir_all(&hermes_home); + } + + #[test] + fn reasonix_memory_policy_defaults_off_and_reads_user_preference() { + let state = test_state(); + state + .control_plane() + .upsert_user(UpsertUserInput { + id: Some("user_1".to_string()), + email: None, + username: "user_1".to_string(), + display_name: "user_1".to_string(), + role: None, + password_hash: None, + }) + .expect("user"); + let context = RequestContext { + trace: crate::context::TraceContext { + request_id: "req_reasonix_memory".into(), + trace_id: "trace_reasonix_memory".into(), + method: "POST".into(), + path: "/api/hermes/client/runs".into(), + }, + auth: crate::context::AuthContext { + authorization: None, + cookie_header: None, + actor_id: "user_1".into(), + actor_type: "user".into(), + session_id: None, + }, + workspace: crate::context::WorkspaceContext { + workspace_id: None, + tenant_id: None, + deployment_id: None, + project_id: None, + }, + source: crate::context::SourceContext { + channel: "test".into(), + client: "mnote-web-test".into(), + idempotency_key: None, + }, + }; + let payload = json!({"actorId": "user_1", "agentId": "reasonix"}); + let off_env = reasonix_memory_env_for_payload(&state, &context, &payload) + .expect("default memory env") + .expect("env"); + assert_eq!( + off_env.get("REASONIX_MEMORY").map(String::as_str), + Some("off") + ); + + state + .control_plane() + .upsert_user_ui_preference(control_plane::UpsertUserUiPreferenceInput { + id: None, + user_id: "user_1".to_string(), + workspace_id: None, + source_kind: None, + scope_kind: "page_ai_agent".to_string(), + scope_id: "reasonix".to_string(), + key: "ai.agent.reasonix.memory_enabled".to_string(), + value_json: "true".to_string(), + }) + .expect("memory preference"); + let on_env = reasonix_memory_env_for_payload(&state, &context, &payload) + .expect("enabled memory env") + .expect("env"); + assert_eq!( + on_env.get("REASONIX_MEMORY").map(String::as_str), + Some("on") + ); } #[test] @@ -6975,6 +9577,18 @@ mod tests { json["aiAccessScope"]["allowedRoots"][0]["rootUri"], "file:///mnt/Data1T/mnote" ); + assert_eq!( + json["agentRunEnvelope"]["schema"], + "mnote.agent_run_envelope.v1" + ); + assert_eq!( + json["agentRunEnvelope"]["primaryTarget"]["documentId"], + "local-md:Current.md" + ); + assert_eq!( + json["agentRunEnvelope"]["resultPolicy"]["receiptSchema"], + "mnote.agent_run_receipt.v1" + ); assert!(json["availableSkills"] .as_array() .unwrap() @@ -7154,6 +9768,478 @@ mod tests { build_app(AppState::new(config)) } + #[test] + fn chatonly_doubao_run_payload_injects_existing_provider_conversation() { + let state = test_state(); + state + .control_plane() + .upsert_user(UpsertUserInput { + id: Some("user_1".to_string()), + email: Some("user_1@example.com".to_string()), + username: "user_1".to_string(), + display_name: "user_1".to_string(), + role: None, + password_hash: None, + }) + .expect("seed user"); + state + .control_plane() + .upsert_ai_external_conversation_binding(UpsertAiExternalConversationBindingInput { + id: None, + user_id: "user_1".to_string(), + workspace_id: Some("ws_1".to_string()), + mnote_session_id: "sess_doubao".to_string(), + acp_session_id: Some("acp_sess_doubao".to_string()), + agent_id: "chat_only".to_string(), + profile: "openclaw-doubao-chat".to_string(), + provider: "doubao-web".to_string(), + remote_conversation_id: "38428454119180290".to_string(), + remote_url: Some("https://www.doubao.com/chat/38428454119180290".to_string()), + status: "active".to_string(), + metadata_json: "{}".to_string(), + }) + .expect("seed binding"); + let headers = HeaderMap::from_iter([( + "x-mnote-actor-id".parse().expect("header name"), + "user_1".parse().expect("header value"), + )]); + let context = RequestContext::from_http_parts( + &axum::http::Method::POST, + &"/api/hermes/client/runs".parse().expect("uri"), + &headers, + ); + let mut payload = json!({ + "agentId": "chat_only", + "profile": "openclaw-doubao-chat", + "sessionId": "sess_doubao", + "workspaceId": "ws_1", + "message": "继续刚才的话题" + }); + let registration = run_registration_from_payload(&context, &payload); + + inject_provider_conversation_binding_for_run( + state.control_plane(), + &context, + ®istration, + &mut payload, + ) + .expect("inject provider conversation"); + + assert_eq!(payload["providerConversation"]["provider"], "doubao-web"); + assert_eq!( + payload["providerConversation"]["remoteConversationId"], + "38428454119180290" + ); + assert_eq!( + payload["providerConversation"]["remoteUrl"], + "https://www.doubao.com/chat/38428454119180290" + ); + } + + #[test] + fn chatonly_doubao_prompt_includes_strip_compatible_mnote_context() { + let payload = json!({ + "actorId": "user_1", + "agentId": "chat_only", + "profile": "openclaw-doubao-chat", + "profileId": "shared_doubao_chat", + "sessionId": "sess_doubao", + "workspaceId": "ws_1", + "providerConversation": { + "provider": "doubao-web", + "remoteConversationId": "38428454119180290" + } + }); + let registration = HermesRunRegistration { + session_id: "sess_doubao".to_string(), + profile: "openclaw-doubao-chat".to_string(), + document_id: "current".to_string(), + trace_id: "trace_1".to_string(), + }; + + let prompt = chatonly_provider_prompt_text( + "请只回复:MNOTE_DOUBAO", + &payload, + ®istration, + "sess_doubao", + "run_1", + ); + + assert!(prompt.starts_with("Conversation info (untrusted metadata):\n```json\n")); + assert!(prompt.contains("\"mnoteSessionId\":\"sess_doubao\"")); + assert!(prompt.contains("\"remoteConversationId\":\"38428454119180290\"")); + assert!(prompt.ends_with("请只回复:MNOTE_DOUBAO")); + } + + #[test] + fn chatonly_provider_prompt_supports_deepseek_and_gemini_context() { + let cases = [ + ( + "openclaw-deepseek-chat", + "shared_deepseek_chat", + "deepseek-web", + "ds-session-1", + ), + ( + "openclaw-gemini-chat", + "shared_gemini_chat", + "gemini-web", + "https://gemini.google.com/app/gem-session-1", + ), + ]; + for (profile, profile_id, provider, remote_conversation_id) in cases { + let payload = json!({ + "actorId": "user_1", + "agentId": "chat_only", + "profile": profile, + "profileId": profile_id, + "sessionId": "sess_provider", + "workspaceId": "ws_1", + "providerConversation": { + "provider": provider, + "remoteConversationId": remote_conversation_id + } + }); + let registration = HermesRunRegistration { + session_id: "sess_provider".to_string(), + profile: profile.to_string(), + document_id: "current".to_string(), + trace_id: "trace_1".to_string(), + }; + + let prompt = chatonly_provider_prompt_text( + "请只回复:MNOTE_PROVIDER", + &payload, + ®istration, + "sess_provider", + "run_1", + ); + + assert!(prompt.contains("\"schema\":\"mnote.provider_chat_context.v1\"")); + assert!(prompt.contains(&format!("\"provider\":\"{provider}\""))); + assert!(prompt.contains("\"mnoteSessionId\":\"sess_provider\"")); + assert!(prompt.contains(remote_conversation_id)); + assert!(prompt.ends_with("请只回复:MNOTE_PROVIDER")); + } + } + + #[tokio::test] + async fn provider_conversation_bound_event_persists_doubao_binding() { + let state = test_state(); + state + .control_plane() + .upsert_user(UpsertUserInput { + id: Some("user_1".to_string()), + email: Some("user_1@example.com".to_string()), + username: "user_1".to_string(), + display_name: "user_1".to_string(), + role: None, + password_hash: None, + }) + .expect("seed user"); + let headers = HeaderMap::from_iter([( + "x-mnote-actor-id".parse().expect("header name"), + "user_1".parse().expect("header value"), + )]); + let context = RequestContext::from_http_parts( + &axum::http::Method::POST, + &"/api/hermes/client/runs/run_1/events".parse().expect("uri"), + &headers, + ); + let registration = HermesRunRegistration { + session_id: "sess_doubao".to_string(), + profile: "openclaw-doubao-chat".to_string(), + document_id: "current".to_string(), + trace_id: "trace_1".to_string(), + }; + let run_payload = json!({ + "actorId": "user_1", + "agentId": "chat_only", + "workspaceId": "ws_1", + "sessionId": "sess_doubao", + "profile": "openclaw-doubao-chat" + }); + + persist_acp_runtime_event( + &state, + &context, + ®istration, + "run_1", + "reasonix", + "provider.conversation.bound", + &json!({ + "provider": "doubao-web", + "remoteConversationId": "38428454119180290", + "remoteUrl": "https://www.doubao.com/chat/38428454119180290", + "acpSessionId": "acp_sess_doubao" + }), + &run_payload, + ) + .await + .expect("persist event"); + + let binding = state + .control_plane() + .find_ai_external_conversation_binding( + "user_1", + Some("ws_1"), + "sess_doubao", + "doubao-web", + ) + .expect("find binding") + .expect("binding exists"); + assert_eq!(binding.status, "active"); + assert_eq!(binding.remote_conversation_id, "38428454119180290"); + assert_eq!(binding.acp_session_id.as_deref(), Some("acp_sess_doubao")); + } + + #[tokio::test] + async fn provider_conversation_bound_event_persists_gemini_binding() { + let state = test_state(); + state + .control_plane() + .upsert_user(UpsertUserInput { + id: Some("user_1".to_string()), + email: Some("user_1@example.com".to_string()), + username: "user_1".to_string(), + display_name: "user_1".to_string(), + role: None, + password_hash: None, + }) + .expect("seed user"); + let headers = HeaderMap::from_iter([( + "x-mnote-actor-id".parse().expect("header name"), + "user_1".parse().expect("header value"), + )]); + let context = RequestContext::from_http_parts( + &axum::http::Method::POST, + &"/api/hermes/client/runs/run_1/events".parse().expect("uri"), + &headers, + ); + let registration = HermesRunRegistration { + session_id: "sess_gemini".to_string(), + profile: "openclaw-gemini-chat".to_string(), + document_id: "current".to_string(), + trace_id: "trace_1".to_string(), + }; + let run_payload = json!({ + "actorId": "user_1", + "agentId": "chat_only", + "workspaceId": "ws_1", + "sessionId": "sess_gemini", + "profile": "openclaw-gemini-chat" + }); + + persist_acp_runtime_event( + &state, + &context, + ®istration, + "run_1", + "hermes", + "provider.conversation.bound", + &json!({ + "provider": "gemini-web", + "remoteConversationId": "https://gemini.google.com/app/gem-session-1", + "remoteUrl": "https://gemini.google.com/app/gem-session-1", + "acpSessionId": "acp_sess_gemini" + }), + &run_payload, + ) + .await + .expect("persist event"); + + let binding = state + .control_plane() + .find_ai_external_conversation_binding( + "user_1", + Some("ws_1"), + "sess_gemini", + "gemini-web", + ) + .expect("find binding") + .expect("binding exists"); + assert_eq!(binding.status, "active"); + assert_eq!( + binding.remote_conversation_id, + "https://gemini.google.com/app/gem-session-1" + ); + assert_eq!(binding.acp_session_id.as_deref(), Some("acp_sess_gemini")); + } + + #[test] + fn chatonly_doubao_session_delete_marks_binding_local_deleted() { + let state = test_state(); + state + .control_plane() + .upsert_user(UpsertUserInput { + id: Some("user_1".to_string()), + email: Some("user_1@example.com".to_string()), + username: "user_1".to_string(), + display_name: "user_1".to_string(), + role: None, + password_hash: None, + }) + .expect("seed user"); + state + .control_plane() + .upsert_ai_external_conversation_binding(UpsertAiExternalConversationBindingInput { + id: None, + user_id: "user_1".to_string(), + workspace_id: Some("ws_1".to_string()), + mnote_session_id: "sess_doubao".to_string(), + acp_session_id: None, + agent_id: "chat_only".to_string(), + profile: "openclaw-doubao-chat".to_string(), + provider: "doubao-web".to_string(), + remote_conversation_id: "38428454119180290".to_string(), + remote_url: Some("https://www.doubao.com/chat/38428454119180290".to_string()), + status: "active".to_string(), + metadata_json: "{}".to_string(), + }) + .expect("seed binding"); + + let changed = mark_provider_conversation_local_deleted_for_session( + state.control_plane(), + "user_1", + Some("ws_1"), + "sess_doubao", + ) + .expect("mark local deleted"); + assert_eq!(changed, 1); + + let binding = state + .control_plane() + .find_ai_external_conversation_binding( + "user_1", + Some("ws_1"), + "sess_doubao", + "doubao-web", + ) + .expect("find binding") + .expect("binding exists"); + assert_eq!(binding.status, "local_deleted"); + assert!(binding.deleted_at.is_some()); + } + + #[tokio::test] + async fn chatonly_doubao_session_delete_calls_provider_and_marks_remote_deleted() { + let _env_guard = env_lock().lock().expect("env lock"); + let calls = Arc::new(Mutex::new(Vec::::new())); + let mock_calls = Arc::clone(&calls); + let mock = axum::Router::new().route( + "/delete/{conversation_id}", + post(move |Path(conversation_id): Path| { + let mock_calls = Arc::clone(&mock_calls); + async move { + mock_calls + .lock() + .expect("mock delete calls") + .push(conversation_id); + Json(json!({ "ok": true })) + } + }), + ); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("bind mock delete endpoint"); + let addr = listener.local_addr().expect("mock addr"); + tokio::spawn(async move { + axum::serve(listener, mock) + .await + .expect("mock delete endpoint"); + }); + std::env::set_var( + "MNOTE_WEB_DOUBAO_CONVERSATION_DELETE_URL", + format!("http://{addr}/delete/{{conversationId}}"), + ); + + let state = test_state(); + state + .control_plane() + .upsert_user(UpsertUserInput { + id: Some("user_1".to_string()), + email: Some("user_1@example.com".to_string()), + username: "user_1".to_string(), + display_name: "user_1".to_string(), + role: None, + password_hash: None, + }) + .expect("seed user"); + state + .control_plane() + .upsert_ai_runtime_run(UpsertAiRuntimeRunInput { + id: None, + user_id: "user_1".to_string(), + workspace_id: Some("ws_1".to_string()), + document_id: Some("doc_1".to_string()), + session_id: "sess_doubao".to_string(), + run_id: "run_1".to_string(), + title: None, + profile: "openclaw-doubao-chat".to_string(), + acp_runtime: "hermes".to_string(), + trace_id: Some("trace_1".to_string()), + status: "completed".to_string(), + runtime_json: "{\"status\":\"completed\"}".to_string(), + payload_json: "{\"message\":\"hello\"}".to_string(), + }) + .expect("seed runtime run"); + state + .control_plane() + .upsert_ai_external_conversation_binding(UpsertAiExternalConversationBindingInput { + id: None, + user_id: "user_1".to_string(), + workspace_id: Some("ws_1".to_string()), + mnote_session_id: "sess_doubao".to_string(), + acp_session_id: None, + agent_id: "chat_only".to_string(), + profile: "openclaw-doubao-chat".to_string(), + provider: "doubao-web".to_string(), + remote_conversation_id: "38428454119180290".to_string(), + remote_url: Some("https://www.doubao.com/chat/38428454119180290".to_string()), + status: "active".to_string(), + metadata_json: "{}".to_string(), + }) + .expect("seed binding"); + + let response = build_app(state.clone()) + .oneshot( + Request::builder() + .method("DELETE") + .uri("/api/hermes/client/sessions/sess_doubao?workspaceId=ws_1") + .header("x-mnote-actor-id", "user_1") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("response"); + + assert_eq!(response.status(), StatusCode::OK); + let body: Value = + serde_json::from_slice(&to_bytes(response.into_body(), usize::MAX).await.unwrap()) + .expect("json body"); + assert_eq!( + body["result"]["providerConversationDelete"]["status"], + "remote_deleted" + ); + assert_eq!( + calls.lock().expect("mock calls").as_slice(), + ["38428454119180290"] + ); + + let binding = state + .control_plane() + .find_ai_external_conversation_binding( + "user_1", + Some("ws_1"), + "sess_doubao", + "doubao-web", + ) + .expect("find binding") + .expect("binding exists"); + assert_eq!(binding.status, "remote_deleted"); + std::env::remove_var("MNOTE_WEB_DOUBAO_CONVERSATION_DELETE_URL"); + } + fn seeded_acp_state() -> AppState { let state = AppState::new(AppConfig { service_name: "mnote-web".into(), @@ -7504,7 +10590,7 @@ mod tests { } #[tokio::test] - async fn hermes_client_local_acp_session_create_writes_private_jsonl() { + async fn hermes_client_local_acp_session_create_writes_sqlite_and_private_jsonl() { let root = std::env::temp_dir().join(format!( "mnote-local-ai-session-private-{}", std::process::id() @@ -7517,8 +10603,36 @@ mod tests { ) .expect("manifest"); let root_uri = format!("file://{}", root.display()); + let state = test_state(); + state + .control_plane() + .upsert_user(UpsertUserInput { + id: Some("user_1".into()), + email: Some("user_1@example.com".into()), + username: "user_1".into(), + display_name: "user_1".into(), + role: None, + password_hash: None, + }) + .expect("user"); + state + .control_plane() + .grant_directory_access(DirectoryGrantInput { + user_id: "user_1".into(), + workspace_id: None, + root_uri: root_uri.clone(), + root_path: root.display().to_string(), + permission: "write".into(), + recursive: true, + capabilities: vec!["local_files".into(), "ai_sessions".into()], + source: "test".into(), + created_by: Some("user_1".into()), + }) + .expect("grant write access"); - let response = app() + let app = build_app(state); + let response = app + .clone() .oneshot( Request::builder() .method("POST") @@ -7547,8 +10661,10 @@ mod tests { .await .expect("body"); let payload: Value = serde_json::from_slice(&body).expect("json"); - assert_eq!(payload["persistence"], "local_ai_session_jsonl"); - assert_eq!(payload["sessionStorage"], "local_private"); + assert_eq!(payload["persistence"], ACP_RUNTIME_SQLITE_STORE); + assert_eq!(payload["sessionStorage"], "sqlite_control_plane"); + assert_eq!(payload["legacyPersistence"], "local_ai_session_jsonl"); + assert_eq!(payload["legacySessionStorage"], "local_private"); let session_id = payload["sessionId"].as_str().expect("session id"); let jsonl_path = root .join("ai-sessions") @@ -7562,7 +10678,8 @@ mod tests { "/api/hermes/client/sessions?source=acp&sourceKind=local_folder&rootUri={}&workspaceId=local-ws-user-1&documentId=local-md%3AREADME.md", url_escape(&root_uri) ); - let list_response = app() + let list_response = app + .clone() .oneshot( Request::builder() .uri(list_uri) @@ -7578,7 +10695,8 @@ mod tests { .await .expect("list body"); let list_payload: Value = serde_json::from_slice(&list_body).expect("list json"); - assert_eq!(list_payload["persistence"], "local_ai_session_jsonl"); + assert_eq!(list_payload["persistence"], ACP_RUNTIME_SQLITE_STORE); + assert_eq!(list_payload["sessionStorage"], "sqlite_control_plane"); assert_eq!(list_payload["sessions"][0]["sessionId"], session_id); let detail_uri = format!( @@ -7586,7 +10704,8 @@ mod tests { session_id, url_escape(&root_uri) ); - let detail_response = app() + let detail_response = app + .clone() .oneshot( Request::builder() .uri(detail_uri) @@ -7602,14 +10721,17 @@ mod tests { .await .expect("detail body"); let detail_payload: Value = serde_json::from_slice(&detail_body).expect("detail json"); - assert_eq!(detail_payload["persistence"], "local_ai_session_jsonl"); - assert_eq!(detail_payload["events"][0]["eventType"], "session.created"); + assert_eq!(detail_payload["persistence"], ACP_RUNTIME_SQLITE_STORE); + assert_eq!( + detail_payload["session"]["runs"][0]["sessionId"], + session_id + ); let _ = fs::remove_dir_all(&root); } #[tokio::test] - async fn hermes_client_local_acp_run_writes_private_jsonl_without_convex() { + async fn hermes_client_local_acp_run_writes_sqlite_and_private_jsonl_without_convex() { let root = std::env::temp_dir().join(format!("mnote-local-ai-run-private-{}", std::process::id())); let _ = fs::remove_dir_all(&root); @@ -7620,8 +10742,34 @@ mod tests { ) .expect("manifest"); let root_uri = format!("file://{}", root.display()); + let state = test_state(); + state + .control_plane() + .upsert_user(UpsertUserInput { + id: Some("user_1".into()), + email: Some("user_1@example.com".into()), + username: "user_1".into(), + display_name: "user_1".into(), + role: None, + password_hash: None, + }) + .expect("user"); + state + .control_plane() + .grant_directory_access(DirectoryGrantInput { + user_id: "user_1".into(), + workspace_id: None, + root_uri: root_uri.clone(), + root_path: root.display().to_string(), + permission: "write".into(), + recursive: true, + capabilities: vec!["local_files".into(), "ai_sessions".into()], + source: "test".into(), + created_by: Some("user_1".into()), + }) + .expect("grant write access"); - let response = app() + let response = build_app(state) .oneshot( Request::builder() .method("POST") @@ -7651,8 +10799,10 @@ mod tests { .await .expect("body"); let payload: Value = serde_json::from_slice(&body).expect("json"); - assert_eq!(payload["persistence"], "local_ai_session_jsonl"); - assert_eq!(payload["sessionStorage"], "local_private"); + assert_eq!(payload["persistence"], ACP_RUNTIME_SQLITE_STORE); + assert_eq!(payload["sessionStorage"], "sqlite_control_plane"); + assert_eq!(payload["legacyPersistence"], "local_ai_session_jsonl"); + assert_eq!(payload["legacySessionStorage"], "local_private"); let jsonl_path = root .join("ai-sessions") .join("private") @@ -7732,8 +10882,9 @@ mod tests { .await .expect("body"); let payload: Value = serde_json::from_slice(&body).expect("json"); - assert_eq!(payload["persistence"], "local_ai_session_jsonl"); - assert_eq!(payload["sessionStorage"], "local_private"); + assert_eq!(payload["persistence"], ACP_RUNTIME_SQLITE_STORE); + assert_eq!(payload["sessionStorage"], "sqlite_control_plane"); + assert_eq!(payload["legacyPersistence"], "local_ai_session_jsonl"); let _ = fs::remove_dir_all(&root); } @@ -7813,7 +10964,8 @@ mod tests { .await .expect("body"); let payload: Value = serde_json::from_slice(&body).expect("json"); - assert_eq!(payload["sessionStorage"], "local_shared"); + assert_eq!(payload["sessionStorage"], "sqlite_control_plane"); + assert_eq!(payload["legacySessionStorage"], "local_shared"); let session_id = payload["sessionId"].as_str().expect("session id"); let jsonl_path = root .join("ai-sessions") @@ -9167,6 +12319,13 @@ mod tests { .contains("\"rootUri\":\"file:///mnt/Data1T/Mnote_data/users/user_1/我的空间\"")); assert!(instructions.contains("\"aiAccessScope\"")); assert!(instructions.contains("\"allowedRoots\"")); + assert!(instructions.contains("\"allowedFiles\":[\"README.md\"]")); + assert!(instructions.contains( + "\"allowedFilePaths\":[\"/mnt/Data1T/Mnote_data/users/user_1/我的空间/README.md\"]" + )); + assert!(instructions.contains("\"agentTargetPackage\"")); + assert!(instructions.contains("\"targetPackage\"")); + assert!(instructions.contains("\"resourceKind\":\"markdown_page\"")); assert!(instructions.contains("\"editorTarget\"")); assert!(instructions.contains("\"runTargetSnapshot\"")); assert!(instructions.contains("\"schema\":\"mnote.page_ai_run_target_snapshot.v1\"")); @@ -9260,9 +12419,11 @@ mod tests { }), ) .expect("body"); + let instructions_text = body["instructions"].as_str().expect("instructions"); + assert!(instructions_text.contains("onlyofficeSessionId 参数")); + assert!(instructions_text.contains("不得改用最近活跃 Office session")); let instructions: Value = - serde_json::from_str(body["instructions"].as_str().expect("instructions")) - .expect("instructions json"); + serde_json::from_str(instructions_text).expect("instructions json"); let envelope = &instructions["agentRunEnvelope"]; assert_eq!(envelope["schema"], "mnote.agent_run_envelope.v1"); assert_eq!(envelope["agentId"], "reasonix"); @@ -9276,6 +12437,15 @@ mod tests { assert_eq!(envelope["contextRefs"][0]["kind"], "active_editor"); assert_eq!(envelope["contextRefs"][0]["relativePath"], "README.md"); assert_eq!(envelope["allowedRoots"][0]["permission"], "write"); + assert_eq!(envelope["allowedFiles"][0], "README.md"); + assert_eq!( + envelope["targetPackage"]["currentFile"]["relativePath"], + "README.md" + ); + assert_eq!( + envelope["targetPackage"]["workspacePath"]["resourceKind"], + "markdown_page" + ); assert_eq!( envelope["resultPolicy"]["receiptSchema"], "mnote.agent_run_receipt.v1" @@ -9286,6 +12456,124 @@ mod tests { assert!(!envelope.to_string().contains("absolutePath")); } + #[test] + fn hermes_client_run_body_preserves_onlyoffice_target_scope() { + let mut headers = HeaderMap::new(); + headers.insert("x-mnote-actor-id", "user_1".parse().unwrap()); + headers.insert("x-mnote-actor-type", "user".parse().unwrap()); + let context = RequestContext::from_http_parts( + &axum::http::Method::POST, + &"/api/hermes/client/runs".parse().expect("uri"), + &headers, + ); + let body = build_run_upstream_body( + &context, + json!({ + "workspaceId": "local-workspace-1", + "documentId": "local-md:README.md", + "sessionId": "sess_local_office", + "runId": "run_local_office", + "sourceKind": "local_folder", + "rootUri": "file:///mnt/Data1T/Mnote_data/users/user_1/我的空间", + "agentId": "reasonix", + "acpRuntime": "reasonix", + "allowedRoots": [{ + "rootUri": "file:///mnt/Data1T/Mnote_data/users/user_1/我的空间", + "permission": "write", + "recursive": true + }], + "contextRefs": [{"kind": "active_editor"}], + "editorTarget": { + "schema": "mnote.ai_editor_target.v1", + "targetId": "resource:onlyoffice:local-md:README.md:local-file:office/report.docx", + "objectIdentity": "resource:onlyoffice:local-md:README.md:local-file:office/report.docx", + "documentId": "local-md:README.md", + "workspaceId": "local-workspace-1", + "editorKind": "office", + "resourceKind": "only_office", + "assetId": "local-file:office/report.docx", + "onlyofficeSessionId": "mnote-oo-session-a", + "workspacePath": { + "schema": "mnote.workspace_path.v1", + "workspaceId": "local-workspace-1", + "sourceKind": "local_folder", + "rootUri": "file:///mnt/Data1T/Mnote_data/users/user_1/我的空间", + "relativePath": "office/report.docx", + "documentId": "local-md:README.md", + "objectIdentity": "resource:onlyoffice:local-md:README.md:local-file:office/report.docx", + "assetId": "local-file:office/report.docx", + "resourceKind": "only_office" + } + }, + "targetPackage": { + "schema": "mnote.agent_target_package.v1", + "primaryTargetId": "resource:onlyoffice:local-md:README.md:local-file:office/report.docx", + "workspaceId": "local-workspace-1", + "sourceKind": "local_folder", + "rootUri": "file:///mnt/Data1T/Mnote_data/users/user_1/我的空间", + "documentId": "local-md:README.md", + "objectIdentity": "resource:onlyoffice:local-md:README.md:local-file:office/report.docx", + "resourceKind": "only_office", + "onlyofficeSessionId": "mnote-oo-session-a", + "workspacePath": { + "schema": "mnote.workspace_path.v1", + "workspaceId": "local-workspace-1", + "sourceKind": "local_folder", + "rootUri": "file:///mnt/Data1T/Mnote_data/users/user_1/我的空间", + "relativePath": "office/report.docx", + "documentId": "local-md:README.md", + "objectIdentity": "resource:onlyoffice:local-md:README.md:local-file:office/report.docx", + "assetId": "local-file:office/report.docx", + "resourceKind": "only_office" + }, + "targets": [{ + "targetId": "resource:onlyoffice:local-md:README.md:local-file:office/report.docx", + "objectIdentity": "resource:onlyoffice:local-md:README.md:local-file:office/report.docx", + "documentId": "local-md:README.md", + "workspaceId": "local-workspace-1", + "sourceKind": "local_folder", + "rootUri": "file:///mnt/Data1T/Mnote_data/users/user_1/我的空间", + "relativePath": "office/report.docx", + "resourceKind": "only_office", + "assetId": "local-file:office/report.docx", + "onlyofficeSessionId": "mnote-oo-session-a" + }] + }, + "message": "编辑 Office 资源", + "traceId": "trace_local_office" + }), + ) + .expect("body"); + let instructions: Value = + serde_json::from_str(body["instructions"].as_str().expect("instructions")) + .expect("instructions json"); + let scope = &instructions["aiAccessScope"]; + assert!(scope["allowedResourceIds"] + .as_array() + .expect("allowed resources") + .iter() + .any(|value| value == "local-file:office/report.docx")); + assert!(scope["allowedResourceIds"] + .as_array() + .expect("allowed resources") + .iter() + .any(|value| value + == "resource:onlyoffice:local-md:README.md:local-file:office/report.docx")); + assert!(scope["allowedResourceIds"] + .as_array() + .expect("allowed resources") + .iter() + .any(|value| value == "mnote-oo-session-a")); + assert_eq!( + instructions["agentTargetPackage"]["targets"][0]["onlyofficeSessionId"], + "mnote-oo-session-a" + ); + assert_eq!( + instructions["agentRunEnvelope"]["targetPackage"]["targets"][0]["assetId"], + "local-file:office/report.docx" + ); + } + #[test] fn capability_policy_does_not_text_classify_ack_prompt() { assert!( @@ -9749,7 +13037,7 @@ mod tests { } #[test] - fn hermes_client_run_guidance_prefers_markdown_edit_for_plain_body_edits() { + fn hermes_client_run_guidance_prefers_markdown_edit_only_for_remote_compat_plain_body_edits() { let context = RequestContext::from_http_parts( &axum::http::Method::POST, &"/api/hermes/client/runs".parse().expect("uri"), @@ -9774,9 +13062,9 @@ mod tests { ) .expect("body"); let instructions = body["instructions"].as_str().expect("instructions"); - assert!(instructions.contains("普通正文 search/replace、局部段落替换或全文 markdown 替换,应优先调用 mnote_doc_markdown_edit")); + assert!(instructions.contains("远端 / cloud / compat 普通正文 search/replace、局部段落替换或全文 markdown 替换,应优先调用 mnote_doc_markdown_edit")); assert!(instructions.contains( - "\"mnote_doc_markdown_edit for plain body search/replace or full markdown replacement\"" + "\"mnote_doc_markdown_edit for remote/cloud/compat plain body search/replace or full markdown replacement\"" )); assert!(!instructions.contains( "简单小段落编辑或同一页内多个普通叶子块操作,应优先用 mnote_doc_apply_block_ops" diff --git a/rust/crates/mnote-web/src/routes/hermes_tools.rs b/rust/crates/mnote-web/src/routes/hermes_tools.rs index 6d65a4a6..82a2039c 100644 --- a/rust/crates/mnote-web/src/routes/hermes_tools.rs +++ b/rust/crates/mnote-web/src/routes/hermes_tools.rs @@ -3,7 +3,8 @@ use crate::app::AppState; use crate::context::RequestContext; use crate::error::WebError; use crate::hermes_tools::{ - artifact, block, context_tools, doc, manifest, page, resource, skill, ToolCallInput, + artifact, block, context_tools, doc, manifest, onlyoffice_live, page, resource, skill, + ToolCallInput, }; use axum::extract::{Extension, Query, State}; use axum::http::{HeaderMap, HeaderName, HeaderValue, StatusCode}; @@ -373,8 +374,112 @@ pub(crate) async fn execute_mnote_tool_call( "mnote.page.update_options" => page::update_options(&state, &context, &input).await, "mnote.mindmap.fetch" => resource::mindmap_fetch(&context, &input).await, "mnote.mindmap.apply_ops" => resource::mindmap_apply_ops(&context, &input).await, + "mnote.mindmap.create_from_outline" => { + resource::mindmap_create_from_outline(&context, &input).await + } "mnote.office.fetch_summary" => resource::office_fetch_summary(&context, &input).await, "mnote.office.propose_changes" => resource::office_propose_changes(&context, &input).await, + "mnote.onlyoffice.session.current" => { + onlyoffice_live::session_current(&context, &input).await + } + "mnote.onlyoffice.capabilities" => onlyoffice_live::capabilities(&context, &input).await, + "mnote.onlyoffice.selection.get" => onlyoffice_live::selection_get(&context, &input).await, + "mnote.onlyoffice.document.insert_text" => { + onlyoffice_live::document_insert_text(&context, &input).await + } + "mnote.onlyoffice.document.replace_selection" => { + onlyoffice_live::document_replace_selection(&context, &input).await + } + "mnote.onlyoffice.document.insert_html" => { + onlyoffice_live::document_insert_html(&context, &input).await + } + "mnote.onlyoffice.document.export" => { + onlyoffice_live::document_export(&context, &input).await + } + "mnote.onlyoffice.document.search_replace" => { + onlyoffice_live::document_search_replace(&context, &input).await + } + "mnote.onlyoffice.document.insert_table" => { + onlyoffice_live::document_insert_table(&context, &input).await + } + "mnote.onlyoffice.document.get_comments" => { + onlyoffice_live::document_get_comments(&context, &input).await + } + "mnote.onlyoffice.document.add_comment" => { + onlyoffice_live::document_add_comment(&context, &input).await + } + "mnote.onlyoffice.sheet.get_sheets" => { + onlyoffice_live::sheet_get_sheets(&context, &input).await + } + "mnote.onlyoffice.sheet.add_sheet" => { + onlyoffice_live::sheet_add_sheet(&context, &input).await + } + "mnote.onlyoffice.sheet.rename_sheet" => { + onlyoffice_live::sheet_rename_sheet(&context, &input).await + } + "mnote.onlyoffice.sheet.get_range" => { + onlyoffice_live::sheet_get_range(&context, &input).await + } + "mnote.onlyoffice.sheet.get_range_values" => { + onlyoffice_live::sheet_get_range_values(&context, &input).await + } + "mnote.onlyoffice.sheet.get_values" => { + onlyoffice_live::sheet_get_values(&context, &input).await + } + "mnote.onlyoffice.sheet.set_value" => { + onlyoffice_live::sheet_set_value(&context, &input).await + } + "mnote.onlyoffice.sheet.set_formula" => { + onlyoffice_live::sheet_set_formula(&context, &input).await + } + "mnote.onlyoffice.sheet.batch_set_values" => { + onlyoffice_live::sheet_batch_set_values(&context, &input).await + } + "mnote.onlyoffice.sheet.set_range_values" => { + onlyoffice_live::sheet_set_range_values(&context, &input).await + } + "mnote.onlyoffice.sheet.format_range" => { + onlyoffice_live::sheet_format_range(&context, &input).await + } + "mnote.onlyoffice.sheet.set_dimensions" => { + onlyoffice_live::sheet_set_dimensions(&context, &input).await + } + "mnote.onlyoffice.sheet.sort_range" => { + onlyoffice_live::sheet_sort_range(&context, &input).await + } + "mnote.onlyoffice.sheet.add_chart" => { + onlyoffice_live::sheet_add_chart(&context, &input).await + } + "mnote.onlyoffice.presentation.get_slides" => { + onlyoffice_live::presentation_get_slides(&context, &input).await + } + "mnote.onlyoffice.presentation.get_slide_texts" => { + onlyoffice_live::presentation_get_slide_texts(&context, &input).await + } + "mnote.onlyoffice.presentation.get_shapes" => { + onlyoffice_live::presentation_get_shapes(&context, &input).await + } + "mnote.onlyoffice.presentation.add_text_slide" => { + onlyoffice_live::presentation_add_text_slide(&context, &input).await + } + "mnote.onlyoffice.presentation.replace_text" => { + onlyoffice_live::presentation_replace_text(&context, &input).await + } + "mnote.onlyoffice.presentation.set_shape_text" => { + onlyoffice_live::presentation_set_shape_text(&context, &input).await + } + "mnote.onlyoffice.presentation.delete_slide" => { + onlyoffice_live::presentation_delete_slide(&context, &input).await + } + "mnote.onlyoffice.presentation.add_table" => { + onlyoffice_live::presentation_add_table(&context, &input).await + } + "mnote.onlyoffice.presentation.clear_slide" => { + onlyoffice_live::presentation_clear_slide(&context, &input).await + } + "mnote.onlyoffice.presentation.add_shape" => { + onlyoffice_live::presentation_add_shape(&context, &input).await + } "mnote.artifact.create_summary" => artifact::create_summary(&state, &context, &input).await, "mnote.artifact.create_ai_note" => artifact::create_ai_note(&state, &context, &input).await, _ => Err( @@ -558,6 +663,18 @@ fn is_read_tool(tool_name: &str) -> bool { | "mnote.mindmap.fetch" | "mnote.office.fetch_summary" | "mnote.office.propose_changes" + | "mnote.onlyoffice.session.current" + | "mnote.onlyoffice.capabilities" + | "mnote.onlyoffice.selection.get" + | "mnote.onlyoffice.document.export" + | "mnote.onlyoffice.document.get_comments" + | "mnote.onlyoffice.sheet.get_sheets" + | "mnote.onlyoffice.sheet.get_range" + | "mnote.onlyoffice.sheet.get_range_values" + | "mnote.onlyoffice.sheet.get_values" + | "mnote.onlyoffice.presentation.get_slides" + | "mnote.onlyoffice.presentation.get_slide_texts" + | "mnote.onlyoffice.presentation.get_shapes" ) } @@ -847,6 +964,7 @@ mod tests { use serde_json::{json, Value}; use std::collections::BTreeMap; use std::fs; + use std::path::PathBuf; use std::sync::Mutex; use tower::util::ServiceExt; @@ -971,8 +1089,68 @@ mod tests { })) } + fn write_mindmap_apply_fixture(test_name: &str, content: Value) -> (PathBuf, String) { + let root = std::env::temp_dir().join(format!( + "mnote-resource-mindmap-{test_name}-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("maps")).expect("maps"); + fs::write( + root.join("maps").join("idea.mindmap.json"), + serde_json::to_string_pretty(&content).expect("mindmap json"), + ) + .expect("mindmap"); + let root_uri = format!("file://{}", root.display()); + crate::routes::local_folder_source::initialize_local_workspace_for_actor( + "user_1", &root_uri, + ) + .expect("workspace"); + (root, root_uri) + } + + async fn post_mindmap_apply_ops( + root_uri: &str, + dry_run: bool, + args: Value, + id_suffix: &str, + ) -> axum::response::Response { + app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "toolName": "mnote.mindmap.apply_ops", + "workspaceId": "local-ws-resource", + "documentId": "local-md:README.md", + "sourceKind": "local_folder", + "rootUri": root_uri, + "actorId": "user_1", + "sessionId": format!("sess_mindmap_apply_{id_suffix}"), + "runId": format!("run_mindmap_apply_{id_suffix}"), + "toolCallId": format!("call_mindmap_apply_{id_suffix}"), + "traceId": format!("trace_mindmap_apply_{id_suffix}"), + "idempotencyKey": format!("idem_mindmap_apply_{id_suffix}"), + "dryRun": dry_run, + "args": args + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response") + } + async fn call_tool_ok(payload: Value) -> Value { - let response = app() + let app = app(); + maybe_register_onlyoffice_test_session(&app, &payload).await; + let response = app .oneshot( Request::builder() .method("POST") @@ -992,6 +1170,7 @@ mod tests { } async fn call_tool_ok_with_app(app: axum::Router, payload: Value) -> Value { + maybe_register_onlyoffice_test_session(&app, &payload).await; let response = app .oneshot( Request::builder() @@ -1011,6 +1190,67 @@ mod tests { serde_json::from_slice(&body).expect("json") } + async fn maybe_register_onlyoffice_test_session(app: &axum::Router, payload: &Value) { + let Some(tool_name) = payload.get("toolName").and_then(Value::as_str) else { + return; + }; + if !tool_name.starts_with("mnote.onlyoffice.") + || tool_name == "mnote.onlyoffice.capabilities" + || tool_name == "mnote.onlyoffice.session.current" + { + return; + } + let Some(session_id) = payload + .get("args") + .and_then(|args| { + args.get("onlyofficeSessionId") + .or_else(|| args.get("bridgeSessionId")) + }) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + else { + return; + }; + let allowed_resource_id = payload + .get("args") + .and_then(|args| args.get("aiAccessScope")) + .and_then(|scope| { + scope + .get("allowedResourceIds") + .or_else(|| scope.get("allowed_resource_ids")) + }) + .and_then(Value::as_array) + .and_then(|values| values.iter().find_map(Value::as_str)) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or(session_id); + let token = format!("token-{session_id}"); + let register = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/session") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": session_id, + "token": token, + "editorType": "cell", + "documentId": "doc_1", + "assetId": allowed_resource_id, + "fileType": "xlsx" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("register response"); + assert_eq!(register.status(), StatusCode::OK); + } + async fn block_revision_ref(block_id: &str) -> String { let payload = call_tool_ok(json!({ "toolName": "mnote.doc.fetch", @@ -1150,12 +1390,94 @@ mod tests { assert!(tools .iter() .any(|tool| tool["name"] == "mnote.mindmap.apply_ops")); + let mindmap_create = tools + .iter() + .find(|tool| tool["name"] == "mnote.mindmap.create_from_outline") + .expect("mindmap create_from_outline tool"); + assert_eq!( + mindmap_create["inputSchema"]["properties"]["outline"]["type"], + "array" + ); + assert!(mindmap_create["capabilityScope"] + .as_array() + .expect("capability scope") + .iter() + .any(|scope| scope == "mindmap.write")); assert!(tools .iter() .any(|tool| tool["name"] == "mnote.office.fetch_summary")); assert!(tools .iter() .any(|tool| tool["name"] == "mnote.office.propose_changes")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.session.current")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.capabilities")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.set_value")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.document.export")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.get_values")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.batch_set_values")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.document.search_replace")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.document.insert_table")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.document.get_comments")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.document.add_comment")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.get_sheets")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.get_range_values")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.set_range_values")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.format_range")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.set_dimensions")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.sort_range")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.sheet.add_chart")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.presentation.get_slide_texts")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.presentation.get_shapes")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.presentation.replace_text")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.presentation.set_shape_text")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.presentation.delete_slide")); + assert!(tools + .iter() + .any(|tool| tool["name"] == "mnote.onlyoffice.presentation.add_table")); let page_save = tools .iter() .find(|tool| tool["name"] == "mnote.page.save") @@ -1167,6 +1489,1312 @@ mod tests { ); } + #[tokio::test] + async fn hermes_tools_onlyoffice_session_current_reads_registered_bridge_session() { + let app = app(); + let bridge_session_id = format!("mnote-oo-tool-test-{}", std::process::id()); + let bridge_token = format!("token-{bridge_session_id}"); + let register = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/session") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": bridge_session_id, + "token": bridge_token, + "editorType": "cell", + "documentId": "doc_meta", + "assetId": "asset_meta", + "fileType": "xlsx" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("register response"); + assert_eq!(register.status(), StatusCode::OK); + + let payload = call_tool_ok_with_app( + app, + json!({ + "toolName": "mnote.onlyoffice.session.current", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_current", + "runId": "run_oo_current", + "toolCallId": "call_oo_current", + "traceId": "trace_oo_current", + "capabilityScope": ["office.read"], + "args": { + "onlyofficeSessionId": bridge_session_id, + "aiAccessScope": { + "permissionLevel": "read", + "allowedResourceIds": ["asset_meta"] + } + } + }), + ) + .await; + + assert_eq!(payload["result"]["schema"], "mnote.onlyoffice.session.v1"); + assert_eq!(payload["result"]["session"]["sessionId"], bridge_session_id); + assert_eq!(payload["result"]["session"]["editorType"], "cell"); + assert_eq!(payload["result"]["session"]["documentId"], "doc_meta"); + assert_eq!(payload["result"]["session"]["assetId"], "asset_meta"); + assert_eq!(payload["result"]["session"]["fileType"], "xlsx"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_session_current_requires_explicit_authorized_scope() { + let app = app(); + let bridge_session_id = format!("mnote-oo-current-scope-{}", std::process::id()); + let bridge_token = format!("token-{bridge_session_id}"); + let register = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/session") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": bridge_session_id, + "token": bridge_token, + "editorType": "word", + "documentId": "doc_current_b", + "assetId": "asset_current_b", + "fileType": "docx" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("register response"); + assert_eq!(register.status(), StatusCode::OK); + + let implicit_response = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .body(Body::from( + json!({ + "toolName": "mnote.onlyoffice.session.current", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_current_implicit", + "runId": "run_oo_current_implicit", + "toolCallId": "call_oo_current_implicit", + "traceId": "trace_oo_current_implicit", + "capabilityScope": ["office.read"], + "args": { + "aiAccessScope": { + "permissionLevel": "read", + "allowedResourceIds": ["asset_current_b"] + } + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("implicit response"); + assert_eq!(implicit_response.status(), StatusCode::BAD_REQUEST); + assert_eq!( + implicit_response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_onlyoffice_session_explicit_required") + ); + + let forbidden_response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .body(Body::from( + json!({ + "toolName": "mnote.onlyoffice.session.current", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_current_forbidden", + "runId": "run_oo_current_forbidden", + "toolCallId": "call_oo_current_forbidden", + "traceId": "trace_oo_current_forbidden", + "capabilityScope": ["office.read"], + "args": { + "onlyofficeSessionId": bridge_session_id, + "aiAccessScope": { + "permissionLevel": "read", + "allowedResourceIds": ["asset_current_a"] + } + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("forbidden response"); + assert_eq!(forbidden_response.status(), StatusCode::FORBIDDEN); + assert_eq!( + forbidden_response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_onlyoffice_resource_scope_forbidden") + ); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_capabilities_lists_shape_and_range_actions() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.capabilities", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_caps", + "runId": "run_oo_caps", + "toolCallId": "call_oo_caps", + "traceId": "trace_oo_caps", + "capabilityScope": ["office.read"] + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.bridge_capabilities.v1" + ); + let actions = payload["result"]["actions"].as_array().expect("actions"); + assert!(actions + .iter() + .any(|action| action == "sheet.set_range_values")); + assert!(actions.iter().any(|action| action == "sheet.format_range")); + assert!(actions + .iter() + .any(|action| action == "sheet.set_dimensions")); + assert!(actions.iter().any(|action| action == "sheet.sort_range")); + assert!(actions.iter().any(|action| action == "sheet.add_chart")); + assert!(actions + .iter() + .any(|action| action == "presentation.set_shape_text")); + assert!(actions + .iter() + .any(|action| action == "presentation.delete_slide")); + assert!(actions + .iter() + .any(|action| action == "presentation.add_table")); + assert!(actions + .iter() + .any(|action| action == "presentation.clear_slide")); + assert!(actions + .iter() + .any(|action| action == "presentation.add_shape")); + assert!(actions + .iter() + .any(|action| action == "document.insert_table")); + assert!(actions + .iter() + .any(|action| action == "document.get_comments")); + assert!(actions + .iter() + .any(|action| action == "document.add_comment")); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_selection_get_round_trips_bridge_command() { + let app = app(); + let bridge_session_id = format!("mnote-oo-selection-test-{}", std::process::id()); + let bridge_token = format!("token-{bridge_session_id}"); + let register = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/session") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": bridge_session_id, + "token": bridge_token, + "editorType": "word" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("register response"); + assert_eq!(register.status(), StatusCode::OK); + + let worker_app = app.clone(); + let worker_session_id = bridge_session_id.clone(); + let worker = tokio::spawn(async move { + let next = worker_app + .clone() + .oneshot( + Request::builder() + .uri(format!( + "/api/onlyoffice/bridge/commands/next?sessionId={worker_session_id}&token={bridge_token}&timeoutMs=5000" + )) + .body(Body::empty()) + .expect("next request"), + ) + .await + .expect("next response"); + assert_eq!(next.status(), StatusCode::OK); + let body = to_bytes(next.into_body(), usize::MAX) + .await + .expect("next body"); + let command: Value = serde_json::from_slice(&body).expect("command json"); + assert_eq!(command["action"], "selection.get"); + let command_id = command["id"].as_str().expect("command id"); + + let posted = worker_app + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/results") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": worker_session_id, + "token": bridge_token, + "id": command_id, + "ok": true, + "result": { + "editorType": "word", + "text": "选中文本" + } + }) + .to_string(), + )) + .expect("post result request"), + ) + .await + .expect("post result response"); + assert_eq!(posted.status(), StatusCode::OK); + }); + + let payload = call_tool_ok_with_app( + app, + json!({ + "toolName": "mnote.onlyoffice.selection.get", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_select", + "runId": "run_oo_select", + "toolCallId": "call_oo_select", + "traceId": "trace_oo_select", + "capabilityScope": ["office.read"], + "args": { + "onlyofficeSessionId": bridge_session_id, + "aiAccessScope": { + "permissionLevel": "read", + "allowedResourceIds": [bridge_session_id] + }, + "timeoutMs": 5000 + } + }), + ) + .await; + worker.await.expect("worker"); + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_result.v1" + ); + assert_eq!(payload["result"]["action"], "selection.get"); + assert_eq!(payload["result"]["result"]["text"], "选中文本"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_write_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.sheet.set_value", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_sheet", + "runId": "run_oo_sheet", + "toolCallId": "call_oo_sheet", + "traceId": "trace_oo_sheet", + "idempotencyKey": "idem_oo_sheet", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "address": "A1", + "value": "after" + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "sheet.set_value"); + assert_eq!(payload["result"]["payload"]["address"], "A1"); + assert_eq!(payload["result"]["payload"]["value"], "after"); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_write_tool_rejects_implicit_current_session() { + let app = app(); + let bridge_session_id = format!("mnote-oo-implicit-write-{}", std::process::id()); + let bridge_token = format!("token-{bridge_session_id}"); + let register = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/session") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": bridge_session_id, + "token": bridge_token, + "editorType": "cell", + "documentId": "doc_office", + "assetId": "asset_office", + "fileType": "xlsx" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("register response"); + assert_eq!(register.status(), StatusCode::OK); + + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .body(Body::from( + json!({ + "toolName": "mnote.onlyoffice.sheet.set_value", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_implicit", + "runId": "run_oo_implicit", + "toolCallId": "call_oo_implicit", + "traceId": "trace_oo_implicit", + "idempotencyKey": "idem_oo_implicit", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "address": "A1", + "value": "after" + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_onlyoffice_session_explicit_required") + ); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_read_tool_rejects_implicit_current_session() { + let app = app(); + let bridge_session_id = format!("mnote-oo-read-implicit-{}", std::process::id()); + let bridge_token = format!("token-{bridge_session_id}"); + let register = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/session") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": bridge_session_id, + "token": bridge_token, + "editorType": "word", + "documentId": "doc_office", + "assetId": "asset_office", + "fileType": "docx" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("register response"); + assert_eq!(register.status(), StatusCode::OK); + + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .body(Body::from( + json!({ + "toolName": "mnote.onlyoffice.document.export", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_read_implicit", + "runId": "run_oo_read_implicit", + "toolCallId": "call_oo_read_implicit", + "traceId": "trace_oo_read_implicit", + "capabilityScope": ["office.read"], + "args": { + "format": "markdown", + "aiAccessScope": { + "permissionLevel": "read", + "allowedResourceIds": ["asset_office"] + } + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_onlyoffice_session_explicit_required") + ); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_write_tool_rejects_missing_resource_scope() { + let app = app(); + let bridge_session_id = format!("mnote-oo-missing-scope-{}", std::process::id()); + let bridge_token = format!("token-{bridge_session_id}"); + let register = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/session") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": bridge_session_id, + "token": bridge_token, + "editorType": "cell", + "documentId": "doc_office", + "assetId": "asset_office", + "fileType": "xlsx" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("register response"); + assert_eq!(register.status(), StatusCode::OK); + + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .body(Body::from( + json!({ + "toolName": "mnote.onlyoffice.sheet.set_value", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_missing_scope", + "runId": "run_oo_missing_scope", + "toolCallId": "call_oo_missing_scope", + "traceId": "trace_oo_missing_scope", + "idempotencyKey": "idem_oo_missing_scope", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": bridge_session_id, + "address": "A1", + "value": "after" + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + assert_eq!(response.status(), StatusCode::FORBIDDEN); + assert_eq!( + response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_onlyoffice_resource_scope_required") + ); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_write_tool_requires_allowed_resource_scope() { + let app = app(); + let bridge_session_id = format!("mnote-oo-scope-write-{}", std::process::id()); + let bridge_token = format!("token-{bridge_session_id}"); + let register = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/session") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": bridge_session_id, + "token": bridge_token, + "editorType": "cell", + "documentId": "doc_office", + "assetId": "asset_b", + "fileType": "xlsx" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("register response"); + assert_eq!(register.status(), StatusCode::OK); + + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .body(Body::from( + json!({ + "toolName": "mnote.onlyoffice.sheet.set_value", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_scope", + "runId": "run_oo_scope", + "toolCallId": "call_oo_scope", + "traceId": "trace_oo_scope", + "idempotencyKey": "idem_oo_scope", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": bridge_session_id, + "address": "A1", + "value": "after", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["asset_a"] + } + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + assert_eq!(response.status(), StatusCode::FORBIDDEN); + assert_eq!( + response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_onlyoffice_resource_scope_forbidden") + ); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_write_tool_allows_authorized_resource_scope() { + let app = app(); + let bridge_session_id = format!("mnote-oo-scope-allow-{}", std::process::id()); + let bridge_token = format!("token-{bridge_session_id}"); + let register = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/onlyoffice/bridge/session") + .header("content-type", "application/json") + .body(Body::from( + json!({ + "sessionId": bridge_session_id, + "token": bridge_token, + "editorType": "cell", + "documentId": "doc_office", + "assetId": "asset_allowed", + "fileType": "xlsx" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("register response"); + assert_eq!(register.status(), StatusCode::OK); + + let payload = call_tool_ok_with_app( + app, + json!({ + "toolName": "mnote.onlyoffice.sheet.set_value", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_scope_allow", + "runId": "run_oo_scope_allow", + "toolCallId": "call_oo_scope_allow", + "traceId": "trace_oo_scope_allow", + "idempotencyKey": "idem_oo_scope_allow", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": bridge_session_id, + "address": "A1", + "value": "after", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["resource:onlyoffice:doc_office:asset_allowed"] + } + } + }), + ) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["sessionId"], bridge_session_id); + assert_eq!(payload["result"]["action"], "sheet.set_value"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_batch_write_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.sheet.batch_set_values", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_batch", + "runId": "run_oo_batch", + "toolCallId": "call_oo_batch", + "traceId": "trace_oo_batch", + "idempotencyKey": "idem_oo_batch", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "startRow": 1, + "startCol": 2, + "values": [["A", "B"], ["C", "D"]] + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "sheet.batch_set_values"); + assert_eq!(payload["result"]["payload"]["startRow"], 1); + assert_eq!(payload["result"]["payload"]["startCol"], 2); + assert_eq!(payload["result"]["payload"]["values"][1][1], "D"); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_range_write_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.sheet.set_range_values", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_range", + "runId": "run_oo_range", + "toolCallId": "call_oo_range", + "traceId": "trace_oo_range", + "idempotencyKey": "idem_oo_range", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "address": "B2:C3", + "values": [["A", "B"], ["C", "D"]] + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "sheet.set_range_values"); + assert_eq!(payload["result"]["payload"]["address"], "B2:C3"); + assert_eq!(payload["result"]["payload"]["values"][1][1], "D"); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_search_replace_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.document.search_replace", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_doc_replace", + "runId": "run_oo_doc_replace", + "toolCallId": "call_oo_doc_replace", + "traceId": "trace_oo_doc_replace", + "idempotencyKey": "idem_oo_doc_replace", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "search": "old", + "replace": "new", + "matchCase": true + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "document.search_replace"); + assert_eq!(payload["result"]["payload"]["search"], "old"); + assert_eq!(payload["result"]["payload"]["replace"], "new"); + assert_eq!(payload["result"]["payload"]["matchCase"], true); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_insert_table_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.document.insert_table", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_table", + "runId": "run_oo_table", + "toolCallId": "call_oo_table", + "traceId": "trace_oo_table", + "idempotencyKey": "idem_oo_table", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "rows": 2, + "cols": 3, + "data": [["A", "B", "C"], ["1", "2", "3"]] + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "document.insert_table"); + assert_eq!(payload["result"]["payload"]["rows"], 2); + assert_eq!(payload["result"]["payload"]["cols"], 3); + assert_eq!(payload["result"]["payload"]["data"][1][2], "3"); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_add_comment_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.document.add_comment", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_comment", + "runId": "run_oo_comment", + "toolCallId": "call_oo_comment", + "traceId": "trace_oo_comment", + "idempotencyKey": "idem_oo_comment", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "text": "Review this paragraph", + "author": "Hermes", + "target": "document_start" + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "document.add_comment"); + assert_eq!( + payload["result"]["payload"]["text"], + "Review this paragraph" + ); + assert_eq!(payload["result"]["payload"]["author"], "Hermes"); + assert_eq!(payload["result"]["payload"]["target"], "document_start"); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_presentation_replace_text_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.presentation.replace_text", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_ppt_replace", + "runId": "run_oo_ppt_replace", + "toolCallId": "call_oo_ppt_replace", + "traceId": "trace_oo_ppt_replace", + "idempotencyKey": "idem_oo_ppt_replace", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "search": "Quarterly", + "replace": "Monthly", + "slideIndex": 0 + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "presentation.replace_text"); + assert_eq!(payload["result"]["payload"]["search"], "Quarterly"); + assert_eq!(payload["result"]["payload"]["replace"], "Monthly"); + assert_eq!(payload["result"]["payload"]["slideIndex"], 0); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_shape_text_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.presentation.set_shape_text", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_ppt_shape", + "runId": "run_oo_ppt_shape", + "toolCallId": "call_oo_ppt_shape", + "traceId": "trace_oo_ppt_shape", + "idempotencyKey": "idem_oo_ppt_shape", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "slideIndex": 0, + "shapeIndex": 1, + "text": "Precise shape text" + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "presentation.set_shape_text"); + assert_eq!(payload["result"]["payload"]["slideIndex"], 0); + assert_eq!(payload["result"]["payload"]["shapeIndex"], 1); + assert_eq!(payload["result"]["payload"]["text"], "Precise shape text"); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_sheet_format_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.sheet.format_range", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_sheet_format", + "runId": "run_oo_sheet_format", + "toolCallId": "call_oo_sheet_format", + "traceId": "trace_oo_sheet_format", + "idempotencyKey": "idem_oo_sheet_format", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "address": "B2:C3", + "bold": true, + "fillColor": "#FFE599", + "fontColor": "#CC0000", + "horizontalAlign": "center", + "numberFormat": "0.00" + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "sheet.format_range"); + assert_eq!(payload["result"]["payload"]["address"], "B2:C3"); + assert_eq!(payload["result"]["payload"]["bold"], true); + assert_eq!(payload["result"]["payload"]["fillColor"], "#FFE599"); + assert_eq!(payload["result"]["payload"]["fontColor"], "#CC0000"); + assert_eq!(payload["result"]["payload"]["horizontalAlign"], "center"); + assert_eq!(payload["result"]["payload"]["numberFormat"], "0.00"); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_sheet_dimensions_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.sheet.set_dimensions", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_sheet_dimensions", + "runId": "run_oo_sheet_dimensions", + "toolCallId": "call_oo_sheet_dimensions", + "traceId": "trace_oo_sheet_dimensions", + "idempotencyKey": "idem_oo_sheet_dimensions", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "columnIndex": 2, + "columnWidth": 24, + "rowIndex": 3, + "rowHeight": 28 + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "sheet.set_dimensions"); + assert_eq!(payload["result"]["payload"]["columnIndex"], 2); + assert_eq!(payload["result"]["payload"]["columnWidth"], 24.0); + assert_eq!(payload["result"]["payload"]["rowIndex"], 3); + assert_eq!(payload["result"]["payload"]["rowHeight"], 28.0); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_delete_slide_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.presentation.delete_slide", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_ppt_delete", + "runId": "run_oo_ppt_delete", + "toolCallId": "call_oo_ppt_delete", + "traceId": "trace_oo_ppt_delete", + "idempotencyKey": "idem_oo_ppt_delete", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "slideIndex": 1 + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "presentation.delete_slide"); + assert_eq!(payload["result"]["payload"]["slideIndex"], 1); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_sheet_sort_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.sheet.sort_range", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_sheet_sort", + "runId": "run_oo_sheet_sort", + "toolCallId": "call_oo_sheet_sort", + "traceId": "trace_oo_sheet_sort", + "idempotencyKey": "idem_oo_sheet_sort", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "address": "A1:B4", + "keyColumn": 2, + "order": "descending", + "header": true + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "sheet.sort_range"); + assert_eq!(payload["result"]["payload"]["address"], "A1:B4"); + assert_eq!(payload["result"]["payload"]["keyColumn"], 2); + assert_eq!(payload["result"]["payload"]["order"], "descending"); + assert_eq!(payload["result"]["payload"]["header"], true); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_sheet_chart_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.sheet.add_chart", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_sheet_chart", + "runId": "run_oo_sheet_chart", + "toolCallId": "call_oo_sheet_chart", + "traceId": "trace_oo_sheet_chart", + "idempotencyKey": "idem_oo_sheet_chart", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "address": "A1:B4", + "chartType": "bar", + "widthMm": 110, + "heightMm": 70 + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "sheet.add_chart"); + assert_eq!(payload["result"]["payload"]["address"], "A1:B4"); + assert_eq!(payload["result"]["payload"]["chartType"], "bar"); + assert_eq!(payload["result"]["payload"]["widthMm"], 110.0); + assert_eq!(payload["result"]["payload"]["heightMm"], 70.0); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_presentation_table_tool_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.presentation.add_table", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_ppt_table", + "runId": "run_oo_ppt_table", + "toolCallId": "call_oo_ppt_table", + "traceId": "trace_oo_ppt_table", + "idempotencyKey": "idem_oo_ppt_table", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "slideIndex": 0, + "rows": 2, + "cols": 2, + "data": [["Metric", "Value"], ["Bridge", "PPT table"]] + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "presentation.add_table"); + assert_eq!(payload["result"]["payload"]["slideIndex"], 0); + assert_eq!(payload["result"]["payload"]["rows"], 2); + assert_eq!(payload["result"]["payload"]["cols"], 2); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_presentation_clear_slide_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.presentation.clear_slide", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_ppt_clear", + "runId": "run_oo_ppt_clear", + "toolCallId": "call_oo_ppt_clear", + "traceId": "trace_oo_ppt_clear", + "idempotencyKey": "idem_oo_ppt_clear", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "slideIndex": 2 + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "presentation.clear_slide"); + assert_eq!(payload["result"]["payload"]["slideIndex"], 2); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + + #[tokio::test] + async fn hermes_tools_onlyoffice_presentation_shape_supports_dry_run_plan() { + let payload = call_tool_ok(json!({ + "toolName": "mnote.onlyoffice.presentation.add_shape", + "workspaceId": "ws_demo", + "documentId": "doc_1", + "actorId": "user_1", + "sessionId": "sess_oo_ppt_shape_add", + "runId": "run_oo_ppt_shape_add", + "toolCallId": "call_oo_ppt_shape_add", + "traceId": "trace_oo_ppt_shape_add", + "idempotencyKey": "idem_oo_ppt_shape_add", + "dryRun": true, + "capabilityScope": ["office.write"], + "args": { + "onlyofficeSessionId": "mnote-oo-dry-run", + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["mnote-oo-dry-run"] + }, + "slideIndex": 1, + "shapeType": "roundRect", + "text": "Bridge shape", + "fillColor": "#4F81BD", + "widthMm": 150, + "heightMm": 60 + } + })) + .await; + + assert_eq!( + payload["result"]["schema"], + "mnote.onlyoffice.action_plan.v1" + ); + assert_eq!(payload["result"]["action"], "presentation.add_shape"); + assert_eq!(payload["result"]["payload"]["slideIndex"], 1); + assert_eq!(payload["result"]["payload"]["shapeType"], "roundRect"); + assert_eq!(payload["result"]["payload"]["text"], "Bridge shape"); + assert_eq!(payload["result"]["payload"]["fillColor"], "#4F81BD"); + assert_eq!(payload["audit"]["effect"], "dry_run"); + } + #[tokio::test] async fn hermes_tools_manifest_describes_markdown_edit_write_contract() { let response = app() @@ -1215,6 +2843,47 @@ mod tests { .any(|rule| rule["required"] == json!(["full_content"]))); } + #[tokio::test] + async fn hermes_tools_manifest_describes_onlyoffice_live_scope() { + let response = app() + .oneshot( + Request::builder() + .uri("/api/hermes/tools/mnote/manifest") + .header("x-mnote-actor-id", "user_1") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("response"); + assert_eq!(response.status(), StatusCode::OK); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + let tools = payload["manifest"]["tools"].as_array().expect("tools"); + let write_tool = tools + .iter() + .find(|tool| tool["name"] == "mnote.onlyoffice.sheet.set_value") + .expect("onlyoffice write tool"); + let schema = &write_tool["inputSchema"]; + let required = schema["required"].as_array().expect("required"); + assert!(required.iter().any(|value| value == "aiAccessScope")); + assert!( + schema["properties"]["aiAccessScope"]["properties"]["allowedResourceIds"].is_object() + ); + assert_eq!( + schema["properties"]["aiAccessScope"]["properties"]["allowedResourceIds"]["minItems"], + 1 + ); + let any_of = schema["anyOf"].as_array().expect("anyOf"); + assert!(any_of + .iter() + .any(|rule| rule["required"] == json!(["onlyofficeSessionId"]))); + assert!(any_of + .iter() + .any(|rule| rule["required"] == json!(["bridgeSessionId"]))); + } + #[tokio::test] async fn hermes_tools_manifest_marks_write_tools_as_compat_fallbacks() { let response = app() @@ -1522,6 +3191,99 @@ mod tests { let _ = fs::remove_dir_all(&root); } + #[tokio::test] + async fn hermes_tools_mindmap_fetch_reads_default_envelope() { + let root = std::env::temp_dir().join(format!( + "mnote-resource-mindmap-envelope-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("maps")).expect("maps"); + fs::write( + root.join("maps").join("default.mindmap.json"), + json!({ + "data": { + "children": [ + { + "data": { + "expand": true, + "isActive": false, + "text": "分支", + "uid": "node_1" + }, + "children": [] + } + ], + "data": { + "expand": true, + "isActive": false, + "text": "KMIND", + "uid": "root" + } + }, + "view": { + "state": { "scale": 1, "sx": 0, "sy": 0, "x": 0, "y": 0 }, + "transform": { "a": 1, "b": 0, "c": 0, "d": 1, "e": 0, "f": 0 } + } + }) + .to_string(), + ) + .expect("mindmap"); + let root_uri = format!("file://{}", root.display()); + crate::routes::local_folder_source::initialize_local_workspace_for_actor( + "user_1", &root_uri, + ) + .expect("workspace"); + + let response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "toolName": "mnote.mindmap.fetch", + "workspaceId": "local-ws-resource", + "documentId": "local-md:README.md", + "sourceKind": "local_folder", + "rootUri": root_uri, + "actorId": "user_1", + "sessionId": "sess_mindmap_envelope", + "runId": "run_mindmap_envelope", + "toolCallId": "call_mindmap_envelope", + "traceId": "trace_mindmap_envelope", + "args": { + "mindmapId": "mind_envelope", + "resourcePath": "maps/default.mindmap.json", + "scope": "full_envelope", + "aiAccessScope": { + "permissionLevel": "shared_read", + "allowedResourceIds": ["mind_envelope"] + } + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + assert_eq!(response.status(), StatusCode::OK); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(payload["result"]["root"]["data"]["text"], "KMIND"); + assert_eq!(payload["result"]["nodes"][0]["id"], "root"); + assert_eq!(payload["result"]["nodes"][1]["text"], "分支"); + assert_eq!(payload["result"]["envelope"]["data"]["data"]["uid"], "root"); + let _ = fs::remove_dir_all(&root); + } + #[tokio::test] async fn hermes_tools_mindmap_apply_ops_shared_read_is_forbidden() { let root = std::env::temp_dir().join(format!( @@ -1591,6 +3353,635 @@ mod tests { let _ = fs::remove_dir_all(&root); } + #[tokio::test] + async fn hermes_tools_mindmap_apply_ops_writes_and_preserves_envelope_fields() { + let root = std::env::temp_dir().join(format!( + "mnote-resource-mindmap-apply-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("maps")).expect("maps"); + fs::write( + root.join("maps").join("idea.mindmap.json"), + serde_json::to_string_pretty(&json!({ + "data": { + "data": { + "text": "中心主题", + "uid": "root", + "customRootField": "keep-root" + }, + "children": [ + { + "data": { + "text": "旧分支", + "uid": "node_1", + "style": { "color": "red" } + }, + "children": [] + } + ] + }, + "view": { "state": { "scale": 2 } }, + "unknownTop": { "keep": true } + })) + .expect("json"), + ) + .expect("mindmap"); + let root_uri = format!("file://{}", root.display()); + crate::routes::local_folder_source::initialize_local_workspace_for_actor( + "user_1", &root_uri, + ) + .expect("workspace"); + + let response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "toolName": "mnote.mindmap.apply_ops", + "workspaceId": "local-ws-resource", + "documentId": "local-md:README.md", + "sourceKind": "local_folder", + "rootUri": root_uri, + "actorId": "user_1", + "sessionId": "sess_mindmap_apply", + "runId": "run_mindmap_apply", + "toolCallId": "call_mindmap_apply", + "traceId": "trace_mindmap_apply", + "idempotencyKey": "idem_mindmap_apply", + "dryRun": false, + "args": { + "mindmapId": "maps/idea.mindmap.json", + "resourcePath": "maps/idea.mindmap.json", + "ops": [ + { "op": "updateText", "nodeId": "node_1", "text": "新分支" }, + { + "op": "insertChild", + "parentId": "root", + "node": { + "id": "node_new", + "text": "新子节点", + "metadata": { "sourceRefs": [{ "page": 1 }] } + } + } + ], + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/idea.mindmap.json"] + } + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + let status = response.status(); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(status, StatusCode::OK, "{payload}"); + assert_eq!( + payload["result"]["root"]["children"][0]["data"]["text"], + "新分支" + ); + assert_eq!( + payload["result"]["root"]["children"][1]["data"]["text"], + "新子节点" + ); + assert!(payload["result"]["changedFiles"] + .as_array() + .expect("changed files") + .iter() + .any(|value| value == "maps/idea.mindmap.json")); + + let written = fs::read_to_string(root.join("maps").join("idea.mindmap.json")) + .expect("written mindmap"); + let written: Value = serde_json::from_str(&written).expect("written json"); + assert_eq!(written["data"]["children"][0]["data"]["text"], "新分支"); + assert_eq!( + written["data"]["children"][0]["data"]["style"]["color"], + "red" + ); + assert_eq!(written["data"]["children"][1]["data"]["uid"], "node_new"); + assert_eq!(written["view"]["state"]["scale"], 2); + assert_eq!(written["unknownTop"]["keep"], true); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn hermes_tools_mindmap_apply_ops_rejects_stale_revision() { + let root = std::env::temp_dir().join(format!( + "mnote-resource-mindmap-conflict-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("maps")).expect("maps"); + fs::write( + root.join("maps").join("idea.mindmap.json"), + r#"{"data":{"data":{"text":"中心主题","uid":"root"},"children":[]}}"#, + ) + .expect("mindmap"); + let root_uri = format!("file://{}", root.display()); + crate::routes::local_folder_source::initialize_local_workspace_for_actor( + "user_1", &root_uri, + ) + .expect("workspace"); + + let response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "toolName": "mnote.mindmap.apply_ops", + "workspaceId": "local-ws-resource", + "documentId": "local-md:README.md", + "sourceKind": "local_folder", + "rootUri": root_uri, + "actorId": "user_1", + "sessionId": "sess_mindmap_conflict", + "runId": "run_mindmap_conflict", + "toolCallId": "call_mindmap_conflict", + "traceId": "trace_mindmap_conflict", + "idempotencyKey": "idem_mindmap_conflict", + "dryRun": false, + "args": { + "mindmapId": "maps/idea.mindmap.json", + "resourcePath": "maps/idea.mindmap.json", + "expectedRevision": "stale-revision", + "ops": [{ "op": "updateText", "nodeId": "root", "text": "改名" }], + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/idea.mindmap.json"] + } + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_resource_revision_conflict") + ); + let written = fs::read_to_string(root.join("maps").join("idea.mindmap.json")) + .expect("written mindmap"); + assert!(written.contains("中心主题")); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn hermes_tools_mindmap_apply_ops_deletes_child_node() { + let (root, root_uri) = write_mindmap_apply_fixture( + "delete-node", + json!({ + "data": { + "data": { "text": "中心主题", "uid": "root" }, + "children": [ + { "data": { "text": "保留节点", "uid": "keep" }, "children": [] }, + { "data": { "text": "删除节点", "uid": "remove_me" }, "children": [] } + ] + } + }), + ); + + let response = post_mindmap_apply_ops( + &root_uri, + false, + json!({ + "mindmapId": "maps/idea.mindmap.json", + "resourcePath": "maps/idea.mindmap.json", + "ops": [{ "op": "deleteNode", "nodeId": "remove_me" }], + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/idea.mindmap.json"] + } + }), + "delete_node", + ) + .await; + + let status = response.status(); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(status, StatusCode::OK, "{payload}"); + assert_eq!( + payload["result"]["root"]["children"] + .as_array() + .unwrap() + .len(), + 1 + ); + assert_eq!( + payload["result"]["root"]["children"][0]["data"]["uid"], + "keep" + ); + let written = + fs::read_to_string(root.join("maps").join("idea.mindmap.json")).expect("written"); + assert!(written.contains("保留节点")); + assert!(!written.contains("删除节点")); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn hermes_tools_mindmap_apply_ops_accepts_common_aliases() { + let (root, root_uri) = write_mindmap_apply_fixture( + "aliases", + json!({ + "data": { + "data": { "text": "中心主题", "uid": "root" }, + "children": [ + { "data": { "text": "旧标题", "uid": "child_1" }, "children": [] } + ] + } + }), + ); + + let response = post_mindmap_apply_ops( + &root_uri, + false, + json!({ + "mindmapId": "maps/idea.mindmap.json", + "resourcePath": "maps/idea.mindmap.json", + "ops": [ + { "type": "update_node", "id": "child_1", "title": "别名更新" }, + { "action": "add-child", "nodeId": "root", "id": "alias_child", "title": "别名新增" } + ], + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/idea.mindmap.json"] + } + }), + "aliases", + ) + .await; + + let status = response.status(); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(status, StatusCode::OK, "{payload}"); + assert_eq!( + payload["result"]["root"]["children"][0]["data"]["text"], + "别名更新" + ); + assert_eq!( + payload["result"]["root"]["children"][1]["data"]["uid"], + "alias_child" + ); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn hermes_tools_mindmap_apply_ops_dry_run_returns_diff_without_writing() { + let (root, root_uri) = write_mindmap_apply_fixture( + "dry-run", + json!({ + "data": { + "data": { "text": "中心主题", "uid": "root" }, + "children": [] + } + }), + ); + + let response = post_mindmap_apply_ops( + &root_uri, + true, + json!({ + "mindmapId": "maps/idea.mindmap.json", + "resourcePath": "maps/idea.mindmap.json", + "ops": [{ "op": "updateText", "nodeId": "root", "text": "dry-run 改名" }], + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/idea.mindmap.json"] + } + }), + "dry_run", + ) + .await; + + let status = response.status(); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(status, StatusCode::OK, "{payload}"); + assert_eq!(payload["result"]["dryRun"], true); + assert_eq!(payload["result"]["diff"][0]["op"], "mindmap.apply_ops"); + let written = + fs::read_to_string(root.join("maps").join("idea.mindmap.json")).expect("written"); + assert!(written.contains("中心主题")); + assert!(!written.contains("dry-run 改名")); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn hermes_tools_mindmap_apply_ops_rejects_unsupported_op() { + let (root, root_uri) = write_mindmap_apply_fixture( + "unsupported-op", + json!({ + "data": { + "data": { "text": "中心主题", "uid": "root" }, + "children": [] + } + }), + ); + + let response = post_mindmap_apply_ops( + &root_uri, + false, + json!({ + "mindmapId": "maps/idea.mindmap.json", + "resourcePath": "maps/idea.mindmap.json", + "ops": [{ "op": "moveNode", "nodeId": "root" }], + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/idea.mindmap.json"] + } + }), + "unsupported_op", + ) + .await; + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_resource_op_unsupported") + ); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn hermes_tools_mindmap_apply_ops_rejects_invalid_ops_payload() { + let (root, root_uri) = write_mindmap_apply_fixture( + "invalid-ops", + json!({ + "data": { + "data": { "text": "中心主题", "uid": "root" }, + "children": [] + } + }), + ); + + let response = post_mindmap_apply_ops( + &root_uri, + false, + json!({ + "mindmapId": "maps/idea.mindmap.json", + "resourcePath": "maps/idea.mindmap.json", + "ops": { "op": "updateText", "nodeId": "root", "text": "bad" }, + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/idea.mindmap.json"] + } + }), + "invalid_ops", + ) + .await; + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_resource_ops_invalid") + ); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn hermes_tools_mindmap_apply_ops_rejects_root_delete() { + let (root, root_uri) = write_mindmap_apply_fixture( + "root-delete", + json!({ + "data": { + "data": { "text": "中心主题", "uid": "root" }, + "children": [] + } + }), + ); + + let response = post_mindmap_apply_ops( + &root_uri, + false, + json!({ + "mindmapId": "maps/idea.mindmap.json", + "resourcePath": "maps/idea.mindmap.json", + "ops": [{ "op": "deleteNode", "nodeId": "root" }], + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/idea.mindmap.json"] + } + }), + "root_delete", + ) + .await; + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response + .headers() + .get("x-error-code") + .and_then(|value| value.to_str().ok()), + Some("mnote_resource_root_delete_forbidden") + ); + let written = + fs::read_to_string(root.join("maps").join("idea.mindmap.json")).expect("written"); + assert!(written.contains("中心主题")); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn hermes_tools_mindmap_create_from_outline_writes_default_envelope() { + let root = std::env::temp_dir().join(format!( + "mnote-resource-mindmap-create-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("maps")).expect("maps"); + let root_uri = format!("file://{}", root.display()); + crate::routes::local_folder_source::initialize_local_workspace_for_actor( + "user_1", &root_uri, + ) + .expect("workspace"); + + let response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "toolName": "mnote.mindmap.create_from_outline", + "workspaceId": "local-ws-resource", + "documentId": "local-md:README.md", + "sourceKind": "local_folder", + "rootUri": root_uri, + "actorId": "user_1", + "sessionId": "sess_mindmap_create", + "runId": "run_mindmap_create", + "toolCallId": "call_mindmap_create", + "traceId": "trace_mindmap_create", + "idempotencyKey": "idem_mindmap_create", + "dryRun": false, + "args": { + "mindmapId": "maps/generated.mindmap.json", + "resourcePath": "maps/generated.mindmap.json", + "embedIntoPage": false, + "title": "PDF 摘要导图", + "outline": [ + { + "text": "章节一", + "children": [ + { "text": "要点 1", "children": [] } + ] + } + ], + "sourceRefs": [ + { "kind": "pdf", "title": "source.pdf", "page": 1 } + ], + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/generated.mindmap.json"] + } + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + let status = response.status(); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(status, StatusCode::OK, "{payload}"); + assert_eq!(payload["result"]["resourceKind"], "mindmap"); + assert_eq!(payload["result"]["root"]["data"]["text"], "PDF 摘要导图"); + assert_eq!( + payload["result"]["root"]["children"][0]["data"]["text"], + "章节一" + ); + assert_eq!(payload["result"]["envelope"]["data"]["data"]["uid"], "root"); + assert!(root.join("maps").join("generated.mindmap.json").exists()); + + let written = fs::read_to_string(root.join("maps").join("generated.mindmap.json")) + .expect("written mindmap"); + let written: Value = serde_json::from_str(&written).expect("written json"); + assert_eq!(written["data"]["data"]["text"], "PDF 摘要导图"); + assert_eq!(written["data"]["children"][0]["data"]["text"], "章节一"); + assert!(written["view"]["transform"].is_object()); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn hermes_tools_mindmap_create_from_outline_can_embed_into_local_markdown_page() { + let root = std::env::temp_dir().join(format!( + "mnote-resource-mindmap-create-embed-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("maps")).expect("maps"); + fs::write(root.join("README.md"), "# README\n").expect("markdown"); + let root_uri = format!("file://{}", root.display()); + crate::routes::local_folder_source::initialize_local_workspace_for_actor( + "user_1", &root_uri, + ) + .expect("workspace"); + + let response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/hermes/tools/mnote/call") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_1") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "toolName": "mnote.mindmap.create_from_outline", + "workspaceId": "local-ws-resource", + "documentId": "local-md:README.md", + "sourceKind": "local_folder", + "rootUri": root_uri, + "actorId": "user_1", + "sessionId": "sess_mindmap_create_embed", + "runId": "run_mindmap_create_embed", + "toolCallId": "call_mindmap_create_embed", + "traceId": "trace_mindmap_create_embed", + "idempotencyKey": "idem_mindmap_create_embed", + "dryRun": false, + "args": { + "mindmapId": "maps/generated-embed.mindmap.json", + "resourcePath": "maps/generated-embed.mindmap.json", + "title": "PDF 摘要导图", + "outline": [ + { "text": "章节一", "children": [] } + ], + "embedIntoPage": true, + "aiAccessScope": { + "permissionLevel": "read_write", + "allowedResourceIds": ["maps/generated-embed.mindmap.json"] + } + } + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + let status = response.status(); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(status, StatusCode::OK, "{payload}"); + assert_eq!(payload["result"]["embedResult"]["status"], "embedded"); + assert!(payload["result"]["changedFiles"] + .as_array() + .expect("changed files") + .iter() + .any(|value| value == "README.md")); + let markdown = fs::read_to_string(root.join("README.md")).expect("markdown"); + assert!(markdown.contains("[PDF 摘要导图](maps/generated-embed.mindmap.json)")); + let _ = fs::remove_dir_all(&root); + } + #[tokio::test] async fn hermes_tools_office_fetch_and_propose_changes_do_not_write_file() { let root = std::env::temp_dir().join(format!( diff --git a/rust/crates/mnote-web/src/routes/local_folder_source.rs b/rust/crates/mnote-web/src/routes/local_folder_source.rs index 4a3352e5..f0ab6f0e 100644 --- a/rust/crates/mnote-web/src/routes/local_folder_source.rs +++ b/rust/crates/mnote-web/src/routes/local_folder_source.rs @@ -8,8 +8,8 @@ use crate::page_aggregate::{ use crate::routes::local_markdown_parser::{ file_stem_title, parse_markdown_attachment_refs, parse_markdown_page, split_frontmatter, }; -use crate::routes::local_search_index; use crate::routes::snapshot_support::ProjectionSnapshot; +use crate::routes::{local_ocr, local_search_index}; use axum::extract::{Extension, Multipart, Path as AxumPath, Query, State}; use axum::http::{header, HeaderMap, HeaderValue, StatusCode}; use axum::Json; @@ -4076,8 +4076,13 @@ fn local_mindmap_file_name(mindmap_id: &str) -> String { fn is_local_mindmap_file_name(file_name: &str) -> bool { let trimmed = file_name.trim(); let lower = trimmed.to_ascii_lowercase(); + if !lower.ends_with(".json") { + return false; + } lower.ends_with(".mindmap.json") - || (trimmed.starts_with("思维导图") && lower.ends_with(".json")) + || lower.starts_with("mindmap-") + || lower.starts_with("mindmap_") + || trimmed.starts_with("思维导图") } fn resolve_local_mindmap_path( @@ -7395,6 +7400,9 @@ fn scan_markdown_page_tree( continue; } if is_markdown_file(&entry.file_name) { + if local_ocr::is_local_ocr_sidecar_relative_path(&entry.relative_path) { + continue; + } let markdown = fs::read_to_string(&entry.path).unwrap_or_default(); let parsed = parse_markdown_page(&markdown, &entry.file_name); let page_id = local_markdown_path_page_id(&entry.relative_path); @@ -8426,7 +8434,6 @@ fn editor_blocks_to_markdown_with_rewrite( lines.push(text); } else { let src = rewrite_local_open_url_to_markdown_relative(src, local_file_context) - .map(|value| markdown_href_for_relative_path(&value)) .unwrap_or_else(|| src.to_string()); lines.push(format!( "![{}]({})", @@ -10424,6 +10431,61 @@ fn main() {} let _ = std::fs::remove_dir_all(&root); } + #[test] + fn local_markdown_generated_mindmap_id_roundtrips_as_mindmap_block() { + let root = temp_root("mnote-local-markdown-generated-mindmap-roundtrip"); + init_workspace(&root); + std::fs::create_dir_all(root.join("Page")).expect("create page dir"); + std::fs::write(root.join("Page").join("Page.md"), "").expect("write md"); + let root_uri = format!("file://{}", root.display()); + + write_local_mindmap_data( + &root_uri, + "local-md:Page~2FPage.md", + "mindmap-123456", + serde_json::json!({"data":{"text":"中心主题"},"children":[]}), + false, + ) + .expect("write generated mindmap"); + + save_local_markdown_page( + &root_uri, + "local-md:Page~2FPage.md", + None, + &serde_json::json!([ + { + "type": "mindmap", + "props": { + "name": "思维导图", + "mindmapId": "mindmap-123456", + "rootNodeId": "root" + } + } + ]), + ) + .expect("save"); + + let saved = std::fs::read_to_string(root.join("Page").join("Page.md")).expect("read md"); + assert!(saved.contains("[思维导图](mindmap-123456.json)")); + + let aggregate = resolve_local_markdown_page_aggregate(&root_uri, "local-md:Page~2FPage.md") + .expect("aggregate"); + let mindmap = aggregate.body.content.as_array().expect("blocks")[0].clone(); + assert_eq!(mindmap["type"], "mindmap"); + assert_eq!(mindmap["props"]["sourcePath"], "mindmap-123456.json"); + assert_eq!(mindmap["props"]["mindmapId"], "mindmap-123456.json"); + assert_eq!( + aggregate.body.block_document["blocks"][0]["attrs"]["mindmapId"], + serde_json::json!("mindmap-123456.json") + ); + assert_eq!( + aggregate.body.block_document["blocks"][0]["attrs"]["sourcePath"], + serde_json::json!("mindmap-123456.json") + ); + + let _ = std::fs::remove_dir_all(&root); + } + #[test] fn local_workspace_access_rejects_owner_mismatch() { let root = temp_root("mnote-local-workspace-owner-mismatch"); @@ -12656,6 +12718,8 @@ fn main() {} std::fs::create_dir_all(root.join("Page")).expect("create page dir"); std::fs::write(root.join("Page").join("Page.md"), "").expect("write md"); std::fs::write(root.join("Page").join("思维导图123456.json"), "{}").expect("write mindmap"); + std::fs::write(root.join("Page").join("mindmap-123456.json"), "{}") + .expect("write generated mindmap"); std::fs::write(root.join("Page").join("report.docx"), b"docx").expect("write docx"); std::fs::write(root.join("Page").join("sheet.xlsx"), b"xlsx").expect("write xlsx"); std::fs::write(root.join("Page").join("slides.pptx"), b"pptx").expect("write pptx"); @@ -12687,6 +12751,17 @@ fn main() {} mindmap["resourceMeta"]["workspacePath"]["objectIdentity"]["assetId"].as_str(), Some("local-file:Page/思维导图123456.json") ); + let generated_mindmap = items + .iter() + .find(|item| item["title"].as_str() == Some("mindmap-123456.json")) + .expect("generated mindmap row"); + assert_eq!(generated_mindmap["rowKind"].as_str(), Some("asset")); + assert_eq!(generated_mindmap["iconHint"].as_str(), Some("mindmap")); + assert_eq!( + generated_mindmap["resourceMeta"]["workspacePath"]["objectIdentity"]["objectKind"] + .as_str(), + Some("mindmap") + ); let office = items .iter() @@ -13239,6 +13314,41 @@ fn main() {} let _ = std::fs::remove_dir_all(&root); } + #[test] + fn local_ocr_sidecar_is_filetree_resource_not_page_tree_document() { + let root = temp_root("mnote-local-ocr-sidecar-page-tree"); + init_workspace(&root); + let root_uri = format!("file://{}", root.display()); + std::fs::create_dir_all(root.join("docs").join("Page.ocr")).expect("ocr dir"); + std::fs::write(root.join("docs").join("Page.md"), "# Page\n").expect("page"); + std::fs::write( + root.join("docs").join("Page.ocr").join("photo.png.ocr.md"), + "---\nmnote_ocr_version: 1\nprovider: mock\nmodel_version: vlm\nowner_document: ../Page.md\nsource_path: ./Page.assets/photo.png\nsource_root_relative_path: docs/Page.assets/photo.png\nsource_size: 3\nsource_mtime_ms: 1\nstatus: done\ncreated_at: 1\nupdated_at: 1\n---\n\nOCR-only-token\n", + ) + .expect("ocr markdown"); + + let file_tree = load_local_folder_file_tree_children_snapshot(&root_uri, "docs/Page.ocr") + .expect("file tree"); + let file_items = file_tree.projection["items"] + .as_array() + .expect("file items"); + assert!(file_items + .iter() + .any(|item| item["title"].as_str() == Some("photo.png.ocr.md"))); + + let page_tree = load_local_folder_page_tree_snapshot(&root_uri).expect("page tree"); + let page_items = page_tree.projection["items"] + .as_array() + .expect("page items"); + assert!(page_items + .iter() + .any(|item| item["documentId"].as_str() == Some("local-md:docs~2FPage.md"))); + assert!(!page_items.iter().any(|item| item["documentId"].as_str() + == Some("local-md:docs~2FPage.ocr~2Fphoto.png.ocr.md"))); + + let _ = std::fs::remove_dir_all(&root); + } + #[test] fn local_rename_markdown_page_renames_nested_bundle() { let root = temp_root("mnote-local-rename-nested-bundle"); diff --git a/rust/crates/mnote-web/src/routes/local_markdown_parser.rs b/rust/crates/mnote-web/src/routes/local_markdown_parser.rs index 7d50b83b..2982890c 100644 --- a/rust/crates/mnote-web/src/routes/local_markdown_parser.rs +++ b/rust/crates/mnote-web/src/routes/local_markdown_parser.rs @@ -915,10 +915,7 @@ fn link_mindmap<'a>(node: &'a AstNode<'a>) -> Option<(String, String)> { .and_then(|value| value.to_str()) .unwrap_or(target) .trim(); - let lower = file_name.to_ascii_lowercase(); - let is_mindmap = lower.ends_with(".mindmap.json") - || (file_name.starts_with("思维导图") && lower.ends_with(".json")); - if !is_mindmap { + if !is_local_mindmap_file_name(file_name) { return None; } let name = collect_plain_text(node).trim().to_string(); @@ -932,6 +929,18 @@ fn link_mindmap<'a>(node: &'a AstNode<'a>) -> Option<(String, String)> { )) } +fn is_local_mindmap_file_name(file_name: &str) -> bool { + let trimmed = file_name.trim(); + let lower = trimmed.to_ascii_lowercase(); + if !lower.ends_with(".json") { + return false; + } + lower.ends_with(".mindmap.json") + || lower.starts_with("mindmap-") + || lower.starts_with("mindmap_") + || trimmed.starts_with("思维导图") +} + fn markdown_ast_document_to_blocks(document: &MarkdownAstDocument) -> Value { Value::Array( document @@ -1239,6 +1248,25 @@ mod tests { assert_eq!(first["props"]["alt"].as_str(), Some("示例图片")); } + #[test] + fn markdown_mindmap_link_parses_generated_mindmap_json_as_mindmap_block() { + let blocks = markdown_to_blocks("[思维导图](mindmap-123456.json)\n"); + let first = blocks + .as_array() + .and_then(|items| items.first()) + .expect("first block"); + + assert_eq!(first["type"].as_str(), Some("mindmap")); + assert_eq!( + first["props"]["sourcePath"].as_str(), + Some("mindmap-123456.json") + ); + assert_eq!( + first["props"]["mindmapId"].as_str(), + Some("mindmap-123456.json") + ); + } + #[test] fn markdown_attachment_refs_parse_standard_href_variants() { let root = std::env::temp_dir().join(format!( diff --git a/rust/crates/mnote-web/src/routes/local_ocr.rs b/rust/crates/mnote-web/src/routes/local_ocr.rs new file mode 100644 index 00000000..dca480bc --- /dev/null +++ b/rust/crates/mnote-web/src/routes/local_ocr.rs @@ -0,0 +1,1881 @@ +use crate::app::AppState; +use crate::context::RequestContext; +use crate::error::WebError; +use crate::routes::local_folder_source::{ + decode_local_id_segment, encode_local_id_segment, + ensure_local_workspace_read_access_with_state, ensure_local_workspace_write_access_with_state, +}; +use axum::extract::{Extension, Query, State}; +use axum::http::{HeaderMap, StatusCode}; +use axum::Json; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::collections::{BTreeMap, HashMap}; +use std::fs; +use std::hash::{Hash, Hasher}; +use std::io::{Cursor, Read}; +use std::path::{Component, Path, PathBuf}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +const OCR_INDEX_VERSION: u32 = 1; +const DEFAULT_PROVIDER: &str = "mineru"; +const DEFAULT_MODEL_VERSION: &str = "vlm"; +const DEFAULT_MINERU_API_BASE_URL: &str = "https://mineru.net"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct OcrJobRequest { + root_uri: String, + document_id: String, + #[serde(default)] + source_path: Option, + source_root_relative_path: String, + #[serde(default)] + provider: Option, + #[serde(default)] + force: bool, + #[serde(default)] + mock_markdown: Option, + #[serde(default)] + mock_error: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct OcrStatusQuery { + root_uri: String, + source_root_relative_path: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct OcrReadQuery { + root_uri: String, + ocr_root_relative_path: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct OcrInsertRequest { + root_uri: String, + document_id: String, + ocr_root_relative_path: String, + #[serde(default)] + mode: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct OcrIndex { + version: u32, + #[serde(default)] + entries: BTreeMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct OcrIndexEntry { + pub(crate) job_id: String, + pub(crate) owner_document_id: String, + pub(crate) owner_document_path: String, + pub(crate) source_root_relative_path: String, + pub(crate) ocr_root_relative_path: String, + pub(crate) provider: String, + pub(crate) model_version: String, + pub(crate) status: String, + pub(crate) source_size: u64, + pub(crate) source_mtime_ms: u128, + pub(crate) created_at_ms: u128, + pub(crate) updated_at_ms: u128, + #[serde(default)] + pub(crate) plain_text_preview: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) error: Option, +} + +#[derive(Debug, Clone)] +struct SourceMetadata { + size: u64, + mtime_ms: u128, +} + +#[derive(Debug, Clone)] +struct MineruClientConfig { + api_base_url: String, + token: String, + model_version: String, + poll_interval: Duration, + max_polls: usize, +} + +#[derive(Debug, Clone)] +struct OcrSidecarPlan { + owner_document_path: String, + source_root_relative_path: String, + ocr_root_relative_path: String, + ocr_path: PathBuf, + provider: String, + model_version: String, + source_size: u64, + source_mtime_ms: u128, +} + +#[derive(Debug, Clone)] +pub(crate) struct OcrFrontmatter { + pub provider: String, + pub owner_document: String, + pub source_path: String, + pub source_root_relative_path: String, + pub source_size: u64, + pub source_mtime_ms: u128, + pub status: String, +} + +pub(crate) async fn create_job( + State(state): State, + Extension(context): Extension, + Json(body): Json, +) -> Result<(StatusCode, HeaderMap, Json), WebError> { + let root_uri = body.root_uri.trim(); + let root = ensure_local_workspace_write_access_with_state(&state, &context, root_uri) + .map_err(|error| error.with_context(&context))?; + let provider = body + .provider + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or(DEFAULT_PROVIDER); + let token = if provider != "mock" { + Some(mineru_token().ok_or_else(|| { + WebError::new( + StatusCode::UNAUTHORIZED, + "mineru_token_missing", + "缺少 MinerU API token", + ) + .with_context(&context) + })?) + } else { + None + }; + if provider != "mock" && token.is_none() { + return Err(WebError::new( + StatusCode::UNAUTHORIZED, + "mineru_token_missing", + "缺少 MinerU API token", + ) + .with_context(&context)); + } + let source_relative = body.source_root_relative_path.trim().replace('\\', "/"); + let _source_path_hint = body + .source_path + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or(&source_relative); + let plan = plan_ocr_sidecar_path( + &root, + &body.document_id, + &source_relative, + provider, + DEFAULT_MODEL_VERSION, + body.force, + )?; + let now = now_ms(); + if let Some(error) = body.mock_error.as_deref() { + let entry = build_index_entry(&plan, "failed", now, "", Some(redact_error(error))); + upsert_ocr_index_entry(&root, entry.clone())?; + return Ok(ok_json( + &context, + json!({ + "ok": true, + "job": ocr_job_payload(&root, &entry), + }), + )); + } + let markdown = if provider == "mock" { + body.mock_markdown + .unwrap_or_else(|| format!("OCR mock result for {}", plan.source_root_relative_path)) + } else { + match run_mineru_ocr(&root, &plan, token.unwrap_or_default()).await { + Ok(markdown) => markdown, + Err(error) => { + let failed_entry = + build_index_entry(&plan, "failed", now, "", Some(redact_error(error.message()))); + upsert_ocr_index_entry(&root, failed_entry)?; + return Err(error.with_context(&context)); + } + } + }; + write_ocr_sidecar(&plan, &markdown, now)?; + let entry = build_index_entry(&plan, "done", now, &markdown, None); + upsert_ocr_index_entry(&root, entry.clone())?; + Ok(ok_json( + &context, + json!({ + "ok": true, + "job": ocr_job_payload(&root, &entry), + }), + )) +} + +pub(crate) async fn list_jobs( + State(state): State, + Extension(context): Extension, + Query(query): Query, +) -> Result<(StatusCode, HeaderMap, Json), WebError> { + let root = + ensure_local_workspace_read_access_with_state(&state, &context, query.root_uri.trim()) + .map_err(|error| error.with_context(&context))?; + let index = read_ocr_index(&root)?; + let jobs = index + .entries + .values() + .map(|entry| ocr_job_payload(&root, entry)) + .collect::>(); + Ok(ok_json(&context, json!({ "ok": true, "jobs": jobs }))) +} + +pub(crate) async fn status( + State(state): State, + Extension(context): Extension, + Query(query): Query, +) -> Result<(StatusCode, HeaderMap, Json), WebError> { + let root = + ensure_local_workspace_read_access_with_state(&state, &context, query.root_uri.trim()) + .map_err(|error| error.with_context(&context))?; + let source = query + .source_root_relative_path + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .ok_or_else(|| { + WebError::bad_request_code("local_ocr_source_required", "OCR 状态查询缺少来源路径") + .with_context(&context) + })?; + let index = read_ocr_index(&root)?; + let job = index + .entries + .get(source) + .map(|entry| ocr_job_payload(&root, entry)); + Ok(ok_json(&context, json!({ "ok": true, "job": job }))) +} + +pub(crate) async fn read( + State(state): State, + Extension(context): Extension, + Query(query): Query, +) -> Result<(StatusCode, HeaderMap, Json), WebError> { + let root = + ensure_local_workspace_read_access_with_state(&state, &context, query.root_uri.trim()) + .map_err(|error| error.with_context(&context))?; + let relative = normalize_relative_path(&query.ocr_root_relative_path)?; + if !is_local_ocr_sidecar_relative_path(&relative) { + return Err(WebError::bad_request_code( + "local_ocr_sidecar_invalid", + "OCR 读取目标不是 OCR sidecar", + ) + .with_context(&context)); + } + let path = root.join(&relative); + ensure_target_under_root(&root, &path, "local_ocr_read_root_escape")?; + let markdown = fs::read_to_string(&path).map_err(|error| { + WebError::bad_request_code( + "local_ocr_read_failed", + format!("无法读取 OCR Markdown {}: {error}", path.display()), + ) + .with_context(&context) + })?; + let frontmatter = parse_ocr_frontmatter(&markdown); + Ok(ok_json( + &context, + json!({ + "ok": true, + "markdown": markdown, + "frontmatter": frontmatter.map(ocr_frontmatter_payload), + "ocrRootRelativePath": relative, + }), + )) +} + +pub(crate) async fn insert( + State(state): State, + Extension(context): Extension, + Json(body): Json, +) -> Result<(StatusCode, HeaderMap, Json), WebError> { + let root = + ensure_local_workspace_write_access_with_state(&state, &context, body.root_uri.trim()) + .map_err(|error| error.with_context(&context))?; + let mode = body + .mode + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("link"); + if mode != "link" { + return Err(WebError::bad_request_code( + "local_ocr_insert_mode_unsupported", + "当前仅支持插入 OCR 链接", + ) + .with_context(&context)); + } + let owner_document_path = owner_document_path_from_id(&body.document_id)?; + let owner_path = root.join(&owner_document_path); + ensure_target_under_root(&root, &owner_path, "local_ocr_owner_root_escape") + .map_err(|error| error.with_context(&context))?; + if !owner_path.is_file() { + return Err(WebError::bad_request_code( + "local_ocr_owner_missing", + "OCR owner Markdown 不存在", + ) + .with_context(&context)); + } + let ocr_root_relative_path = normalize_relative_path(&body.ocr_root_relative_path)?; + if !is_local_ocr_sidecar_relative_path(&ocr_root_relative_path) { + return Err(WebError::bad_request_code( + "local_ocr_sidecar_invalid", + "OCR 插入目标不是 OCR sidecar", + ) + .with_context(&context)); + } + let ocr_path = root.join(&ocr_root_relative_path); + ensure_target_under_root(&root, &ocr_path, "local_ocr_read_root_escape") + .map_err(|error| error.with_context(&context))?; + if !ocr_path.is_file() { + return Err( + WebError::bad_request_code("local_ocr_read_failed", "OCR Markdown 不存在") + .with_context(&context), + ); + } + let current = fs::read_to_string(&owner_path).map_err(|error| { + WebError::bad_request_code( + "local_ocr_owner_read_failed", + format!( + "无法读取 OCR owner Markdown {}: {error}", + owner_path.display() + ), + ) + .with_context(&context) + })?; + let link_target = relative_from_owner_dir(&owner_document_path, &ocr_root_relative_path); + let label = ocr_link_label(&ocr_root_relative_path); + let inserted_markdown = format!("[OCR:{}]({})", label, link_target); + let mut next = current.trim_end_matches('\n').to_string(); + if next.is_empty() { + next.push_str(&inserted_markdown); + next.push('\n'); + } else { + next.push_str("\n\n"); + next.push_str(&inserted_markdown); + next.push('\n'); + } + fs::write(&owner_path, next).map_err(|error| { + WebError::bad_request_code( + "local_ocr_owner_write_failed", + format!( + "无法写入 OCR owner Markdown {}: {error}", + owner_path.display() + ), + ) + .with_context(&context) + })?; + Ok(ok_json( + &context, + json!({ + "ok": true, + "documentId": body.document_id, + "ownerDocumentPath": owner_document_path, + "ocrRootRelativePath": ocr_root_relative_path, + "insertedMarkdown": inserted_markdown, + }), + )) +} + +pub(crate) fn is_local_ocr_sidecar_relative_path(relative_path: &str) -> bool { + let normalized = relative_path.trim().replace('\\', "/"); + let path = Path::new(&normalized); + let Some(file_name) = path.file_name().and_then(|value| value.to_str()) else { + return false; + }; + let Some(parent_name) = path + .parent() + .and_then(|value| value.file_name()) + .and_then(|value| value.to_str()) + else { + return false; + }; + parent_name.ends_with(".ocr") && file_name.ends_with(".ocr.md") +} + +pub(crate) fn parse_ocr_frontmatter(markdown: &str) -> Option { + let trimmed = markdown.strip_prefix("---\n")?; + let end = trimmed.find("\n---")?; + let frontmatter = &trimmed[..end]; + let mut fields = HashMap::::new(); + for line in frontmatter.lines() { + let Some((key, value)) = line.split_once(':') else { + continue; + }; + fields.insert( + key.trim().to_string(), + value.trim().trim_matches('"').to_string(), + ); + } + if fields.get("mnote_ocr_version").map(String::as_str) != Some("1") { + return None; + } + Some(OcrFrontmatter { + provider: fields.get("provider")?.to_string(), + owner_document: fields.get("owner_document")?.to_string(), + source_path: fields.get("source_path")?.to_string(), + source_root_relative_path: fields.get("source_root_relative_path")?.to_string(), + source_size: fields.get("source_size")?.parse().ok()?, + source_mtime_ms: fields.get("source_mtime_ms")?.parse().ok()?, + status: fields.get("status")?.to_string(), + }) +} + +#[cfg(test)] +fn read_ocr_index_entry( + root: &Path, + source_root_relative_path: &str, +) -> Result, WebError> { + let source = normalize_relative_path(source_root_relative_path)?; + Ok(read_ocr_index(root)?.entries.remove(&source)) +} + +pub(crate) fn ocr_index_entries(root: &Path) -> Result, WebError> { + Ok(read_ocr_index(root)?.entries.into_values().collect()) +} + +pub(crate) fn strip_ocr_frontmatter(markdown: &str) -> &str { + let Some(trimmed) = markdown.strip_prefix("---\n") else { + return markdown; + }; + let Some(end) = trimmed.find("\n---") else { + return markdown; + }; + trimmed[end + 4..].trim_start_matches('\n') +} + +async fn run_mineru_ocr(root: &Path, plan: &OcrSidecarPlan, token: String) -> Result { + let config = MineruClientConfig { + api_base_url: mineru_api_base_url(), + token, + model_version: plan.model_version.clone(), + poll_interval: mineru_poll_interval(), + max_polls: mineru_max_polls(), + }; + let source_path = root.join(&plan.source_root_relative_path); + let bytes = fs::read(&source_path).map_err(|error| { + WebError::bad_request_code( + "mineru_source_read_failed", + format!( + "无法读取 MinerU OCR 来源文件 {}: {error}", + source_path.display() + ), + ) + })?; + let file_name = Path::new(&plan.source_root_relative_path) + .file_name() + .and_then(|value| value.to_str()) + .unwrap_or("source.pdf"); + let client = reqwest::Client::new(); + let upload = create_mineru_upload_task(&client, &config, file_name).await?; + upload_mineru_source(&client, &upload.upload_url, bytes).await?; + let zip_url = poll_mineru_result_zip_url(&client, &config, &upload.batch_id).await?; + let zip_bytes = download_mineru_result_zip(&client, &zip_url).await?; + extract_mineru_markdown_from_zip(&zip_bytes) +} + +#[derive(Debug)] +struct MineruUploadTask { + batch_id: String, + upload_url: String, +} + +async fn create_mineru_upload_task( + client: &reqwest::Client, + config: &MineruClientConfig, + file_name: &str, +) -> Result { + let url = format!( + "{}/api/v4/file-urls/batch", + config.api_base_url.trim_end_matches('/') + ); + let payload = json!({ + "files": [{ "name": file_name, "data_id": short_hash(file_name) }], + "model_version": config.model_version, + }); + let response = client + .post(&url) + .bearer_auth(&config.token) + .json(&payload) + .send() + .await + .map_err(|error| { + WebError::bad_gateway_code( + "mineru_upload_task_failed", + format!("MinerU 上传任务创建失败: {}", redact_error(&error.to_string())), + ) + })?; + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + if !status.is_success() { + return Err(WebError::bad_gateway_code( + "mineru_upload_task_http_failed", + format!("MinerU 上传任务创建失败: HTTP {}", status.as_u16()), + )); + } + let value = parse_mineru_json(&body, "mineru_upload_task_json_invalid")?; + let batch_id = find_json_string_by_keys(&value, &["batch_id", "batchId", "id"]) + .ok_or_else(|| { + WebError::bad_gateway_code( + "mineru_upload_task_batch_missing", + "MinerU 上传任务响应缺少 batch_id", + ) + })?; + let upload_url = find_upload_url(&value).ok_or_else(|| { + WebError::bad_gateway_code( + "mineru_upload_url_missing", + "MinerU 上传任务响应缺少 upload_url", + ) + })?; + Ok(MineruUploadTask { + batch_id, + upload_url, + }) +} + +async fn upload_mineru_source( + client: &reqwest::Client, + upload_url: &str, + bytes: Vec, +) -> Result<(), WebError> { + let response = client + .put(upload_url) + .body(bytes) + .send() + .await + .map_err(|error| { + WebError::bad_gateway_code( + "mineru_source_upload_failed", + format!("MinerU 源文件上传失败: {}", redact_error(&error.to_string())), + ) + })?; + if !response.status().is_success() { + return Err(WebError::bad_gateway_code( + "mineru_source_upload_http_failed", + format!("MinerU 源文件上传失败: HTTP {}", response.status().as_u16()), + )); + } + Ok(()) +} + +async fn poll_mineru_result_zip_url( + client: &reqwest::Client, + config: &MineruClientConfig, + batch_id: &str, +) -> Result { + let url = format!( + "{}/api/v4/extract-results/batch/{}", + config.api_base_url.trim_end_matches('/'), + batch_id + ); + for _ in 0..config.max_polls.max(1) { + let response = client + .get(&url) + .bearer_auth(&config.token) + .send() + .await + .map_err(|error| { + WebError::bad_gateway_code( + "mineru_result_poll_failed", + format!("MinerU 结果轮询失败: {}", redact_error(&error.to_string())), + ) + })?; + let status = response.status(); + let body = response.text().await.unwrap_or_default(); + if !status.is_success() { + return Err(WebError::bad_gateway_code( + "mineru_result_poll_http_failed", + format!("MinerU 结果轮询失败: HTTP {}", status.as_u16()), + )); + } + let value = parse_mineru_json(&body, "mineru_result_poll_json_invalid")?; + if let Some(error_message) = mineru_result_error(&value) { + return Err(WebError::bad_gateway_code( + "mineru_result_failed", + format!("MinerU 识别失败: {}", redact_error(&error_message)), + )); + } + if let Some(zip_url) = find_json_string_by_keys( + &value, + &["full_zip_url", "fullZipUrl", "zip_url", "zipUrl", "result_url", "resultUrl"], + ) { + return Ok(zip_url); + } + tokio::time::sleep(config.poll_interval).await; + } + Err(WebError::gateway_timeout_code( + "mineru_result_poll_timeout", + "MinerU 识别结果轮询超时", + )) +} + +async fn download_mineru_result_zip( + client: &reqwest::Client, + zip_url: &str, +) -> Result, WebError> { + let response = client.get(zip_url).send().await.map_err(|error| { + WebError::bad_gateway_code( + "mineru_result_download_failed", + format!("MinerU 结果包下载失败: {}", redact_error(&error.to_string())), + ) + })?; + if !response.status().is_success() { + return Err(WebError::bad_gateway_code( + "mineru_result_download_http_failed", + format!("MinerU 结果包下载失败: HTTP {}", response.status().as_u16()), + )); + } + response.bytes().await.map(|bytes| bytes.to_vec()).map_err(|error| { + WebError::bad_gateway_code( + "mineru_result_download_failed", + format!("MinerU 结果包读取失败: {}", redact_error(&error.to_string())), + ) + }) +} + +fn extract_mineru_markdown_from_zip(bytes: &[u8]) -> Result { + let cursor = Cursor::new(bytes); + let mut archive = zip::ZipArchive::new(cursor).map_err(|error| { + WebError::bad_gateway_code( + "mineru_result_zip_invalid", + format!("MinerU 结果包不是合法 zip: {error}"), + ) + })?; + let mut candidates = Vec::<(String, String)>::new(); + for index in 0..archive.len() { + let mut file = archive.by_index(index).map_err(|error| { + WebError::bad_gateway_code( + "mineru_result_zip_read_failed", + format!("MinerU 结果包读取失败: {error}"), + ) + })?; + let name = file.name().replace('\\', "/"); + if !name.to_ascii_lowercase().ends_with(".md") || name.contains("/.") { + continue; + } + let mut markdown = String::new(); + file.read_to_string(&mut markdown).map_err(|error| { + WebError::bad_gateway_code( + "mineru_result_markdown_read_failed", + format!("MinerU Markdown 读取失败: {error}"), + ) + })?; + candidates.push((name, markdown)); + } + candidates + .into_iter() + .max_by_key(|(name, markdown)| { + let preferred = name.ends_with("/full.md") || name == "full.md" || name.ends_with("/result.md"); + (preferred, markdown.len()) + }) + .map(|(_, markdown)| markdown) + .filter(|markdown| !markdown.trim().is_empty()) + .ok_or_else(|| { + WebError::bad_gateway_code( + "mineru_result_markdown_missing", + "MinerU 结果包中缺少 Markdown 文件", + ) + }) +} + +fn parse_mineru_json(body: &str, code: &'static str) -> Result { + serde_json::from_str::(body).map_err(|error| { + WebError::bad_gateway_code(code, format!("MinerU 响应 JSON 解析失败: {error}")) + }) +} + +fn find_upload_url(value: &Value) -> Option { + if let Some(url) = find_json_string_by_keys(value, &["upload_url", "uploadUrl"]) { + return Some(url); + } + None +} + +fn find_json_string_by_keys(value: &Value, keys: &[&str]) -> Option { + match value { + Value::Object(map) => { + for key in keys { + if let Some(text) = map.get(*key).and_then(Value::as_str) { + let trimmed = text.trim(); + if !trimmed.is_empty() { + return Some(trimmed.to_string()); + } + } + } + for nested in map.values() { + if let Some(found) = find_json_string_by_keys(nested, keys) { + return Some(found); + } + } + None + } + Value::Array(items) => items + .iter() + .find_map(|item| find_json_string_by_keys(item, keys)), + _ => None, + } +} + +fn mineru_result_error(value: &Value) -> Option { + let status = find_json_string_by_keys(value, &["state", "status"]) + .unwrap_or_default() + .to_ascii_lowercase(); + if matches!( + status.as_str(), + "failed" | "fail" | "error" | "interrupted" | "canceled" | "cancelled" + ) { + return find_json_string_by_keys(value, &["error", "message", "msg", "err_msg", "errMsg"]) + .or_else(|| Some(status)); + } + None +} + +fn ok_json(context: &RequestContext, result: Value) -> (StatusCode, HeaderMap, Json) { + let mut headers = HeaderMap::new(); + if let Ok(value) = axum::http::HeaderValue::from_str(&context.trace.request_id) { + headers.insert("x-request-id", value); + } + (StatusCode::OK, headers, Json(result)) +} + +fn plan_ocr_sidecar_path( + root: &Path, + document_id: &str, + source_root_relative_path: &str, + provider: &str, + model_version: &str, + force: bool, +) -> Result { + let owner_document_path = owner_document_path_from_id(document_id)?; + let source_root_relative_path = normalize_relative_path(source_root_relative_path)?; + let source_path = root.join(&source_root_relative_path); + ensure_target_under_root(root, &source_path, "local_ocr_source_root_escape")?; + if !source_path.is_file() { + return Err(WebError::bad_request_code( + "local_ocr_source_missing", + "OCR 来源文件不存在", + )); + } + if !is_supported_ocr_source(&source_path) { + return Err(WebError::bad_request_code( + "local_ocr_source_type_unsupported", + "OCR 仅支持图片和 PDF", + )); + } + let owner_path = root.join(&owner_document_path); + ensure_target_under_root(root, &owner_path, "local_ocr_owner_root_escape")?; + if !owner_path.is_file() { + return Err(WebError::bad_request_code( + "local_ocr_owner_missing", + "OCR owner Markdown 不存在", + )); + } + let metadata = fs::metadata(&source_path).map_err(|error| { + WebError::bad_request_code( + "local_ocr_source_stat_failed", + format!( + "无法读取 OCR 来源文件状态 {}: {error}", + source_path.display() + ), + ) + })?; + let source_metadata = SourceMetadata { + size: metadata.len(), + mtime_ms: metadata + .modified() + .ok() + .and_then(|value| value.duration_since(UNIX_EPOCH).ok()) + .map(|value| value.as_millis()) + .unwrap_or_default(), + }; + let owner_parent = Path::new(&owner_document_path) + .parent() + .map(Path::to_path_buf) + .unwrap_or_default(); + let owner_stem = Path::new(&owner_document_path) + .file_stem() + .and_then(|value| value.to_str()) + .unwrap_or("Page"); + let ocr_dir = owner_parent.join(format!("{owner_stem}.ocr")); + let source_leaf = Path::new(&source_root_relative_path) + .file_name() + .and_then(|value| value.to_str()) + .unwrap_or("source"); + let base_file_name = format!("{source_leaf}.ocr.md"); + let mut ocr_relative = ocr_dir.join(&base_file_name); + let default_path = root.join(&ocr_relative); + if !force && default_path.exists() { + let suffix = short_hash(&format!( + "{}:{}:{}", + source_root_relative_path, source_metadata.size, source_metadata.mtime_ms + )); + ocr_relative = ocr_dir.join(format!("{source_leaf}-{suffix}.ocr.md")); + } + let ocr_root_relative_path = ocr_relative.to_string_lossy().replace('\\', "/"); + let ocr_path = root.join(&ocr_root_relative_path); + ensure_target_under_root(root, &ocr_path, "local_ocr_sidecar_root_escape")?; + Ok(OcrSidecarPlan { + owner_document_path, + source_root_relative_path, + ocr_root_relative_path, + ocr_path, + provider: provider.to_string(), + model_version: model_version.to_string(), + source_size: source_metadata.size, + source_mtime_ms: source_metadata.mtime_ms, + }) +} + +fn write_ocr_sidecar( + plan: &OcrSidecarPlan, + markdown_body: &str, + now: u128, +) -> Result<(), WebError> { + if let Some(parent) = plan.ocr_path.parent() { + fs::create_dir_all(parent).map_err(|error| { + WebError::bad_request_code( + "local_ocr_sidecar_create_failed", + format!("无法创建 OCR sidecar 目录 {}: {error}", parent.display()), + ) + })?; + } + let content = build_ocr_markdown(plan, markdown_body, "done", now); + fs::write(&plan.ocr_path, content).map_err(|error| { + WebError::bad_request_code( + "local_ocr_sidecar_write_failed", + format!("无法写入 OCR Markdown {}: {error}", plan.ocr_path.display()), + ) + }) +} + +fn build_ocr_markdown( + plan: &OcrSidecarPlan, + markdown_body: &str, + status: &str, + timestamp_ms: u128, +) -> String { + let owner_document = + relative_from_sidecar_dir(&plan.ocr_root_relative_path, &plan.owner_document_path); + let source_path = + relative_from_owner_dir(&plan.owner_document_path, &plan.source_root_relative_path); + let timestamp = timestamp_ms.to_string(); + format!( + "---\nmnote_ocr_version: 1\nprovider: {}\nmodel_version: {}\nowner_document: {}\nsource_path: {}\nsource_root_relative_path: {}\nsource_size: {}\nsource_mtime_ms: {}\nstatus: {}\ncreated_at: {}\nupdated_at: {}\n---\n\n{}\n", + plan.provider, + plan.model_version, + owner_document, + source_path, + plan.source_root_relative_path, + plan.source_size, + plan.source_mtime_ms, + status, + timestamp, + timestamp, + markdown_body.trim_end() + ) +} + +fn build_index_entry( + plan: &OcrSidecarPlan, + status: &str, + now: u128, + markdown: &str, + error: Option, +) -> OcrIndexEntry { + OcrIndexEntry { + job_id: format!( + "ocr_{}_{}", + now, + short_hash(&plan.source_root_relative_path) + ), + owner_document_id: format!( + "local-md:{}", + encode_local_id_segment(&plan.owner_document_path) + ), + owner_document_path: plan.owner_document_path.clone(), + source_root_relative_path: plan.source_root_relative_path.clone(), + ocr_root_relative_path: plan.ocr_root_relative_path.clone(), + provider: plan.provider.clone(), + model_version: plan.model_version.clone(), + status: status.to_string(), + source_size: plan.source_size, + source_mtime_ms: plan.source_mtime_ms, + created_at_ms: now, + updated_at_ms: now, + plain_text_preview: markdown.chars().take(240).collect(), + error, + } +} + +fn read_ocr_index(root: &Path) -> Result { + let path = ocr_index_path(root); + if !path.exists() { + return Ok(OcrIndex { + version: OCR_INDEX_VERSION, + entries: BTreeMap::new(), + }); + } + let content = fs::read_to_string(&path).map_err(|error| { + WebError::bad_request_code( + "local_ocr_index_read_failed", + format!("无法读取 OCR 索引 {}: {error}", path.display()), + ) + })?; + serde_json::from_str::(&content).map_err(|error| { + WebError::bad_request_code( + "local_ocr_index_invalid", + format!("OCR 索引格式非法 {}: {error}", path.display()), + ) + }) +} + +fn write_ocr_index(root: &Path, index: &OcrIndex) -> Result<(), WebError> { + let path = ocr_index_path(root); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).map_err(|error| { + WebError::bad_request_code( + "local_ocr_index_create_failed", + format!("无法创建 OCR 索引目录 {}: {error}", parent.display()), + ) + })?; + } + let content = serde_json::to_vec_pretty(index) + .map_err(|error| WebError::internal(format!("OCR 索引序列化失败: {error}")))?; + let tmp = path.with_extension("json.tmp"); + fs::write(&tmp, [&content[..], b"\n"].concat()).map_err(|error| { + WebError::bad_request_code( + "local_ocr_index_write_failed", + format!("无法写入 OCR 临时索引 {}: {error}", tmp.display()), + ) + })?; + fs::rename(&tmp, &path).map_err(|error| { + WebError::bad_request_code( + "local_ocr_index_write_failed", + format!("无法替换 OCR 索引 {}: {error}", path.display()), + ) + }) +} + +fn upsert_ocr_index_entry(root: &Path, entry: OcrIndexEntry) -> Result<(), WebError> { + let mut index = read_ocr_index(root)?; + index.version = OCR_INDEX_VERSION; + index + .entries + .insert(entry.source_root_relative_path.clone(), entry); + write_ocr_index(root, &index) +} + +fn ocr_job_payload(root: &Path, entry: &OcrIndexEntry) -> Value { + let stale = source_is_stale(root, entry); + json!({ + "jobId": entry.job_id, + "ownerDocumentId": entry.owner_document_id, + "ownerDocumentPath": entry.owner_document_path, + "sourceRootRelativePath": entry.source_root_relative_path, + "ocrRootRelativePath": entry.ocr_root_relative_path, + "provider": entry.provider, + "modelVersion": entry.model_version, + "status": if stale && entry.status == "done" { "stale" } else { entry.status.as_str() }, + "stageLabel": stage_label(if stale && entry.status == "done" { "stale" } else { entry.status.as_str() }), + "stale": stale, + "updatedAtMs": entry.updated_at_ms, + "finishedAtMs": if matches!(entry.status.as_str(), "done" | "failed") { Some(entry.updated_at_ms) } else { None }, + "plainTextPreview": entry.plain_text_preview, + "error": entry.error, + }) +} + +fn source_is_stale(root: &Path, entry: &OcrIndexEntry) -> bool { + let source = root.join(&entry.source_root_relative_path); + let Ok(metadata) = fs::metadata(source) else { + return true; + }; + let mtime_ms = metadata + .modified() + .ok() + .and_then(|value| value.duration_since(UNIX_EPOCH).ok()) + .map(|value| value.as_millis()) + .unwrap_or_default(); + metadata.len() != entry.source_size || mtime_ms != entry.source_mtime_ms +} + +fn stage_label(status: &str) -> &'static str { + match status { + "queued" => "排队中", + "uploading" => "上传中", + "mineru_processing" => "识别中", + "downloading" => "下载中", + "writing_sidecar" => "写入中", + "done" => "已识别", + "failed" => "识别失败", + "interrupted" => "已中断", + "stale" => "来源已变化", + _ => "未知状态", + } +} + +fn ocr_frontmatter_payload(frontmatter: OcrFrontmatter) -> Value { + json!({ + "provider": frontmatter.provider, + "ownerDocument": frontmatter.owner_document, + "sourcePath": frontmatter.source_path, + "sourceRootRelativePath": frontmatter.source_root_relative_path, + "sourceSize": frontmatter.source_size, + "sourceMtimeMs": frontmatter.source_mtime_ms, + "status": frontmatter.status, + }) +} + +fn ocr_index_path(root: &Path) -> PathBuf { + root.join(".mnote").join("ocr-index.json") +} + +fn owner_document_path_from_id(document_id: &str) -> Result { + let encoded = document_id + .trim() + .strip_prefix("local-md:") + .ok_or_else(|| { + WebError::bad_request_code( + "local_ocr_owner_document_invalid", + "OCR owner documentId 必须是 local-md 路径型 ID", + ) + })?; + let decoded = decode_local_id_segment(encoded)?; + normalize_relative_path(&decoded) +} + +fn normalize_relative_path(value: &str) -> Result { + let normalized = value.trim().trim_start_matches('/').replace('\\', "/"); + if normalized.is_empty() { + return Err(WebError::bad_request_code( + "local_ocr_path_required", + "OCR 路径不能为空", + )); + } + let path = Path::new(&normalized); + if path.is_absolute() + || path.components().any(|component| { + matches!( + component, + Component::ParentDir | Component::RootDir | Component::Prefix(_) + ) + }) + { + return Err(WebError::bad_request_code( + "local_ocr_path_escape", + "OCR 相对路径不能越过 root", + )); + } + Ok(normalized) +} + +fn ensure_target_under_root( + root: &Path, + target: &Path, + code: &'static str, +) -> Result<(), WebError> { + let parent = target.parent().unwrap_or(root); + let canonical_parent = parent + .canonicalize() + .unwrap_or_else(|_| parent.to_path_buf()); + let canonical_root = root.canonicalize().unwrap_or_else(|_| root.to_path_buf()); + if !canonical_parent.starts_with(&canonical_root) { + return Err(WebError::bad_request_code(code, "OCR 路径不能越过 root")); + } + Ok(()) +} + +fn is_supported_ocr_source(path: &Path) -> bool { + let ext = path + .extension() + .and_then(|value| value.to_str()) + .unwrap_or_default() + .to_ascii_lowercase(); + matches!( + ext.as_str(), + "pdf" | "png" | "jpg" | "jpeg" | "webp" | "bmp" | "tif" | "tiff" + ) +} + +fn relative_from_owner_dir(owner_document_path: &str, source_root_relative_path: &str) -> String { + let owner_parent = Path::new(owner_document_path) + .parent() + .unwrap_or(Path::new("")); + Path::new(source_root_relative_path) + .strip_prefix(owner_parent) + .map(|value| format!("./{}", value.to_string_lossy().replace('\\', "/"))) + .unwrap_or_else(|_| source_root_relative_path.to_string()) +} + +fn relative_from_sidecar_dir(ocr_root_relative_path: &str, owner_document_path: &str) -> String { + let sidecar_parent = Path::new(ocr_root_relative_path) + .parent() + .unwrap_or_else(|| Path::new("")); + let owner = Path::new(owner_document_path); + let sidecar_components = sidecar_parent + .components() + .map(|component| component.as_os_str().to_string_lossy().to_string()) + .collect::>(); + let owner_components = owner + .components() + .map(|component| component.as_os_str().to_string_lossy().to_string()) + .collect::>(); + let common = sidecar_components + .iter() + .zip(owner_components.iter()) + .take_while(|(left, right)| left == right) + .count(); + let mut parts = Vec::::new(); + for _ in common..sidecar_components.len() { + parts.push("..".to_string()); + } + parts.extend(owner_components.into_iter().skip(common)); + if parts.is_empty() { + ".".to_string() + } else { + parts.join("/") + } +} + +fn short_hash(value: &str) -> String { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + value.hash(&mut hasher); + format!("{:x}", hasher.finish()).chars().take(6).collect() +} + +fn now_ms() -> u128 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|value| value.as_millis()) + .unwrap_or_default() +} + +fn redact_error(error: &str) -> String { + let text = error.trim().replace('\n', " "); + let lower = text.to_ascii_lowercase(); + if lower.contains("token") || lower.contains("signature") || lower.contains("x-oss-") { + return "provider_error_redacted".to_string(); + } + text.chars().take(160).collect() +} + +fn ocr_link_label(ocr_root_relative_path: &str) -> String { + Path::new(ocr_root_relative_path) + .file_name() + .and_then(|value| value.to_str()) + .unwrap_or("OCR") + .trim_end_matches(".ocr.md") + .replace(['[', ']'], "") +} + +fn mineru_token() -> Option { + std::env::var("MNOTE_MINERU_API_TOKEN") + .ok() + .or_else(|| std::env::var("MINERU_API_TOKEN").ok()) + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) +} + +fn mineru_api_base_url() -> String { + std::env::var("MNOTE_MINERU_API_BASE_URL") + .ok() + .or_else(|| std::env::var("MINERU_API_BASE_URL").ok()) + .map(|value| value.trim().trim_end_matches('/').to_string()) + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| DEFAULT_MINERU_API_BASE_URL.to_string()) +} + +fn mineru_poll_interval() -> Duration { + std::env::var("MNOTE_MINERU_POLL_INTERVAL_MS") + .ok() + .and_then(|value| value.trim().parse::().ok()) + .filter(|value| *value > 0) + .map(Duration::from_millis) + .unwrap_or_else(|| Duration::from_secs(2)) +} + +fn mineru_max_polls() -> usize { + std::env::var("MNOTE_MINERU_MAX_POLLS") + .ok() + .and_then(|value| value.trim().parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(90) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::app::{build_app, AppConfig, AppState}; + use axum::body::{to_bytes, Body}; + use axum::http::Request; + use std::io::Write as _; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; + use tower::util::ServiceExt; + + fn temp_root(name: &str) -> PathBuf { + let root = std::env::temp_dir().join(format!("{name}-{}", std::process::id())); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("docs").join("Page.assets")).expect("create assets"); + fs::create_dir_all(root.join(".mnote")).expect("create metadata"); + root + } + + fn app() -> axum::Router { + build_app(AppState::new(AppConfig { + service_name: "mnote-web".into(), + service_version: "0.1.0".into(), + bind_addr: "127.0.0.1:0".into(), + public_bind_addr: "127.0.0.1:3000".into(), + legacy_next_base_url: Some("http://127.0.0.1:3100".into()), + enable_legacy_next_compat: true, + enable_debug_shell_routes: false, + enable_editor_actor: true, + hermes_base_path: "/api/hermes".into(), + compat_next_base_path: "/api/compat/next".into(), + convex_url: None, + convex_admin_key: None, + allow_dev_fixtures: true, + query_fixtures_json: None, + mutation_fixtures_json: None, + dev_user_id: "dev-user".into(), + dev_user_name: "开发用户".into(), + dev_user_email: "dev@mnote.local".into(), + })) + } + + fn write_workspace_manifest(root: &Path) { + fs::write( + root.join(".mnote").join("workspace.json"), + r#"{"workspaceId":"local-ws-ocr","ownerId":"user_test","createdAt":"2026-06-01T00:00:00Z","capabilities":["local_files"]}"#, + ) + .expect("manifest"); + } + + async fn post_ocr_job(root: &Path, body: Value) -> (StatusCode, Value) { + let response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/local-folder/ocr/jobs") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_test") + .header("x-mnote-actor-type", "user") + .body(Body::from(body.to_string())) + .expect("request"), + ) + .await + .expect("response"); + let status = response.status(); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("response body"); + let payload = serde_json::from_slice(&body).expect("response json"); + let _ = fs::remove_dir_all(root); + (status, payload) + } + + fn query_escape(value: &str) -> String { + value + .bytes() + .map(|byte| match byte { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + (byte as char).to_string() + } + _ => format!("%{byte:02X}"), + }) + .collect() + } + + fn build_test_mineru_zip(markdown: &str) -> Vec { + let mut bytes = Cursor::new(Vec::::new()); + { + let mut writer = zip::ZipWriter::new(&mut bytes); + writer + .start_file("full.md", zip::write::SimpleFileOptions::default()) + .expect("zip start file"); + writer + .write_all(markdown.as_bytes()) + .expect("zip markdown"); + writer.finish().expect("zip finish"); + } + bytes.into_inner() + } + + #[test] + fn local_ocr_sidecar_path_and_frontmatter_contract() { + let root = temp_root("mnote-local-ocr-sidecar"); + fs::write(root.join("docs").join("Page.md"), "# Page\n").expect("page"); + fs::write( + root.join("docs").join("Page.assets").join("photo.png"), + b"png", + ) + .expect("photo"); + let plan = plan_ocr_sidecar_path( + &root, + "local-md:docs~2FPage.md", + "docs/Page.assets/photo.png", + "mineru", + "vlm", + false, + ) + .expect("plan"); + assert_eq!( + plan.ocr_root_relative_path, + "docs/Page.ocr/photo.png.ocr.md" + ); + let markdown = build_ocr_markdown(&plan, "识别文本", "done", 1780000000000); + assert!(markdown.contains("mnote_ocr_version: 1")); + assert!(markdown.contains("provider: mineru")); + assert!(markdown.contains("model_version: vlm")); + assert!(markdown.contains("owner_document: ../Page.md")); + assert!(markdown.contains("source_path: ./Page.assets/photo.png")); + assert!(markdown.contains("source_root_relative_path: docs/Page.assets/photo.png")); + assert!(markdown.contains("status: done")); + let parsed = parse_ocr_frontmatter(&markdown).expect("frontmatter"); + assert_eq!( + parsed.source_root_relative_path, + "docs/Page.assets/photo.png" + ); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn local_ocr_index_marks_stale_and_redacts_error() { + let root = temp_root("mnote-local-ocr-index"); + fs::write(root.join("docs").join("Page.md"), "# Page\n").expect("page"); + let source = root.join("docs").join("Page.assets").join("scan.pdf"); + fs::write(&source, b"pdf").expect("pdf"); + let plan = plan_ocr_sidecar_path( + &root, + "local-md:docs~2FPage.md", + "docs/Page.assets/scan.pdf", + "mock", + "vlm", + false, + ) + .expect("plan"); + let entry = build_index_entry( + &plan, + "failed", + 1780000000000, + "", + Some(redact_error("token=secret upload_url=https://example.test")), + ); + upsert_ocr_index_entry(&root, entry.clone()).expect("write index"); + let stored = read_ocr_index_entry(&root, "docs/Page.assets/scan.pdf") + .expect("read index") + .expect("entry"); + assert_eq!(stored.error.as_deref(), Some("provider_error_redacted")); + fs::write(&source, b"pdf changed").expect("change source"); + assert!(source_is_stale(&root, &stored)); + let _ = fs::remove_dir_all(root); + } + + #[tokio::test] + async fn local_ocr_jobs_route_writes_mock_sidecar_and_reads_status() { + let root = temp_root("mnote-local-ocr-route"); + write_workspace_manifest(&root); + fs::write(root.join("docs").join("Page.md"), "# Page\n").expect("page"); + fs::write( + root.join("docs").join("Page.assets").join("photo.png"), + b"png", + ) + .expect("photo"); + let root_uri = format!("file://{}", root.display()); + let response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/local-folder/ocr/jobs") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_test") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "rootUri": root_uri, + "documentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "docs/Page.assets/photo.png", + "provider": "mock", + "mockMarkdown": "Route OCR Token" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + assert_eq!(response.status(), StatusCode::OK); + assert!(root + .join("docs") + .join("Page.ocr") + .join("photo.png.ocr.md") + .is_file()); + + let escaped_root = query_escape(&root_uri); + let status_response = app() + .oneshot( + Request::builder() + .uri(format!( + "/api/local-folder/ocr/status?rootUri={escaped_root}&sourceRootRelativePath=docs%2FPage.assets%2Fphoto.png" + )) + .header("x-mnote-actor-id", "user_test") + .header("x-mnote-actor-type", "user") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("status response"); + assert_eq!(status_response.status(), StatusCode::OK); + let status_body = to_bytes(status_response.into_body(), usize::MAX) + .await + .expect("status body"); + let status_payload: Value = serde_json::from_slice(&status_body).expect("status json"); + assert_eq!(status_payload["job"]["status"].as_str(), Some("done")); + + let read_response = app() + .oneshot( + Request::builder() + .uri(format!( + "/api/local-folder/ocr/read?rootUri={escaped_root}&ocrRootRelativePath=docs%2FPage.ocr%2Fphoto.png.ocr.md" + )) + .header("x-mnote-actor-id", "user_test") + .header("x-mnote-actor-type", "user") + .body(Body::empty()) + .expect("request"), + ) + .await + .expect("read response"); + assert_eq!(read_response.status(), StatusCode::OK); + let read_body = to_bytes(read_response.into_body(), usize::MAX) + .await + .expect("read body"); + let read_payload: Value = serde_json::from_slice(&read_body).expect("read json"); + assert!(read_payload["markdown"] + .as_str() + .is_some_and(|markdown| markdown.contains("Route OCR Token"))); + let _ = fs::remove_dir_all(root); + } + + #[tokio::test] + async fn local_ocr_jobs_route_rejects_missing_mineru_token() { + let old_mnote_token = std::env::var("MNOTE_MINERU_API_TOKEN").ok(); + let old_mineru_token = std::env::var("MINERU_API_TOKEN").ok(); + std::env::remove_var("MNOTE_MINERU_API_TOKEN"); + std::env::remove_var("MINERU_API_TOKEN"); + + let root = temp_root("mnote-local-ocr-token"); + write_workspace_manifest(&root); + let root_uri = format!("file://{}", root.display()); + let (status, payload) = post_ocr_job( + &root, + json!({ + "rootUri": root_uri, + "documentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "docs/Page.assets/photo.png", + "provider": "mineru" + }), + ) + .await; + if let Some(value) = old_mnote_token { + std::env::set_var("MNOTE_MINERU_API_TOKEN", value); + } + if let Some(value) = old_mineru_token { + std::env::set_var("MINERU_API_TOKEN", value); + } + assert_eq!(status, StatusCode::UNAUTHORIZED); + assert_eq!(payload["code"].as_str(), Some("mineru_token_missing")); + } + + #[tokio::test] + async fn local_ocr_jobs_route_runs_mineru_runtime_against_http_mock() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("mock mineru bind"); + let base_url = format!("http://{}", listener.local_addr().expect("mock addr")); + let upload_count = Arc::new(AtomicUsize::new(0)); + let poll_count = Arc::new(AtomicUsize::new(0)); + let zip_bytes = Arc::new(build_test_mineru_zip("# MinerU Result\n\n识别文本")); + + let mock_mineru = axum::Router::new() + .route( + "/api/v4/file-urls/batch", + axum::routing::post({ + let base_url = base_url.clone(); + || async move { + Json(json!({ + "batch_id": "batch_1", + "file_urls": [{ "upload_url": format!("{base_url}/upload/source") }] + })) + } + }), + ) + .route( + "/upload/source", + axum::routing::put({ + let upload_count = upload_count.clone(); + move |body: axum::body::Bytes| { + let upload_count = upload_count.clone(); + async move { + assert!(!body.is_empty()); + upload_count.fetch_add(1, Ordering::SeqCst); + StatusCode::OK + } + } + }), + ) + .route( + "/api/v4/extract-results/batch/batch_1", + axum::routing::get({ + let base_url = base_url.clone(); + let poll_count = poll_count.clone(); + move || { + let poll_count = poll_count.clone(); + async move { + poll_count.fetch_add(1, Ordering::SeqCst); + Json(json!({ + "status": "done", + "full_zip_url": format!("{base_url}/result.zip") + })) + } + } + }), + ) + .route( + "/result.zip", + axum::routing::get({ + let zip_bytes = zip_bytes.clone(); + move || { + let zip_bytes = zip_bytes.clone(); + async move { + ( + [(axum::http::header::CONTENT_TYPE, "application/zip")], + (*zip_bytes).clone(), + ) + } + } + }), + ); + let mock_handle = tokio::spawn(async move { + axum::serve(listener, mock_mineru).await.expect("mock mineru server"); + }); + + let old_token = std::env::var("MNOTE_MINERU_API_TOKEN").ok(); + let old_base = std::env::var("MNOTE_MINERU_API_BASE_URL").ok(); + let old_interval = std::env::var("MNOTE_MINERU_POLL_INTERVAL_MS").ok(); + let old_max_polls = std::env::var("MNOTE_MINERU_MAX_POLLS").ok(); + std::env::set_var("MNOTE_MINERU_API_TOKEN", "test-mineru-token"); + std::env::set_var("MNOTE_MINERU_API_BASE_URL", &base_url); + std::env::set_var("MNOTE_MINERU_POLL_INTERVAL_MS", "1"); + std::env::set_var("MNOTE_MINERU_MAX_POLLS", "2"); + + let root = temp_root("mnote-local-ocr-mineru-runtime"); + write_workspace_manifest(&root); + fs::write(root.join("docs").join("Page.md"), "# Page\n").expect("page"); + fs::write( + root.join("docs").join("Page.assets").join("photo.png"), + b"png", + ) + .expect("photo"); + let root_uri = format!("file://{}", root.display()); + let response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/local-folder/ocr/jobs") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_test") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "rootUri": root_uri, + "documentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "docs/Page.assets/photo.png", + "provider": "mineru" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + + if let Some(value) = old_token { + std::env::set_var("MNOTE_MINERU_API_TOKEN", value); + } else { + std::env::remove_var("MNOTE_MINERU_API_TOKEN"); + } + if let Some(value) = old_base { + std::env::set_var("MNOTE_MINERU_API_BASE_URL", value); + } else { + std::env::remove_var("MNOTE_MINERU_API_BASE_URL"); + } + if let Some(value) = old_interval { + std::env::set_var("MNOTE_MINERU_POLL_INTERVAL_MS", value); + } else { + std::env::remove_var("MNOTE_MINERU_POLL_INTERVAL_MS"); + } + if let Some(value) = old_max_polls { + std::env::set_var("MNOTE_MINERU_MAX_POLLS", value); + } else { + std::env::remove_var("MNOTE_MINERU_MAX_POLLS"); + } + + assert_eq!(response.status(), StatusCode::OK); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("response body"); + let payload: Value = serde_json::from_slice(&body).expect("response json"); + assert_eq!(payload["job"]["status"].as_str(), Some("done")); + assert_eq!(payload["job"]["provider"].as_str(), Some("mineru")); + assert_eq!(upload_count.load(Ordering::SeqCst), 1); + assert_eq!(poll_count.load(Ordering::SeqCst), 1); + let sidecar = fs::read_to_string( + root.join("docs") + .join("Page.ocr") + .join("photo.png.ocr.md"), + ) + .expect("sidecar"); + assert!(sidecar.contains("provider: mineru")); + assert!(sidecar.contains("识别文本")); + + mock_handle.abort(); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn local_ocr_mineru_runtime_config_uses_safe_defaults_and_env_overrides() { + let old_base = std::env::var("MNOTE_MINERU_API_BASE_URL").ok(); + let old_legacy_base = std::env::var("MINERU_API_BASE_URL").ok(); + let old_interval = std::env::var("MNOTE_MINERU_POLL_INTERVAL_MS").ok(); + let old_max_polls = std::env::var("MNOTE_MINERU_MAX_POLLS").ok(); + std::env::remove_var("MNOTE_MINERU_API_BASE_URL"); + std::env::remove_var("MINERU_API_BASE_URL"); + std::env::remove_var("MNOTE_MINERU_POLL_INTERVAL_MS"); + std::env::remove_var("MNOTE_MINERU_MAX_POLLS"); + + assert_eq!(mineru_api_base_url(), DEFAULT_MINERU_API_BASE_URL); + assert_eq!(mineru_poll_interval(), Duration::from_secs(2)); + assert_eq!(mineru_max_polls(), 90); + + std::env::set_var("MNOTE_MINERU_API_BASE_URL", "https://mineru.example.test/"); + std::env::set_var("MNOTE_MINERU_POLL_INTERVAL_MS", "25"); + std::env::set_var("MNOTE_MINERU_MAX_POLLS", "3"); + assert_eq!(mineru_api_base_url(), "https://mineru.example.test"); + assert_eq!(mineru_poll_interval(), Duration::from_millis(25)); + assert_eq!(mineru_max_polls(), 3); + + if let Some(value) = old_base { + std::env::set_var("MNOTE_MINERU_API_BASE_URL", value); + } else { + std::env::remove_var("MNOTE_MINERU_API_BASE_URL"); + } + if let Some(value) = old_legacy_base { + std::env::set_var("MINERU_API_BASE_URL", value); + } else { + std::env::remove_var("MINERU_API_BASE_URL"); + } + if let Some(value) = old_interval { + std::env::set_var("MNOTE_MINERU_POLL_INTERVAL_MS", value); + } else { + std::env::remove_var("MNOTE_MINERU_POLL_INTERVAL_MS"); + } + if let Some(value) = old_max_polls { + std::env::set_var("MNOTE_MINERU_MAX_POLLS", value); + } else { + std::env::remove_var("MNOTE_MINERU_MAX_POLLS"); + } + } + + #[tokio::test] + async fn local_ocr_jobs_route_rejects_root_escape_source() { + let root = temp_root("mnote-local-ocr-escape"); + write_workspace_manifest(&root); + fs::write(root.join("docs").join("Page.md"), "# Page\n").expect("page"); + let root_uri = format!("file://{}", root.display()); + let (status, payload) = post_ocr_job( + &root, + json!({ + "rootUri": root_uri, + "documentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "../outside.png", + "provider": "mock", + "mockMarkdown": "should not write" + }), + ) + .await; + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(payload["code"].as_str(), Some("local_ocr_path_escape")); + } + + #[tokio::test] + async fn local_ocr_jobs_route_rejects_non_image_pdf_source() { + let root = temp_root("mnote-local-ocr-unsupported"); + write_workspace_manifest(&root); + fs::write(root.join("docs").join("Page.md"), "# Page\n").expect("page"); + fs::write( + root.join("docs").join("Page.assets").join("notes.txt"), + b"text", + ) + .expect("text"); + let root_uri = format!("file://{}", root.display()); + let (status, payload) = post_ocr_job( + &root, + json!({ + "rootUri": root_uri, + "documentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "docs/Page.assets/notes.txt", + "provider": "mock", + "mockMarkdown": "should not write" + }), + ) + .await; + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!( + payload["code"].as_str(), + Some("local_ocr_source_type_unsupported") + ); + } + + #[tokio::test] + async fn local_ocr_jobs_route_writes_failed_mock_entry_with_redacted_error() { + let root = temp_root("mnote-local-ocr-failed"); + write_workspace_manifest(&root); + fs::write(root.join("docs").join("Page.md"), "# Page\n").expect("page"); + fs::write( + root.join("docs").join("Page.assets").join("photo.png"), + b"png", + ) + .expect("photo"); + let root_uri = format!("file://{}", root.display()); + let response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/local-folder/ocr/jobs") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_test") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "rootUri": root_uri, + "documentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "docs/Page.assets/photo.png", + "provider": "mock", + "mockError": "token=secret https://signed.example.test/full.zip?X-Oss-Signature=abc" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("response"); + assert_eq!(response.status(), StatusCode::OK); + let body = to_bytes(response.into_body(), usize::MAX) + .await + .expect("response body"); + let payload: Value = serde_json::from_slice(&body).expect("response json"); + assert_eq!(payload["job"]["status"].as_str(), Some("failed")); + assert_eq!( + payload["job"]["error"].as_str(), + Some("provider_error_redacted") + ); + let stored = read_ocr_index_entry(&root, "docs/Page.assets/photo.png") + .expect("read index") + .expect("entry"); + assert_eq!(stored.status, "failed"); + assert_eq!(stored.error.as_deref(), Some("provider_error_redacted")); + assert!(!root + .join("docs") + .join("Page.ocr") + .join("photo.png.ocr.md") + .exists()); + let _ = fs::remove_dir_all(root); + } + + #[tokio::test] + async fn local_ocr_insert_route_appends_explicit_ocr_link() { + let root = temp_root("mnote-local-ocr-insert"); + write_workspace_manifest(&root); + fs::write(root.join("docs").join("Page.md"), "# Page\n\n正文\n").expect("page"); + fs::write( + root.join("docs").join("Page.assets").join("photo.png"), + b"png", + ) + .expect("photo"); + let root_uri = format!("file://{}", root.display()); + let create_response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/local-folder/ocr/jobs") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_test") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "rootUri": root_uri, + "documentId": "local-md:docs~2FPage.md", + "sourceRootRelativePath": "docs/Page.assets/photo.png", + "provider": "mock", + "mockMarkdown": "识别文本" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("create response"); + assert_eq!(create_response.status(), StatusCode::OK); + + let insert_response = app() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/local-folder/ocr/insert") + .header("content-type", "application/json") + .header("x-mnote-actor-id", "user_test") + .header("x-mnote-actor-type", "user") + .body(Body::from( + json!({ + "rootUri": root_uri, + "documentId": "local-md:docs~2FPage.md", + "ocrRootRelativePath": "docs/Page.ocr/photo.png.ocr.md", + "mode": "link" + }) + .to_string(), + )) + .expect("request"), + ) + .await + .expect("insert response"); + assert_eq!(insert_response.status(), StatusCode::OK); + let body = to_bytes(insert_response.into_body(), usize::MAX) + .await + .expect("insert body"); + let payload: Value = serde_json::from_slice(&body).expect("insert json"); + assert_eq!( + payload["insertedMarkdown"].as_str(), + Some("[OCR:photo.png](./Page.ocr/photo.png.ocr.md)") + ); + let owner = fs::read_to_string(root.join("docs").join("Page.md")).expect("owner"); + assert_eq!( + owner, + "# Page\n\n正文\n\n[OCR:photo.png](./Page.ocr/photo.png.ocr.md)\n" + ); + let _ = fs::remove_dir_all(root); + } +} diff --git a/rust/crates/mnote-web/src/routes/local_search_index.rs b/rust/crates/mnote-web/src/routes/local_search_index.rs index 8428cfb6..29be5f97 100644 --- a/rust/crates/mnote-web/src/routes/local_search_index.rs +++ b/rust/crates/mnote-web/src/routes/local_search_index.rs @@ -3,6 +3,7 @@ use crate::routes::local_folder_source::encode_local_id_segment; use crate::routes::local_markdown_parser::{ parse_markdown_attachment_link, parse_markdown_page, split_frontmatter, }; +use crate::routes::local_ocr; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::fs; @@ -55,6 +56,7 @@ pub(crate) fn query_local_search_index( limit: u32, title_only: bool, exact: bool, + include_ocr: bool, ) -> Result { let index = load_or_rebuild_local_search_index(root_path, root_uri, workspace_id)?; let normalized_query = normalize_search_text(query); @@ -90,6 +92,39 @@ pub(crate) fn query_local_search_index( } } } + if include_ocr && results.len() < limit.max(1) as usize { + for entry in local_ocr::ocr_index_entries(root_path)? { + if let Some(page_id) = page_id { + if entry.owner_document_id != page_id { + continue; + } + } + if entry.status != "done" { + continue; + } + let ocr_path = root_path.join(&entry.ocr_root_relative_path); + let markdown = match fs::read_to_string(&ocr_path) { + Ok(markdown) => markdown, + Err(_) => continue, + }; + if local_ocr::parse_ocr_frontmatter(&markdown).is_none() { + continue; + } + let body = local_ocr::strip_ocr_frontmatter(&markdown); + if !local_search_ocr_matches(&entry, body, &normalized_query, title_only, exact) { + continue; + } + results.push(local_search_ocr_projection( + &entry, + body, + root_uri, + &normalized_query, + )); + if results.len() >= limit.max(1) as usize { + break; + } + } + } Ok(json!({ "enqueueAssetIds": [], "projectionOwner": "rust-kernel", @@ -100,7 +135,8 @@ pub(crate) fn query_local_search_index( "workspaceId": index.workspace_id, "builtAt": index.built_at, "documentCount": index.documents.len(), - "resourceCount": index.resources.len() + "resourceCount": index.resources.len(), + "includeOcr": include_ocr }, "recentChanges": recent_changes, "results": results @@ -404,6 +440,14 @@ fn collect_markdown_documents( continue; } if is_markdown_path(&path) { + let relative_path = path + .strip_prefix(root_path) + .unwrap_or(&path) + .to_string_lossy() + .replace('\\', "/"); + if local_ocr::is_local_ocr_sidecar_relative_path(&relative_path) { + continue; + } documents.push(index_markdown_file(root_path, &path)?); } else if resource_type_from_path(&path).is_some() { resources.push(index_resource_file(root_path, &path)?); @@ -565,6 +609,34 @@ fn local_search_resource_matches( } } +fn local_search_ocr_matches( + entry: &local_ocr::OcrIndexEntry, + body: &str, + query: &str, + title_only: bool, + exact: bool, +) -> bool { + if query.is_empty() { + return false; + } + let haystack = if title_only { + normalize_search_text(&format!( + "{}\n{}", + entry.source_root_relative_path, entry.ocr_root_relative_path + )) + } else { + normalize_search_text(&format!( + "{}\n{}\n{}", + entry.source_root_relative_path, entry.ocr_root_relative_path, body + )) + }; + if exact { + haystack == query + } else { + haystack.contains(query) + } +} + fn local_search_document_projection( document: &LocalSearchDocument, root_uri: &str, @@ -608,6 +680,42 @@ fn local_search_resource_projection(resource: &LocalSearchResource, root_uri: &s }) } +fn local_search_ocr_projection( + entry: &local_ocr::OcrIndexEntry, + body: &str, + root_uri: &str, + query: &str, +) -> Value { + let title = Path::new(&entry.owner_document_path) + .file_stem() + .and_then(|value| value.to_str()) + .unwrap_or("OCR") + .to_string(); + json!({ + "id": format!("{}#ocr:{}", entry.owner_document_id, entry.source_root_relative_path), + "documentId": entry.owner_document_id, + "title": title, + "path": entry.owner_document_path, + "resourceType": "markdown", + "sourceKind": "local_folder", + "rootUri": root_uri, + "hasOcr": true, + "snippet": ocr_search_snippet(body, query), + "ocrEvidence": { + "sourceRootRelativePath": entry.source_root_relative_path, + "ocrRootRelativePath": entry.ocr_root_relative_path, + "provider": entry.provider, + "status": entry.status, + }, + "updatedAt": entry.updated_at_ms, + "publicPath": format!( + "/documents/{}?sourceKind=local_folder&rootUri={}", + entry.owner_document_id, + encode_query_component(root_uri), + ) + }) +} + fn encode_query_component(value: &str) -> String { let mut encoded = String::with_capacity(value.len()); for byte in value.as_bytes() { @@ -764,6 +872,27 @@ fn search_snippet(document: &LocalSearchDocument, query: &str) -> String { document.raw_text.chars().take(180).collect() } +fn ocr_search_snippet(body: &str, query: &str) -> String { + let normalized_body = body.replace('\n', " "); + let normalized_query = query.trim(); + if normalized_query.is_empty() { + return normalized_body.chars().take(160).collect(); + } + let lower = normalized_body.to_ascii_lowercase(); + let lower_query = normalized_query.to_ascii_lowercase(); + if let Some(byte_index) = lower.find(&lower_query) { + let start = normalized_body[..byte_index] + .char_indices() + .rev() + .nth(40) + .map(|(idx, _)| idx) + .unwrap_or(0); + normalized_body[start..].chars().take(160).collect() + } else { + normalized_body.chars().take(160).collect() + } +} + fn is_markdown_path(path: &Path) -> bool { path.extension() .and_then(|value| value.to_str()) @@ -845,6 +974,7 @@ mod tests { 10, false, false, + false, ) .expect("projection"); let results = projection["results"].as_array().expect("results"); @@ -898,6 +1028,7 @@ mod tests { 10, false, false, + false, ) .expect("child search"); let child_result = child_search["results"] @@ -929,6 +1060,7 @@ mod tests { 10, false, false, + false, ) .expect("mindmap projection"); assert!(mindmap_projection["results"] @@ -953,6 +1085,7 @@ mod tests { 10, false, false, + false, ) .expect("office projection"); assert!(office_projection["results"] @@ -1017,6 +1150,104 @@ mod tests { let _ = fs::remove_dir_all(&root); } + #[test] + fn local_search_ocr_sidecar_requires_include_ocr_and_returns_owner_page() { + let root = temp_root("mnote-local-search-ocr"); + let root_uri = format!("file://{}", root.display()); + let workspace_id = "local-ws-ocr"; + fs::write(root.join("docs").join("Page.md"), "# Page\n正文\n").expect("page"); + fs::create_dir_all(root.join("docs").join("Page.assets")).expect("assets"); + fs::write( + root.join("docs").join("Page.assets").join("photo.png"), + b"png", + ) + .expect("photo"); + fs::create_dir_all(root.join("docs").join("Page.ocr")).expect("ocr dir"); + fs::write( + root.join("docs").join("Page.ocr").join("photo.png.ocr.md"), + "---\nmnote_ocr_version: 1\nprovider: mock\nmodel_version: vlm\nowner_document: ../Page.md\nsource_path: ./Page.assets/photo.png\nsource_root_relative_path: docs/Page.assets/photo.png\nsource_size: 3\nsource_mtime_ms: 1\nstatus: done\ncreated_at: 1\nupdated_at: 1\n---\n\nOCR-only-token 识别正文\n", + ) + .expect("ocr markdown"); + fs::create_dir_all(root.join(".mnote")).expect("mnote dir"); + fs::write( + root.join(".mnote").join("ocr-index.json"), + serde_json::to_string_pretty(&json!({ + "version": 1, + "entries": { + "docs/Page.assets/photo.png": { + "jobId": "ocr_test", + "ownerDocumentId": "local-md:docs~2FPage.md", + "ownerDocumentPath": "docs/Page.md", + "sourceRootRelativePath": "docs/Page.assets/photo.png", + "ocrRootRelativePath": "docs/Page.ocr/photo.png.ocr.md", + "provider": "mock", + "modelVersion": "vlm", + "status": "done", + "sourceSize": 3, + "sourceMtimeMs": 1, + "createdAtMs": 1, + "updatedAtMs": 1, + "plainTextPreview": "OCR-only-token 识别正文" + } + } + })) + .expect("serialize index"), + ) + .expect("ocr index"); + + let without_ocr = query_local_search_index( + &root, + &root_uri, + workspace_id, + "OCR-only-token", + None, + 10, + false, + false, + false, + ) + .expect("without ocr"); + assert_eq!(without_ocr["results"].as_array().map(Vec::len), Some(0)); + + let with_ocr = query_local_search_index( + &root, + &root_uri, + workspace_id, + "OCR-only-token", + None, + 10, + false, + false, + true, + ) + .expect("with ocr"); + let result = with_ocr["results"] + .as_array() + .and_then(|items| items.first()) + .expect("ocr result"); + assert_eq!( + result["documentId"].as_str(), + Some("local-md:docs~2FPage.md") + ); + assert_eq!(result["hasOcr"].as_bool(), Some(true)); + assert_eq!( + result["ocrEvidence"]["sourceRootRelativePath"].as_str(), + Some("docs/Page.assets/photo.png") + ); + assert_eq!( + result["ocrEvidence"]["ocrRootRelativePath"].as_str(), + Some("docs/Page.ocr/photo.png.ocr.md") + ); + let index = read_local_search_index(&root) + .expect("read search index") + .expect("search index"); + assert!(!index + .documents + .iter() + .any(|document| document.path == "docs/Page.ocr/photo.png.ocr.md")); + let _ = fs::remove_dir_all(&root); + } + #[test] fn local_search_query_reads_existing_index_without_rebuilding() { let root = temp_root("mnote-local-search-query-cache"); @@ -1038,6 +1269,7 @@ mod tests { 10, false, false, + false, ) .expect("first query"); assert_eq!( @@ -1060,6 +1292,7 @@ mod tests { 10, false, false, + false, ) .expect("query existing index"); assert_eq!( @@ -1078,6 +1311,7 @@ mod tests { 10, false, false, + false, ) .expect("query refreshed index"); assert_eq!( diff --git a/rust/crates/mnote-web/src/routes/mod.rs b/rust/crates/mnote-web/src/routes/mod.rs index 6317291a..3de1b8d2 100644 --- a/rust/crates/mnote-web/src/routes/mod.rs +++ b/rust/crates/mnote-web/src/routes/mod.rs @@ -13,12 +13,14 @@ mod kernel; mod local_folder_events; mod local_folder_source; mod local_markdown_parser; +mod local_ocr; mod local_search_index; mod media; mod mindmap_api; mod mindmap_shell; pub(crate) mod navigation_recent; mod onlyoffice; +pub(crate) mod onlyoffice_bridge; mod page_ai_workflow; mod query_support; mod resource_trash; @@ -35,7 +37,7 @@ pub(crate) mod web_shell; mod ws; pub(crate) use local_folder_source::{ - ensure_local_path_read_access, ensure_local_workspace_access, + decode_local_id_segment, ensure_local_path_read_access, ensure_local_workspace_access, local_markdown_conflict_detection_key, local_workspace_id_from_root_uri, update_local_markdown_title, write_local_markdown_page_body, }; @@ -183,6 +185,34 @@ pub fn build_router(state: AppState) -> Router { "/api/mnote-browser-runtime/sidebar-page-ai-runtime.js", get(web_shell::sidebar_page_ai_runtime_asset), ) + .route( + "/api/mnote-browser-runtime/sidebar-page-ai-markdown-runtime.js", + get(web_shell::sidebar_page_ai_markdown_runtime_asset), + ) + .route( + "/api/mnote-browser-runtime/sidebar-page-ai-render-runtime.js", + get(web_shell::sidebar_page_ai_render_runtime_asset), + ) + .route( + "/api/mnote-browser-runtime/sidebar-page-ai-permission-runtime.js", + get(web_shell::sidebar_page_ai_permission_runtime_asset), + ) + .route( + "/api/mnote-browser-runtime/sidebar-page-ai-profile-runtime.js", + get(web_shell::sidebar_page_ai_profile_runtime_asset), + ) + .route( + "/api/mnote-browser-runtime/sidebar-page-ai-session-runtime.js", + get(web_shell::sidebar_page_ai_session_runtime_asset), + ) + .route( + "/api/mnote-browser-runtime/sidebar-page-ai-skill-runtime.js", + get(web_shell::sidebar_page_ai_skill_runtime_asset), + ) + .route( + "/api/mnote-browser-runtime/sidebar-page-ai-target-runtime.js", + get(web_shell::sidebar_page_ai_target_runtime_asset), + ) .route( "/api/mnote-browser-runtime/sidebar-page-settings-runtime.js", get(web_shell::sidebar_page_settings_runtime_asset), @@ -288,6 +318,10 @@ pub fn build_router(state: AppState) -> Router { .route("/api/auth/whoami", get(session::session)) .route("/api/auth/mnote-web-token", get(session::session)) .route("/api/auth/session/refresh", post(session::refresh_session)) + .route( + "/api/ai/agent-profiles", + get(hermes_client::list_agent_profiles), + ) .route( "/api/sidebar/shortcuts", get(sidebar_shortcuts::list_shortcuts).post(sidebar_shortcuts::upsert_shortcut), @@ -382,6 +416,54 @@ pub fn build_router(state: AppState) -> Router { .route("/api/onlyoffice/proxy", get(onlyoffice::proxy)) .route("/api/onlyoffice/callback", post(onlyoffice::callback)) .route("/api/onlyoffice/forcesave", post(onlyoffice::forcesave)) + .route( + "/api/onlyoffice/bridge/plugin/config", + get(onlyoffice_bridge::plugin_config), + ) + .route( + "/api/onlyoffice/bridge/plugin/index", + get(onlyoffice_bridge::plugin_index), + ) + .route( + "/api/onlyoffice/bridge/plugin/index/config.json", + get(onlyoffice_bridge::plugin_config), + ) + .route( + "/api/onlyoffice/bridge/plugin/index/{state}", + get(onlyoffice_bridge::plugin_index_with_state), + ) + .route( + "/api/onlyoffice/bridge/session", + post(onlyoffice_bridge::register_session), + ) + .route( + "/api/onlyoffice/bridge/session/current", + get(onlyoffice_bridge::current_session), + ) + .route( + "/api/onlyoffice/bridge/session/close", + post(onlyoffice_bridge::close_session), + ) + .route( + "/api/onlyoffice/bridge/sessions", + get(onlyoffice_bridge::list_sessions), + ) + .route( + "/api/onlyoffice/bridge/capabilities", + get(onlyoffice_bridge::capabilities), + ) + .route( + "/api/onlyoffice/bridge/commands", + post(onlyoffice_bridge::enqueue_command), + ) + .route( + "/api/onlyoffice/bridge/commands/next", + get(onlyoffice_bridge::next_command), + ) + .route( + "/api/onlyoffice/bridge/results", + get(onlyoffice_bridge::get_result).post(onlyoffice_bridge::post_result), + ) .route("/api/media/upload", post(media::upload)) .route("/api/media/sign", get(media::sign)) .route("/api/media/batch", post(resource_trash::media_batch)) @@ -452,6 +534,13 @@ pub fn build_router(state: AppState) -> Router { "/api/local-folder/events", get(local_folder_events::local_folder_events), ) + .route( + "/api/local-folder/ocr/jobs", + get(local_ocr::list_jobs).post(local_ocr::create_job), + ) + .route("/api/local-folder/ocr/status", get(local_ocr::status)) + .route("/api/local-folder/ocr/read", get(local_ocr::read)) + .route("/api/local-folder/ocr/insert", post(local_ocr::insert)) .route( "/api/local-folder/workspaces/default", post(local_folder_source::create_default_local_workspace), @@ -1027,6 +1116,13 @@ mod tests { "/api/mnote-browser-runtime/sidebar-filetree-upload-runtime.js", "/api/mnote-browser-runtime/sidebar-attachment-open-runtime.js", "/api/mnote-browser-runtime/sidebar-page-ai-runtime.js", + "/api/mnote-browser-runtime/sidebar-page-ai-markdown-runtime.js", + "/api/mnote-browser-runtime/sidebar-page-ai-render-runtime.js", + "/api/mnote-browser-runtime/sidebar-page-ai-permission-runtime.js", + "/api/mnote-browser-runtime/sidebar-page-ai-profile-runtime.js", + "/api/mnote-browser-runtime/sidebar-page-ai-session-runtime.js", + "/api/mnote-browser-runtime/sidebar-page-ai-skill-runtime.js", + "/api/mnote-browser-runtime/sidebar-page-ai-target-runtime.js", "/api/mnote-browser-runtime/sidebar-page-settings-runtime.js", "/api/mnote-browser-runtime/sidebar-tree-runtime.js", "/api/mnote-browser-runtime/tree-live-controller.js", diff --git a/rust/crates/mnote-web/src/routes/onlyoffice.rs b/rust/crates/mnote-web/src/routes/onlyoffice.rs index c327a00f..28e52146 100644 --- a/rust/crates/mnote-web/src/routes/onlyoffice.rs +++ b/rust/crates/mnote-web/src/routes/onlyoffice.rs @@ -1,3 +1,4 @@ +use super::onlyoffice_bridge; use crate::app::AppConfig; use crate::app::AppState; use crate::error::WebError; @@ -147,6 +148,9 @@ pub struct OnlyOfficeCallbackQuery { asset_id: Option, #[serde(rename = "userId")] user_id: Option, + #[serde(rename = "sessionId")] + session_id: Option, + token: Option, #[serde(rename = "rootUri")] root_uri: Option, path: Option, @@ -506,10 +510,12 @@ pub async fn page(Query(query): Query) -> Result) -> Result) -> Result) -> Result) -> Result {{ window.__MNOTE_ONLYOFFICE_READY__ = true; }}, onAppReady: () => {{ window.__MNOTE_ONLYOFFICE_READY__ = true; }}, + onRequestEditRights: () => {{ + const editHref = editModeLocationHref(); + window.__MNOTE_ONLYOFFICE_REQUEST_EDIT_RIGHTS__ = {{ + requested: true, + targetUrl: editHref + }}; + window.location.replace(editHref); + }}, onError: (event) => showError(JSON.stringify(event)) }} }}; @@ -690,7 +725,52 @@ pub async fn page(Query(query): Query) -> Result null); + if (!enqueue.ok) throw new Error(enqueued && enqueued.message || "ONLYOFFICE bridge command enqueue failed"); + const commandId = enqueued && enqueued.command && enqueued.command.id; + if (!commandId) throw new Error("ONLYOFFICE bridge command id missing"); + const resultUrl = new URL("/api/onlyoffice/bridge/results", location.origin); + resultUrl.searchParams.set("sessionId", bridgeSessionId); + resultUrl.searchParams.set("token", bridgeToken); + resultUrl.searchParams.set("commandId", commandId); + resultUrl.searchParams.set("timeoutMs", String(timeoutMs || 25000)); + const resultResponse = await fetch(resultUrl.toString()); + if (resultResponse.status === 204) throw new Error("ONLYOFFICE bridge command timed out"); + const result = await resultResponse.json().catch(() => null); + if (!resultResponse.ok || !result || result.ok === false) {{ + throw new Error(result && result.error || result && result.message || "ONLYOFFICE bridge command failed"); + }} + return result.result; + }} }}; const readyDeadline = Date.now() + 120000; const timer = window.setInterval(() => {{ @@ -1070,12 +1150,15 @@ fn onlyoffice_callback_success(extra: Value) -> Response { } fn onlyoffice_callback_failure(error: WebError) -> Response { - Json(json!({ + ( + error.status(), + Json(json!({ "error": 1, "code": error.code(), "message": error.message(), - })) - .into_response() + })), + ) + .into_response() } async fn download_onlyoffice_callback_body(download_url: &str) -> Result { @@ -1135,6 +1218,53 @@ async fn local_folder_onlyoffice_callback( ) })?; let target = resolve_onlyoffice_local_file_path(root_uri, relative_path)?; + let session_id = query + .session_id + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .ok_or_else(|| { + WebError::new( + StatusCode::UNAUTHORIZED, + "onlyoffice_local_callback_session_required", + "OnlyOffice 本地保存缺少 bridge sessionId", + ) + })?; + let token = query + .token + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .ok_or_else(|| { + WebError::new( + StatusCode::UNAUTHORIZED, + "onlyoffice_local_callback_token_required", + "OnlyOffice 本地保存缺少 bridge token", + ) + })?; + if !onlyoffice_bridge::session_token_matches(session_id, token) { + return Err(WebError::new( + StatusCode::UNAUTHORIZED, + "onlyoffice_local_callback_token_invalid", + "OnlyOffice 本地保存 bridge token 无效", + )); + } + let session = onlyoffice_bridge::session_info(session_id).ok_or_else(|| { + WebError::new( + StatusCode::UNAUTHORIZED, + "onlyoffice_local_callback_session_unregistered", + "OnlyOffice 本地保存 bridge session 未注册", + ) + })?; + if let Some(session_asset_id) = session.asset_id.as_deref() { + if !session_asset_id.trim().is_empty() && session_asset_id.trim() != asset_id { + return Err(WebError::new( + StatusCode::FORBIDDEN, + "onlyoffice_local_callback_asset_mismatch", + "OnlyOffice 本地保存 session 与资源不匹配", + )); + } + } let onlyoffice_internal_url = resolve_onlyoffice_internal_url().await; let prepared = prepare_callback(OnlyOfficeCallbackPreparationInput { asset_id: asset_id.to_string(), @@ -1840,6 +1970,22 @@ mod tests { )); assert!(html.contains("if (proxyOrigin && isLocalFolderFileOpen)")); assert!(html.contains("if (proxyOrigin && (isLocal || url.searchParams.has(\"token\")))")); + assert!(html.contains( + "const MNOTE_AGENT_PLUGIN_GUID = \"asc.{05F87DDF-7B42-4C6F-9F2B-9C77A8D5F4E2}\";" + )); + assert!(html.contains( + "const bridgePluginConfigUrl = new URL(\"/api/onlyoffice/bridge/plugin/config\", location.origin);" + )); + assert!( + html.contains("bridgePluginConfigUrl.searchParams.set(\"apiBase\", location.origin);") + ); + assert!(html.contains("const bridgeSessionSalt =")); + assert!(html.contains( + "const bridgeSessionId = \"mnote-oo-\" + fileState.docKey + \"-\" + bridgeSessionSalt;" + )); + assert!(html.contains("/api/onlyoffice/bridge/plugin/config")); + assert!(html.contains("pluginsData: [bridgePluginConfigUrl.toString()]")); + assert!(html.contains("window.__MNOTE_ONLYOFFICE_BRIDGE__")); } fn test_state(legacy_next_base_url: Option) -> AppState { @@ -1897,6 +2043,8 @@ mod tests { Query(OnlyOfficeCallbackQuery { asset_id: Some("asset_1".into()), user_id: None, + session_id: None, + token: None, root_uri: None, path: None, }), @@ -1917,9 +2065,9 @@ mod tests { } #[tokio::test] - async fn onlyoffice_local_callback_writes_status_two_body_to_original_file() { + async fn onlyoffice_local_callback_rejects_unauthenticated_local_write() { let root = std::env::temp_dir().join(format!( - "mnote-onlyoffice-local-callback-{}", + "mnote-onlyoffice-local-callback-unauth-{}", std::process::id() )); let _ = fs::remove_dir_all(&root); @@ -1938,6 +2086,74 @@ mod tests { Query(OnlyOfficeCallbackQuery { asset_id: Some("local:asset:Page/report.docx".into()), user_id: None, + session_id: None, + token: None, + root_uri: Some(format!("file://{}", root.display())), + path: Some("Page/report.docx".into()), + }), + Json(json!({ + "status": 2, + "key": "doc_key", + "url": download_url + })), + ) + .await; + + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(payload["error"], 1); + assert_eq!( + payload["code"], + "onlyoffice_local_callback_session_required" + ); + assert_eq!(fs::read(&target).expect("read target"), b"old"); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn onlyoffice_local_callback_writes_status_two_body_to_original_file() { + let root = std::env::temp_dir().join(format!( + "mnote-onlyoffice-local-callback-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("Page")).expect("create page"); + let target = root.join("Page").join("report.docx"); + fs::write(&target, b"old").expect("write old docx"); + let (download_url, _captured) = spawn_legacy_json_server("new docx bytes").await; + let session_id = format!("mnote-oo-local-callback-{}", std::process::id()); + let token = format!("token-{session_id}"); + let registered = + onlyoffice_bridge::register_session(Json(onlyoffice_bridge::BridgeSessionPayload { + session_id: session_id.clone(), + token: Some(token.clone()), + editor_type: Some("word".into()), + document_id: Some("local-md:Page".into()), + asset_id: Some("local:asset:Page/report.docx".into()), + file_type: Some("docx".into()), + doc_key: None, + page_origin: None, + })) + .await; + assert_eq!(registered.status(), StatusCode::OK); + let response = callback( + State(test_state(None)), + format!( + "/api/onlyoffice/callback?assetId=local%3Aasset%3APage%2Freport.docx&sessionId={}&token={}&rootUri=file%3A%2F%2F{}&path=Page%2Freport.docx", + session_id, + token, + root.display(), + ) + .parse::() + .expect("uri"), + Query(OnlyOfficeCallbackQuery { + asset_id: Some("local:asset:Page/report.docx".into()), + user_id: None, + session_id: Some(session_id), + token: Some(token), root_uri: Some(format!("file://{}", root.display())), path: Some("Page/report.docx".into()), }), @@ -1958,6 +2174,147 @@ mod tests { let _ = fs::remove_dir_all(&root); } + #[tokio::test] + async fn onlyoffice_local_callback_writes_status_six_body_to_original_file() { + let root = std::env::temp_dir().join(format!( + "mnote-onlyoffice-local-callback-status-six-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(root.join("Page")).expect("create page"); + let target = root.join("Page").join("report.docx"); + fs::write(&target, b"old").expect("write old docx"); + let (download_url, _captured) = spawn_legacy_json_server("status six bytes").await; + let session_id = format!("mnote-oo-local-callback-six-{}", std::process::id()); + let token = format!("token-{session_id}"); + let registered = + onlyoffice_bridge::register_session(Json(onlyoffice_bridge::BridgeSessionPayload { + session_id: session_id.clone(), + token: Some(token.clone()), + editor_type: Some("word".into()), + document_id: Some("local-md:Page".into()), + asset_id: Some("local:asset:Page/report.docx".into()), + file_type: Some("docx".into()), + doc_key: None, + page_origin: None, + })) + .await; + assert_eq!(registered.status(), StatusCode::OK); + let response = callback( + State(test_state(None)), + format!( + "/api/onlyoffice/callback?assetId=local%3Aasset%3APage%2Freport.docx&sessionId={}&token={}&rootUri=file%3A%2F%2F{}&path=Page%2Freport.docx", + session_id, + token, + root.display(), + ) + .parse::() + .expect("uri"), + Query(OnlyOfficeCallbackQuery { + asset_id: Some("local:asset:Page/report.docx".into()), + user_id: None, + session_id: Some(session_id), + token: Some(token), + root_uri: Some(format!("file://{}", root.display())), + path: Some("Page/report.docx".into()), + }), + Json(json!({ + "status": 6, + "key": "doc_key", + "url": download_url + })), + ) + .await; + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + + assert_eq!(payload["error"], 0); + assert_eq!(fs::read(&target).expect("read target"), b"status six bytes"); + let _ = fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn onlyoffice_local_callback_rejects_root_escape_path() { + let root = std::env::temp_dir().join(format!( + "mnote-onlyoffice-local-callback-root-{}", + std::process::id() + )); + let outside = std::env::temp_dir().join(format!( + "mnote-onlyoffice-local-callback-outside-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + let _ = fs::remove_dir_all(&outside); + fs::create_dir_all(root.join("Page")).expect("create page"); + fs::create_dir_all(&outside).expect("create outside"); + fs::write(outside.join("report.docx"), b"outside").expect("write outside"); + let (download_url, _captured) = spawn_legacy_json_server("should not write").await; + let session_id = format!("mnote-oo-local-callback-escape-{}", std::process::id()); + let token = format!("token-{session_id}"); + let registered = + onlyoffice_bridge::register_session(Json(onlyoffice_bridge::BridgeSessionPayload { + session_id: session_id.clone(), + token: Some(token.clone()), + editor_type: Some("word".into()), + document_id: Some("local-md:Page".into()), + asset_id: Some("local:asset:Page/report.docx".into()), + file_type: Some("docx".into()), + doc_key: None, + page_origin: None, + })) + .await; + assert_eq!(registered.status(), StatusCode::OK); + let response = callback( + State(test_state(None)), + format!( + "/api/onlyoffice/callback?assetId=local%3Aasset%3APage%2Freport.docx&sessionId={}&token={}&rootUri=file%3A%2F%2F{}&path=..%2F{}%2Freport.docx", + session_id, + token, + root.display(), + outside.file_name().and_then(|value| value.to_str()).unwrap_or_default(), + ) + .parse::() + .expect("uri"), + Query(OnlyOfficeCallbackQuery { + asset_id: Some("local:asset:Page/report.docx".into()), + user_id: None, + session_id: Some(session_id), + token: Some(token), + root_uri: Some(format!("file://{}", root.display())), + path: Some(format!( + "../{}/report.docx", + outside + .file_name() + .and_then(|value| value.to_str()) + .unwrap_or_default() + )), + }), + Json(json!({ + "status": 2, + "key": "doc_key", + "url": download_url + })), + ) + .await; + let status = response.status(); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(payload["error"], 1); + assert_eq!(payload["code"], "onlyoffice_local_file_root_escape"); + assert_eq!( + fs::read(outside.join("report.docx")).expect("read outside"), + b"outside" + ); + let _ = fs::remove_dir_all(&root); + let _ = fs::remove_dir_all(&outside); + } + #[tokio::test] async fn onlyoffice_local_callback_ignores_non_write_status() { let root = std::env::temp_dir().join(format!( @@ -1968,17 +2325,36 @@ mod tests { fs::create_dir_all(root.join("Page")).expect("create page"); let target = root.join("Page").join("report.docx"); fs::write(&target, b"old").expect("write old docx"); + let session_id = format!("mnote-oo-local-callback-ignore-{}", std::process::id()); + let token = format!("token-{session_id}"); + let registered = + onlyoffice_bridge::register_session(Json(onlyoffice_bridge::BridgeSessionPayload { + session_id: session_id.clone(), + token: Some(token.clone()), + editor_type: Some("word".into()), + document_id: Some("local-md:Page".into()), + asset_id: Some("local:asset:Page/report.docx".into()), + file_type: Some("docx".into()), + doc_key: None, + page_origin: None, + })) + .await; + assert_eq!(registered.status(), StatusCode::OK); let response = callback( State(test_state(None)), format!( - "/api/onlyoffice/callback?assetId=local%3Aasset%3APage%2Freport.docx&rootUri=file%3A%2F%2F{}&path=Page%2Freport.docx", - root.display() + "/api/onlyoffice/callback?assetId=local%3Aasset%3APage%2Freport.docx&sessionId={}&token={}&rootUri=file%3A%2F%2F{}&path=Page%2Freport.docx", + session_id, + token, + root.display(), ) .parse::() .expect("uri"), Query(OnlyOfficeCallbackQuery { asset_id: Some("local:asset:Page/report.docx".into()), user_id: None, + session_id: Some(session_id), + token: Some(token), root_uri: Some(format!("file://{}", root.display())), path: Some("Page/report.docx".into()), }), @@ -2006,6 +2382,8 @@ mod tests { Query(OnlyOfficeCallbackQuery { asset_id: Some("asset_1".into()), user_id: Some("user_1".into()), + session_id: None, + token: None, root_uri: None, path: None, }), @@ -2128,4 +2506,32 @@ mod tests { assert!(html.contains("anonymous: { request: false, label: \"Guest\" }")); assert!(html.contains("features: { featuresTips: false }")); } + + #[tokio::test] + async fn onlyoffice_page_reinitializes_edit_url_on_request_edit_rights() { + let response = page(Query(OnlyOfficePageQuery { + file_url: Some( + "http://localhost:3000/api/local-folder/files/open?rootUri=file:///tmp&path=Page/report.docx" + .into(), + ), + file_name: Some("report.docx".into()), + file_type: Some("docx".into()), + asset_id: Some("local-file:Page/report.docx".into()), + document_id: Some("local-md:Page".into()), + user_id: None, + mode: Some("view".into()), + })) + .await + .expect("onlyoffice page"); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let html = String::from_utf8(body.to_vec()).expect("html"); + + assert!(html.contains("function editModeLocationHref()")); + assert!(html.contains("next.searchParams.set(\"mode\", \"edit\");")); + assert!(html.contains("onRequestEditRights: () =>")); + assert!(html.contains("window.__MNOTE_ONLYOFFICE_REQUEST_EDIT_RIGHTS__")); + assert!(html.contains("window.location.replace(editHref);")); + } } diff --git a/rust/crates/mnote-web/src/routes/onlyoffice_bridge.rs b/rust/crates/mnote-web/src/routes/onlyoffice_bridge.rs new file mode 100644 index 00000000..4c2405d1 --- /dev/null +++ b/rust/crates/mnote-web/src/routes/onlyoffice_bridge.rs @@ -0,0 +1,2226 @@ +use axum::extract::{Path, Query}; +use axum::http::{header, HeaderMap, HeaderValue, StatusCode}; +use axum::response::{Html, IntoResponse, Response}; +use axum::Json; +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use base64::Engine; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Mutex, OnceLock}; +use std::time::{SystemTime, UNIX_EPOCH}; +use tokio::time::{sleep, Duration, Instant}; +use uuid::Uuid; + +pub const MNOTE_ONLYOFFICE_BRIDGE_GUID: &str = "asc.{05F87DDF-7B42-4C6F-9F2B-9C77A8D5F4E2}"; +const MAX_LONG_POLL_MS: u64 = 25_000; +const LONG_POLL_INTERVAL_MS: u64 = 250; + +static BRIDGE_COMMAND_COUNTER: AtomicU64 = AtomicU64::new(1); +static BRIDGE_STATE: OnceLock>> = OnceLock::new(); + +#[derive(Debug, Default)] +struct BridgeSessionState { + queue: VecDeque, + results: HashMap, + token: Option, + editor_type: Option, + document_id: Option, + asset_id: Option, + file_type: Option, + doc_key: Option, + page_origin: Option, + last_seen_millis: u128, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgeCommandWire { + pub id: String, + pub action: String, + #[serde(default)] + pub payload: Value, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgeResultWire { + pub id: String, + pub ok: bool, + #[serde(default)] + pub result: Value, + #[serde(default)] + pub error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgeSessionInfo { + pub session_id: String, + pub editor_type: Option, + pub document_id: Option, + pub asset_id: Option, + pub file_type: Option, + pub doc_key: Option, + pub page_origin: Option, + pub last_seen_millis: u128, + pub pending_commands: usize, + pub pending_results: usize, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgePluginConfigQuery { + session_id: Option, + api_base: Option, + token: Option, + document_id: Option, + asset_id: Option, + file_type: Option, + doc_key: Option, + page_origin: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgePluginIndexQuery { + session_id: Option, + api_base: Option, + token: Option, + document_id: Option, + asset_id: Option, + file_type: Option, + doc_key: Option, + page_origin: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct BridgePluginIndexState { + session_id: String, + api_base: String, + token: String, + #[serde(default)] + document_id: Option, + #[serde(default)] + asset_id: Option, + #[serde(default)] + file_type: Option, + #[serde(default)] + doc_key: Option, + #[serde(default)] + page_origin: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgeSessionPayload { + pub session_id: String, + #[serde(default)] + pub token: Option, + #[serde(default)] + pub editor_type: Option, + #[serde(default)] + pub document_id: Option, + #[serde(default)] + pub asset_id: Option, + #[serde(default)] + pub file_type: Option, + #[serde(default)] + pub doc_key: Option, + #[serde(default)] + pub page_origin: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgeCloseSessionPayload { + pub session_id: String, + #[serde(default)] + pub token: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgeEnqueuePayload { + session_id: String, + #[serde(default)] + token: Option, + action: String, + #[serde(default)] + payload: Value, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgeNextQuery { + session_id: String, + token: Option, + timeout_ms: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BridgeResultQuery { + session_id: String, + token: Option, + command_id: String, + timeout_ms: Option, +} + +pub async fn plugin_config(Query(query): Query) -> Response { + let session_id = non_empty(query.session_id).unwrap_or_else(|| "mnote-onlyoffice".into()); + let api_base = normalize_api_base(query.api_base.as_deref()); + let token = non_empty(query.token).unwrap_or_else(new_bridge_token); + let index_state = encode_index_state( + &session_id, + &api_base, + &token, + query.document_id.as_deref(), + query.asset_id.as_deref(), + query.file_type.as_deref(), + query.doc_key.as_deref(), + query.page_origin.as_deref(), + ); + let index_url = format!("{api_base}/api/onlyoffice/bridge/plugin/index/{index_state}"); + let payload = json!({ + "name": "MNote Bridge", + "guid": MNOTE_ONLYOFFICE_BRIDGE_GUID, + "mnoteBridgeToken": token, + "version": "0.1.0", + "variations": [{ + "description": "MNote agent bridge for ONLYOFFICE documents, spreadsheets, and presentations.", + "url": index_url, + "EditorsSupport": ["word", "cell", "slide"], + "isViewer": false, + "isVisual": false, + "isModal": false, + "isInsideMode": false, + "initDataType": "none", + "initData": "", + "buttons": [] + }] + }); + json_response(payload) +} + +pub async fn plugin_index(Query(query): Query) -> impl IntoResponse { + let session_id = non_empty(query.session_id).unwrap_or_else(|| "mnote-onlyoffice".into()); + let api_base = normalize_api_base(query.api_base.as_deref()); + let token = non_empty(query.token).unwrap_or_else(new_bridge_token); + render_plugin_index( + &session_id, + &api_base, + &token, + query.document_id.as_deref(), + query.asset_id.as_deref(), + query.file_type.as_deref(), + query.doc_key.as_deref(), + query.page_origin.as_deref(), + ) +} + +pub async fn plugin_index_with_state( + Path(encoded_state): Path, + Query(query): Query, +) -> impl IntoResponse { + let state = decode_index_state(&encoded_state); + let session_id = non_empty(query.session_id) + .or_else(|| state.as_ref().map(|value| value.session_id.clone())) + .unwrap_or_else(|| "mnote-onlyoffice".into()); + let api_base = normalize_api_base(query.api_base.as_deref().or_else(|| { + state + .as_ref() + .map(|value| value.api_base.as_str()) + .filter(|value| !value.is_empty()) + })); + let document_id = query.document_id.as_deref().or_else(|| { + state + .as_ref() + .and_then(|value| value.document_id.as_deref()) + }); + let asset_id = query + .asset_id + .as_deref() + .or_else(|| state.as_ref().and_then(|value| value.asset_id.as_deref())); + let file_type = query + .file_type + .as_deref() + .or_else(|| state.as_ref().and_then(|value| value.file_type.as_deref())); + let doc_key = query + .doc_key + .as_deref() + .or_else(|| state.as_ref().and_then(|value| value.doc_key.as_deref())); + let page_origin = query.page_origin.as_deref().or_else(|| { + state + .as_ref() + .and_then(|value| value.page_origin.as_deref()) + }); + let token = non_empty(query.token) + .or_else(|| { + state + .as_ref() + .map(|value| value.token.as_str()) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) + }) + .unwrap_or_else(new_bridge_token); + render_plugin_index( + &session_id, + &api_base, + &token, + document_id, + asset_id, + file_type, + doc_key, + page_origin, + ) +} + +fn render_plugin_index( + session_id: &str, + api_base: &str, + token: &str, + document_id: Option<&str>, + asset_id: Option<&str>, + file_type: Option<&str>, + doc_key: Option<&str>, + page_origin: Option<&str>, +) -> Html { + let html = format!( + r#" + + + + + + + + +"#, + session_id = json_string(session_id), + api_base = json_string(api_base), + token = json_string(token), + document_id = json_string(document_id.unwrap_or("")), + asset_id = json_string(asset_id.unwrap_or("")), + file_type = json_string(file_type.unwrap_or("")), + doc_key = json_string(doc_key.unwrap_or("")), + page_origin = json_string(page_origin.unwrap_or("")), + ); + Html(html) +} + +pub async fn register_session(Json(payload): Json) -> Response { + let Some(session_id) = non_empty(Some(payload.session_id)) else { + return bad_request("onlyoffice_bridge_session_required", "缺少 sessionId"); + }; + let Some(token) = non_empty(payload.token) else { + return unauthorized("onlyoffice_bridge_token_required", "缺少 bridge token"); + }; + let mut state = bridge_state().lock().expect("bridge state lock poisoned"); + let session = state.entry(session_id.clone()).or_default(); + session.token = Some(token); + session.last_seen_millis = now_millis(); + session.editor_type = payload + .editor_type + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + session.document_id = payload + .document_id + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + session.asset_id = payload + .asset_id + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + session.file_type = payload + .file_type + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + session.doc_key = payload + .doc_key + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + session.page_origin = payload + .page_origin + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + json_response(json!({ + "ok": true, + "sessionId": session_id, + "editorType": session.editor_type.clone().unwrap_or_default(), + "documentId": session.document_id.clone().unwrap_or_default(), + "assetId": session.asset_id.clone().unwrap_or_default(), + "fileType": session.file_type.clone().unwrap_or_default(), + "docKey": session.doc_key.clone().unwrap_or_default(), + "pageOrigin": session.page_origin.clone().unwrap_or_default() + })) +} + +pub async fn list_sessions() -> Response { + json_response(json!({ + "ok": true, + "sessions": session_infos() + })) +} + +pub async fn current_session() -> Response { + let current = session_infos().into_iter().next(); + json_response(json!({ + "ok": true, + "session": current + })) +} + +pub async fn close_session(Json(payload): Json) -> Response { + let Some(session_id) = non_empty(Some(payload.session_id)) else { + return bad_request("onlyoffice_bridge_session_required", "缺少 sessionId"); + }; + if let Err(response) = ensure_session_token(&session_id, payload.token.as_deref()) { + return response; + } + let mut state = bridge_state().lock().expect("bridge state lock poisoned"); + let closed = state.remove(&session_id).is_some(); + json_response(json!({ + "ok": true, + "sessionId": session_id, + "closed": closed + })) +} + +pub async fn capabilities() -> Response { + json_response(capability_payload()) +} + +pub fn capability_payload() -> Value { + json!({ + "ok": true, + "schema": "mnote.onlyoffice.bridge_capabilities.v1", + "guid": MNOTE_ONLYOFFICE_BRIDGE_GUID, + "actions": [ + "selection.get", + "document.insert_text", + "document.replace_selection", + "document.insert_html", + "document.export", + "document.search_replace", + "document.insert_table", + "document.get_comments", + "document.add_comment", + "sheet.get_sheets", + "sheet.add_sheet", + "sheet.rename_sheet", + "sheet.get_range", + "sheet.get_range_values", + "sheet.get_values", + "sheet.set_value", + "sheet.set_formula", + "sheet.batch_set_values", + "sheet.set_range_values", + "sheet.format_range", + "sheet.set_dimensions", + "sheet.sort_range", + "sheet.add_chart", + "presentation.get_slides", + "presentation.get_slide_texts", + "presentation.get_shapes", + "presentation.add_text_slide", + "presentation.replace_text", + "presentation.set_shape_text", + "presentation.delete_slide", + "presentation.add_table", + "presentation.clear_slide", + "presentation.add_shape" + ], + "editors": { + "word": ["selection.get", "document.insert_text", "document.replace_selection", "document.insert_html", "document.export", "document.search_replace", "document.insert_table", "document.get_comments", "document.add_comment"], + "cell": ["selection.get", "sheet.get_sheets", "sheet.add_sheet", "sheet.rename_sheet", "sheet.get_range", "sheet.get_range_values", "sheet.get_values", "sheet.set_value", "sheet.set_formula", "sheet.batch_set_values", "sheet.set_range_values", "sheet.format_range", "sheet.set_dimensions", "sheet.sort_range", "sheet.add_chart"], + "slide": ["selection.get", "presentation.get_slides", "presentation.get_slide_texts", "presentation.get_shapes", "presentation.add_text_slide", "presentation.replace_text", "presentation.set_shape_text", "presentation.delete_slide", "presentation.add_table", "presentation.clear_slide", "presentation.add_shape"] + } + }) +} + +pub async fn enqueue_command(Json(payload): Json) -> Response { + let Some(session_id) = non_empty(Some(payload.session_id)) else { + return bad_request("onlyoffice_bridge_session_required", "缺少 sessionId"); + }; + if let Err(response) = ensure_session_token(&session_id, payload.token.as_deref()) { + return response; + } + let Some(action) = non_empty(Some(payload.action)) else { + return bad_request("onlyoffice_bridge_action_required", "缺少 action"); + }; + let command = push_command(&session_id, action, payload.payload); + json_response(json!({ + "ok": true, + "sessionId": session_id, + "command": command + })) +} + +pub async fn next_command(Query(query): Query) -> Response { + let Some(session_id) = non_empty(Some(query.session_id)) else { + return bad_request("onlyoffice_bridge_session_required", "缺少 sessionId"); + }; + if let Err(response) = ensure_session_token(&session_id, query.token.as_deref()) { + return response; + } + let timeout = query + .timeout_ms + .unwrap_or(MAX_LONG_POLL_MS) + .min(MAX_LONG_POLL_MS); + let deadline = Instant::now() + Duration::from_millis(timeout); + loop { + if let Some(command) = pop_command(&session_id) { + return json_response(json!(command)); + } + if Instant::now() >= deadline { + return StatusCode::NO_CONTENT.into_response(); + } + sleep(Duration::from_millis(LONG_POLL_INTERVAL_MS)).await; + } +} + +pub async fn post_result(Json(payload): Json) -> Response { + let Some(session_id) = payload + .get("sessionId") + .and_then(Value::as_str) + .map(str::to_string) + .and_then(|value| non_empty(Some(value))) + else { + return bad_request("onlyoffice_bridge_session_required", "缺少 sessionId"); + }; + if let Err(response) = + ensure_session_token(&session_id, payload.get("token").and_then(Value::as_str)) + { + return response; + } + let Ok(result) = serde_json::from_value::(payload) else { + return bad_request("onlyoffice_bridge_result_invalid", "bridge result 格式无效"); + }; + let mut state = bridge_state().lock().expect("bridge state lock poisoned"); + let session = state.entry(session_id.clone()).or_default(); + session.results.insert(result.id.clone(), result.clone()); + json_response(json!({ + "ok": true, + "sessionId": session_id, + "result": result + })) +} + +pub async fn get_result(Query(query): Query) -> Response { + let Some(session_id) = non_empty(Some(query.session_id)) else { + return bad_request("onlyoffice_bridge_session_required", "缺少 sessionId"); + }; + let Some(command_id) = non_empty(Some(query.command_id)) else { + return bad_request("onlyoffice_bridge_command_required", "缺少 commandId"); + }; + if let Err(response) = ensure_session_token(&session_id, query.token.as_deref()) { + return response; + } + let timeout = query + .timeout_ms + .unwrap_or(MAX_LONG_POLL_MS) + .min(MAX_LONG_POLL_MS); + let deadline = Instant::now() + Duration::from_millis(timeout); + loop { + if let Some(result) = take_result(&session_id, &command_id) { + return json_response(json!(result)); + } + if Instant::now() >= deadline { + return StatusCode::NO_CONTENT.into_response(); + } + sleep(Duration::from_millis(LONG_POLL_INTERVAL_MS)).await; + } +} + +pub fn session_info(session_id: &str) -> Option { + let session_id = session_id.trim(); + if session_id.is_empty() { + return None; + } + session_infos() + .into_iter() + .find(|session| session.session_id == session_id) +} + +pub fn session_token_matches(session_id: &str, token: &str) -> bool { + let session_id = session_id.trim(); + let token = token.trim(); + if session_id.is_empty() || token.is_empty() { + return false; + } + let state = bridge_state().lock().expect("bridge state lock poisoned"); + state + .get(session_id) + .and_then(|session| session.token.as_deref()) + .is_some_and(|expected| expected == token) +} + +pub fn session_infos() -> Vec { + let state = bridge_state().lock().expect("bridge state lock poisoned"); + let mut sessions = state + .iter() + .map(|(session_id, session)| (session_id.clone(), session.last_seen_millis)) + .collect::>(); + sessions.sort_by(|(_, left), (_, right)| right.cmp(left)); + sessions + .into_iter() + .filter_map(|(session_id, _)| { + let session = state.get(&session_id)?; + Some(BridgeSessionInfo { + session_id: session_id.clone(), + editor_type: session.editor_type.clone(), + document_id: session.document_id.clone(), + asset_id: session.asset_id.clone(), + file_type: session.file_type.clone(), + doc_key: session.doc_key.clone(), + page_origin: session.page_origin.clone(), + last_seen_millis: session.last_seen_millis, + pending_commands: session.queue.len(), + pending_results: session.results.len(), + }) + }) + .collect() +} + +pub async fn run_bridge_command( + session_id: &str, + action: &str, + payload: Value, + timeout_ms: u64, +) -> Result { + let session_id = session_id.trim(); + if session_id.is_empty() { + return Err(BridgeRunError::BadRequest( + "缺少 ONLYOFFICE bridge sessionId".into(), + )); + } + let action = action.trim(); + if action.is_empty() { + return Err(BridgeRunError::BadRequest( + "缺少 ONLYOFFICE bridge action".into(), + )); + } + let token = { + let state = bridge_state().lock().expect("bridge state lock poisoned"); + state + .get(session_id) + .and_then(|session| session.token.clone()) + .ok_or_else(|| { + BridgeRunError::BadRequest("ONLYOFFICE bridge session 未注册或缺少 token".into()) + })? + }; + let command = push_command(session_id, action.to_string(), payload); + let timeout = timeout_ms.min(MAX_LONG_POLL_MS); + let deadline = Instant::now() + Duration::from_millis(timeout); + loop { + if ensure_session_token(session_id, Some(&token)).is_err() { + return Err(BridgeRunError::BadRequest( + "ONLYOFFICE bridge session token 已失效".into(), + )); + } + if let Some(result) = take_result(session_id, &command.id) { + return Ok(result); + } + if Instant::now() >= deadline { + return Err(BridgeRunError::Timeout { + session_id: session_id.to_string(), + command_id: command.id, + }); + } + sleep(Duration::from_millis(LONG_POLL_INTERVAL_MS)).await; + } +} + +#[derive(Debug, Clone)] +pub enum BridgeRunError { + BadRequest(String), + Timeout { + session_id: String, + command_id: String, + }, +} + +fn bridge_state() -> &'static Mutex> { + BRIDGE_STATE.get_or_init(|| Mutex::new(HashMap::new())) +} + +fn push_command(session_id: &str, action: String, payload: Value) -> BridgeCommandWire { + let command = BridgeCommandWire { + id: format!( + "oo_cmd_{}_{}", + now_millis(), + BRIDGE_COMMAND_COUNTER.fetch_add(1, Ordering::Relaxed) + ), + action, + payload, + }; + let mut state = bridge_state().lock().expect("bridge state lock poisoned"); + state + .entry(session_id.to_string()) + .or_default() + .queue + .push_back(command.clone()); + command +} + +fn pop_command(session_id: &str) -> Option { + let mut state = bridge_state().lock().expect("bridge state lock poisoned"); + let session = state.entry(session_id.to_string()).or_default(); + session.last_seen_millis = now_millis(); + session.queue.pop_front() +} + +fn take_result(session_id: &str, command_id: &str) -> Option { + let mut state = bridge_state().lock().expect("bridge state lock poisoned"); + state + .get_mut(session_id) + .and_then(|session| session.results.remove(command_id)) +} + +fn non_empty(value: Option) -> Option { + value + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) +} + +fn normalize_api_base(value: Option<&str>) -> String { + value + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("http://127.0.0.1:3000") + .trim_end_matches('/') + .to_string() +} + +fn encode_index_state( + session_id: &str, + api_base: &str, + token: &str, + document_id: Option<&str>, + asset_id: Option<&str>, + file_type: Option<&str>, + doc_key: Option<&str>, + page_origin: Option<&str>, +) -> String { + let payload = json!(BridgePluginIndexState { + session_id: session_id.to_string(), + api_base: api_base.to_string(), + token: token.to_string(), + document_id: document_id + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned), + asset_id: asset_id + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned), + file_type: file_type + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned), + doc_key: doc_key + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned), + page_origin: page_origin + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned), + }); + URL_SAFE_NO_PAD.encode(payload.to_string()) +} + +fn decode_index_state(encoded_state: &str) -> Option { + let bytes = URL_SAFE_NO_PAD.decode(encoded_state.as_bytes()).ok()?; + serde_json::from_slice(&bytes).ok() +} + +fn json_response(payload: Value) -> Response { + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + HeaderValue::from_static("application/json; charset=utf-8"), + ); + (StatusCode::OK, headers, payload.to_string()).into_response() +} + +fn unauthorized(code: &'static str, message: &'static str) -> Response { + ( + StatusCode::UNAUTHORIZED, + Json(json!({ + "ok": false, + "code": code, + "message": message + })), + ) + .into_response() +} + +fn bad_request(code: &'static str, message: &'static str) -> Response { + ( + StatusCode::BAD_REQUEST, + Json(json!({ + "ok": false, + "code": code, + "message": message + })), + ) + .into_response() +} + +fn new_bridge_token() -> String { + Uuid::new_v4().to_string() +} + +fn ensure_session_token(session_id: &str, token: Option<&str>) -> Result<(), Response> { + let Some(token) = token.map(str::trim).filter(|value| !value.is_empty()) else { + return Err(unauthorized( + "onlyoffice_bridge_token_required", + "缺少 bridge token", + )); + }; + let state = bridge_state().lock().expect("bridge state lock poisoned"); + match state + .get(session_id) + .and_then(|session| session.token.as_deref()) + { + Some(expected) if expected == token => Ok(()), + Some(_) => Err(unauthorized( + "onlyoffice_bridge_token_invalid", + "bridge token 无效", + )), + None => Err(unauthorized( + "onlyoffice_bridge_session_unregistered", + "bridge session 未注册", + )), + } +} + +fn json_string(input: &str) -> String { + serde_json::to_string(input).unwrap_or_else(|_| "\"\"".into()) +} + +fn now_millis() -> u128 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn bridge_plugin_config_exposes_dynamic_index_and_guid() { + let response = plugin_config(Query(BridgePluginConfigQuery { + session_id: Some("session-a".into()), + api_base: Some("http://127.0.0.1:3000/".into()), + token: Some("token-a".into()), + document_id: Some("doc-a".into()), + asset_id: Some("asset-a".into()), + file_type: Some("xlsx".into()), + doc_key: Some("doc-key-a".into()), + page_origin: Some("http://127.0.0.1:3000".into()), + })) + .await; + assert_eq!(response.status(), StatusCode::OK); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + assert_eq!(payload["guid"], MNOTE_ONLYOFFICE_BRIDGE_GUID); + assert_eq!(payload["version"], "0.1.0"); + let url = payload["variations"][0]["url"].as_str().unwrap_or_default(); + assert!(url.contains("/api/onlyoffice/bridge/plugin/index")); + assert!(!url.contains('?')); + let encoded_state = url.rsplit('/').next().unwrap_or_default(); + let state = decode_index_state(encoded_state).expect("encoded state"); + assert_eq!(state.session_id, "session-a"); + assert_eq!(state.api_base, "http://127.0.0.1:3000"); + assert_eq!(state.token, "token-a"); + assert_eq!(state.document_id.as_deref(), Some("doc-a")); + assert_eq!(state.asset_id.as_deref(), Some("asset-a")); + assert_eq!(state.file_type.as_deref(), Some("xlsx")); + assert_eq!(state.doc_key.as_deref(), Some("doc-key-a")); + assert_eq!(state.page_origin.as_deref(), Some("http://127.0.0.1:3000")); + } + + #[tokio::test] + async fn bridge_plugin_index_accepts_path_state_with_onlyoffice_query_params() { + let encoded_state = encode_index_state( + "session-b", + "http://127.0.0.1:3000", + "token-b", + Some("doc-b"), + Some("asset-b"), + Some("pptx"), + Some("doc-key-b"), + Some("http://127.0.0.1:3001"), + ); + let response = plugin_index_with_state( + Path(encoded_state), + Query(BridgePluginIndexQuery { + session_id: None, + api_base: None, + token: None, + document_id: None, + asset_id: None, + file_type: None, + doc_key: None, + page_origin: None, + }), + ) + .await + .into_response(); + assert_eq!(response.status(), StatusCode::OK); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let html = String::from_utf8(body.to_vec()).expect("html"); + assert!(html.contains(r#"var sessionId = "session-b";"#)); + assert!(html.contains(r#"var apiBase = "http://127.0.0.1:3000";"#)); + assert!(html.contains(r#"documentId: "doc-b""#)); + assert!(html.contains(r#"assetId: "asset-b""#)); + assert!(html.contains(r#"fileType: "pptx""#)); + assert!(html.contains(r#"docKey: "doc-key-b""#)); + assert!(html.contains(r#"pageOrigin: "http://127.0.0.1:3001""#)); + } + + #[tokio::test] + async fn bridge_plugin_index_exposes_second_batch_recipe_actions() { + let response = plugin_index(Query(BridgePluginIndexQuery { + session_id: Some("session-recipes".into()), + api_base: Some("http://127.0.0.1:3000".into()), + token: Some("token-recipes".into()), + document_id: None, + asset_id: None, + file_type: None, + doc_key: None, + page_origin: None, + })) + .await + .into_response(); + assert_eq!(response.status(), StatusCode::OK); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .expect("body"); + let html = String::from_utf8(body.to_vec()).expect("html"); + assert!(html.contains(r#"case "document.search_replace":"#)); + assert!(html.contains(r#"case "document.insert_table":"#)); + assert!(html.contains(r#"case "document.get_comments":"#)); + assert!(html.contains(r#"case "document.add_comment":"#)); + assert!(html.contains(r#"case "sheet.get_sheets":"#)); + assert!(html.contains(r#"case "sheet.add_sheet":"#)); + assert!(html.contains(r#"case "sheet.rename_sheet":"#)); + assert!(html.contains(r#"case "sheet.get_range_values":"#)); + assert!(html.contains(r#"case "sheet.set_range_values":"#)); + assert!(html.contains(r#"case "sheet.format_range":"#)); + assert!(html.contains(r#"case "sheet.set_dimensions":"#)); + assert!(html.contains(r#"case "sheet.sort_range":"#)); + assert!(html.contains(r#"case "sheet.add_chart":"#)); + assert!(html.contains("range.SetSort")); + assert!(html.contains("worksheet.AddChart")); + assert!(html.contains(r#"case "presentation.get_slide_texts":"#)); + assert!(html.contains(r#"case "presentation.get_shapes":"#)); + assert!(html.contains(r#"case "presentation.replace_text":"#)); + assert!(html.contains(r#"case "presentation.set_shape_text":"#)); + assert!(html.contains(r#"case "presentation.delete_slide":"#)); + assert!(html.contains(r#"case "presentation.add_table":"#)); + assert!(html.contains(r#"case "presentation.clear_slide":"#)); + assert!(html.contains(r#"case "presentation.add_shape":"#)); + assert!(html.contains("slide.AddObject(table)")); + assert!(html.contains("slide.RemoveAllObjects()")); + assert!(html.contains("Api.CreateShape")); + assert!(html.contains("function expectedEditorTypeForAction(action)")); + assert!(html.contains(r#"if (name.indexOf("document.") === 0) return "word";"#)); + assert!(html.contains(r#"if (name.indexOf("sheet.") === 0) return "cell";"#)); + assert!(html.contains(r#"if (name.indexOf("presentation.") === 0) return "slide";"#)); + assert!(html.contains("onlyoffice_editor_type_mismatch")); + } + + #[tokio::test] + async fn bridge_session_registration_lists_metadata_and_capabilities() { + let session_id = format!("test-session-meta-{}", now_millis()); + let token = format!("token-{session_id}"); + let registered = register_session(Json(BridgeSessionPayload { + session_id: session_id.clone(), + token: Some(token), + editor_type: Some("slide".into()), + document_id: Some("doc-meta".into()), + asset_id: Some("asset-meta".into()), + file_type: Some("pptx".into()), + doc_key: Some("doc-key-meta".into()), + page_origin: Some("http://127.0.0.1:3000".into()), + })) + .await; + assert_eq!(registered.status(), StatusCode::OK); + + let listed = list_sessions().await; + assert_eq!(listed.status(), StatusCode::OK); + let body = axum::body::to_bytes(listed.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + let session = payload["sessions"] + .as_array() + .expect("sessions") + .iter() + .find(|value| value["sessionId"] == session_id) + .expect("registered session"); + assert_eq!(session["editorType"], "slide"); + assert_eq!(session["documentId"], "doc-meta"); + assert_eq!(session["assetId"], "asset-meta"); + assert_eq!(session["fileType"], "pptx"); + assert_eq!(session["docKey"], "doc-key-meta"); + assert_eq!(session["pageOrigin"], "http://127.0.0.1:3000"); + + let current = current_session().await; + assert_eq!(current.status(), StatusCode::OK); + let body = axum::body::to_bytes(current.into_body(), usize::MAX) + .await + .expect("current body"); + let payload: Value = serde_json::from_slice(&body).expect("current json"); + assert!(payload["session"].is_object() || payload["session"].is_null()); + + let closed = close_session(Json(BridgeCloseSessionPayload { + session_id: session_id.clone(), + token: Some(format!("token-{session_id}")), + })) + .await; + assert_eq!(closed.status(), StatusCode::OK); + assert!(session_info(&session_id).is_none()); + + let capabilities = capabilities().await; + let body = axum::body::to_bytes(capabilities.into_body(), usize::MAX) + .await + .expect("capabilities body"); + let payload: Value = serde_json::from_slice(&body).expect("capabilities json"); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "presentation.set_shape_text")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "sheet.format_range")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "presentation.delete_slide")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "document.insert_table")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "document.get_comments")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "document.add_comment")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "sheet.sort_range")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "sheet.add_chart")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "presentation.add_table")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "presentation.clear_slide")); + assert!(payload["actions"] + .as_array() + .expect("actions") + .iter() + .any(|action| action == "presentation.add_shape")); + } + + #[tokio::test] + async fn bridge_queue_round_trips_command_and_result() { + let session_id = format!("test-session-{}", now_millis()); + let token = format!("token-{session_id}"); + let registered = register_session(Json(BridgeSessionPayload { + session_id: session_id.clone(), + token: Some(token.clone()), + editor_type: Some("word".into()), + document_id: None, + asset_id: None, + file_type: None, + doc_key: None, + page_origin: None, + })) + .await; + assert_eq!(registered.status(), StatusCode::OK); + + let denied = enqueue_command(Json(BridgeEnqueuePayload { + session_id: session_id.clone(), + token: None, + action: "selection.get".into(), + payload: json!({"sample": true}), + })) + .await; + assert_eq!(denied.status(), StatusCode::UNAUTHORIZED); + + let enqueue = enqueue_command(Json(BridgeEnqueuePayload { + session_id: session_id.clone(), + token: Some(token.clone()), + action: "selection.get".into(), + payload: json!({"sample": true}), + })) + .await; + assert_eq!(enqueue.status(), StatusCode::OK); + let body = axum::body::to_bytes(enqueue.into_body(), usize::MAX) + .await + .expect("body"); + let payload: Value = serde_json::from_slice(&body).expect("json"); + let command_id = payload["command"]["id"].as_str().unwrap().to_string(); + + let next = next_command(Query(BridgeNextQuery { + session_id: session_id.clone(), + token: Some(token.clone()), + timeout_ms: Some(1), + })) + .await; + assert_eq!(next.status(), StatusCode::OK); + let body = axum::body::to_bytes(next.into_body(), usize::MAX) + .await + .expect("body"); + let command: BridgeCommandWire = serde_json::from_slice(&body).expect("command"); + assert_eq!(command.id, command_id); + assert_eq!(command.action, "selection.get"); + + let posted = post_result(Json(json!({ + "sessionId": session_id, + "token": token, + "id": command_id, + "ok": true, + "result": {"text": "hello"} + }))) + .await; + assert_eq!(posted.status(), StatusCode::OK); + } +} diff --git a/rust/crates/mnote-web/src/routes/resource_trash.rs b/rust/crates/mnote-web/src/routes/resource_trash.rs index 6fd50138..e57c5544 100644 --- a/rust/crates/mnote-web/src/routes/resource_trash.rs +++ b/rust/crates/mnote-web/src/routes/resource_trash.rs @@ -1215,10 +1215,11 @@ mod tests { ) .await .expect("response"); - assert_eq!(response.status(), StatusCode::OK); + let status = response.status(); let body = to_bytes(response.into_body(), usize::MAX) .await .expect("body"); + assert_eq!(status, StatusCode::OK, "{}", String::from_utf8_lossy(&body)); serde_json::from_slice(&body).expect("json") } @@ -1236,10 +1237,11 @@ mod tests { ) .await .expect("response"); - assert_eq!(response.status(), StatusCode::OK); + let status = response.status(); let body = to_bytes(response.into_body(), usize::MAX) .await .expect("body"); + assert_eq!(status, StatusCode::OK, "{}", String::from_utf8_lossy(&body)); serde_json::from_slice(&body).expect("json") } @@ -1256,10 +1258,11 @@ mod tests { ) .await .expect("response"); - assert_eq!(response.status(), StatusCode::OK); + let status = response.status(); let body = to_bytes(response.into_body(), usize::MAX) .await .expect("body"); + assert_eq!(status, StatusCode::OK, "{}", String::from_utf8_lossy(&body)); serde_json::from_slice(&body).expect("json") } diff --git a/rust/crates/mnote-web/src/routes/search.rs b/rust/crates/mnote-web/src/routes/search.rs index 319bc525..58f2be06 100644 --- a/rust/crates/mnote-web/src/routes/search.rs +++ b/rust/crates/mnote-web/src/routes/search.rs @@ -192,6 +192,7 @@ pub async fn documents( body.limit.unwrap_or(30), filters.title_only.unwrap_or(false), filters.exact.unwrap_or(false), + filters.include_ocr.unwrap_or(false), )? } else { load_search_results_with_filters( diff --git a/rust/crates/mnote-web/src/routes/web_shell.rs b/rust/crates/mnote-web/src/routes/web_shell.rs index 5acb3bcf..6dbffa49 100644 --- a/rust/crates/mnote-web/src/routes/web_shell.rs +++ b/rust/crates/mnote-web/src/routes/web_shell.rs @@ -1623,6 +1623,104 @@ pub async fn sidebar_page_ai_runtime_asset() -> Response { .unwrap_or_else(|_| Response::new(Body::empty())) } +pub async fn sidebar_page_ai_markdown_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/sidebar-page-ai-markdown-runtime.js"); + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + "application/javascript; charset=utf-8", + ) + .header(header::CACHE_CONTROL, runtime_asset_cache_control()) + .header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .body(browser_runtime_js_body(JS)) + .unwrap_or_else(|_| Response::new(Body::empty())) +} + +pub async fn sidebar_page_ai_render_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/sidebar-page-ai-render-runtime.js"); + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + "application/javascript; charset=utf-8", + ) + .header(header::CACHE_CONTROL, runtime_asset_cache_control()) + .header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .body(browser_runtime_js_body(JS)) + .unwrap_or_else(|_| Response::new(Body::empty())) +} + +pub async fn sidebar_page_ai_permission_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/sidebar-page-ai-permission-runtime.js"); + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + "application/javascript; charset=utf-8", + ) + .header(header::CACHE_CONTROL, runtime_asset_cache_control()) + .header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .body(browser_runtime_js_body(JS)) + .unwrap_or_else(|_| Response::new(Body::empty())) +} + +pub async fn sidebar_page_ai_profile_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/sidebar-page-ai-profile-runtime.js"); + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + "application/javascript; charset=utf-8", + ) + .header(header::CACHE_CONTROL, runtime_asset_cache_control()) + .header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .body(browser_runtime_js_body(JS)) + .unwrap_or_else(|_| Response::new(Body::empty())) +} + +pub async fn sidebar_page_ai_session_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/sidebar-page-ai-session-runtime.js"); + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + "application/javascript; charset=utf-8", + ) + .header(header::CACHE_CONTROL, runtime_asset_cache_control()) + .header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .body(browser_runtime_js_body(JS)) + .unwrap_or_else(|_| Response::new(Body::empty())) +} + +pub async fn sidebar_page_ai_skill_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/sidebar-page-ai-skill-runtime.js"); + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + "application/javascript; charset=utf-8", + ) + .header(header::CACHE_CONTROL, runtime_asset_cache_control()) + .header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .body(browser_runtime_js_body(JS)) + .unwrap_or_else(|_| Response::new(Body::empty())) +} + +pub async fn sidebar_page_ai_target_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/sidebar-page-ai-target-runtime.js"); + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + "application/javascript; charset=utf-8", + ) + .header(header::CACHE_CONTROL, runtime_asset_cache_control()) + .header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .body(browser_runtime_js_body(JS)) + .unwrap_or_else(|_| Response::new(Body::empty())) +} + pub async fn sidebar_page_settings_runtime_asset() -> Response { const JS: &str = include_str!("../../browser/sidebar-page-settings-runtime.js"); Response::builder() @@ -3151,8 +3249,18 @@ mod tests { "const pageBodyTiptapDocument = (body, fallbackText = '', context = null) => {" )); assert!(conversion_runtime.contains("body?.blockDocument || body?.block_document")); + let block_document_source_index = conversion_runtime + .find("if (blockDocument) return 'page_aggregate.block_document';") + .expect("blockDocument source should be explicit"); + let local_markdown_source_index = conversion_runtime + .find("return 'local_markdown.content';") + .expect("local markdown legacy fallback should remain explicit"); assert!( - conversion_runtime.contains("localizeTiptapAssetUrls(toTiptapDocument(body.content") + block_document_source_index < local_markdown_source_index, + "local-first 浏览器转换应优先消费 blockDocument,再降级到 body.content" + ); + assert!( + conversion_runtime.contains("localizeTiptapAssetUrls(toTiptapDocument(body?.content") ); assert!(conversion_runtime.contains("localAttachmentClassForTiptapHref")); assert!(conversion_runtime.contains("mnote-uploaded-attachment-code")); @@ -4428,4 +4536,15 @@ mod tests { "block.contentNodes.map((node) => typeof node?.text === 'string' ? node.text : '').join('')" )); } + + #[test] + fn mindmap_resize_runtime_contract_includes_dimension_attrs() { + let runtime = DOCUMENT_TIPTAP_CONVERSION_RUNTIME_JS; + assert!(runtime.contains("mindmapWidth")); + assert!(runtime.contains("mindmapHeight")); + assert!(runtime.contains("data?.mindmap_width")); + assert!(runtime.contains("dataset.mnoteMindmapWidth")); + assert!(SIDEBAR_PAGE_SETTINGS_RUNTIME_JS.contains("--mnote-mindmap-block-max-width")); + assert!(SIDEBAR_PAGE_SETTINGS_RUNTIME_JS.contains("currentPageWidthPreferences().mindmap")); + } } diff --git a/rust/crates/mnote-web/src/ssr/pages/layout.rs b/rust/crates/mnote-web/src/ssr/pages/layout.rs index 1f0cc2a8..a9d62d9f 100644 --- a/rust/crates/mnote-web/src/ssr/pages/layout.rs +++ b/rust/crates/mnote-web/src/ssr/pages/layout.rs @@ -230,6 +230,18 @@ mod tests { include_str!("../../../browser/sidebar-page-tree-runtime.js"); const SIDEBAR_PAGE_AI_RUNTIME_JS: &str = include_str!("../../../browser/sidebar-page-ai-runtime.js"); + const SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS: &str = + include_str!("../../../browser/sidebar-page-ai-render-runtime.js"); + const SIDEBAR_PAGE_AI_PERMISSION_RUNTIME_JS: &str = + include_str!("../../../browser/sidebar-page-ai-permission-runtime.js"); + const SIDEBAR_PAGE_AI_PROFILE_RUNTIME_JS: &str = + include_str!("../../../browser/sidebar-page-ai-profile-runtime.js"); + const SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS: &str = + include_str!("../../../browser/sidebar-page-ai-session-runtime.js"); + const SIDEBAR_PAGE_AI_SKILL_RUNTIME_JS: &str = + include_str!("../../../browser/sidebar-page-ai-skill-runtime.js"); + const SIDEBAR_PAGE_AI_TARGET_RUNTIME_JS: &str = + include_str!("../../../browser/sidebar-page-ai-target-runtime.js"); const SIDEBAR_PAGE_SETTINGS_RUNTIME_JS: &str = include_str!("../../../browser/sidebar-page-settings-runtime.js"); const FILETREE_CONTEXT_MENU_RUNTIME_JS: &str = @@ -504,25 +516,62 @@ mod tests { ); } + #[test] + fn page_ai_agent_target_picker_contract_is_visible_and_serialized() { + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("createSidebarPageAiRenderRuntime")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS + .contains("export function createSidebarPageAiRenderRuntime")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("data-page-ai-target-button")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("data-page-ai-target-popover")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("data-page-ai-target-option")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("data-page-ai-target-chip")); + assert!(SIDEBAR_PAGE_AI_TARGET_RUNTIME_JS.contains("primaryTargetId")); + assert!(SIDEBAR_PAGE_AI_TARGET_RUNTIME_JS.contains("targets: [")); + assert!(SIDEBAR_PAGE_AI_TARGET_RUNTIME_JS.contains("policy: {")); + } + #[test] fn page_ai_uses_backend_acp_session_runtime_store() { - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiLoadBackendSessions")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("/api/hermes/client/sessions?")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("params.set('source', 'acp')")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiLoadBackendSessionDetail")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiSearchBackendSessions")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-session-rename")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-session-delete")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-session-resume")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-session-search")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("createSidebarPageAiSessionRuntime")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("createSidebarPageAiSkillRuntime")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("createSidebarPageAiTargetRuntime")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("function renderPageAiControls")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("function ensurePageAiDrawer")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("function renderPageAiConversation")); + assert!(SIDEBAR_PAGE_AI_SKILL_RUNTIME_JS + .contains("export function createSidebarPageAiSkillRuntime")); + assert!(SIDEBAR_PAGE_AI_TARGET_RUNTIME_JS + .contains("export function createSidebarPageAiTargetRuntime")); + assert!( + SIDEBAR_PAGE_AI_TARGET_RUNTIME_JS.contains("function currentPageAiOpenEditorsSnapshot") + ); + assert!(SIDEBAR_PAGE_AI_TARGET_RUNTIME_JS.contains("mnote.agent_target_package.v1")); + assert!(SIDEBAR_PAGE_AI_SKILL_RUNTIME_JS.contains("function pageAiSkillSourceOptions")); + assert!(SIDEBAR_PAGE_AI_SKILL_RUNTIME_JS.contains("function pageAiSkillPreferenceTable")); + assert!(SIDEBAR_PAGE_AI_SKILL_RUNTIME_JS.contains("ai.agent.reasonix.memory_enabled")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("function pageAiLoadBackendSessions")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("/api/hermes/client/sessions?")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("params.set('source', 'acp')")); + assert!( + SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("function pageAiLoadBackendSessionDetail") + ); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("function pageAiSearchBackendSessions")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("data-page-ai-session-rename")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("data-page-ai-session-delete")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("data-page-ai-session-resume")); + assert!(SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("data-page-ai-session-search")); assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("pageAiUsageSummary")); assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("usage.updated")); assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("thought.delta")); assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("permission.requested")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-permission-action=\"allow\"")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-permission-action=\"deny\"")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiHidePermissionDialog")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("if (!message.resolved)")); + assert!(SIDEBAR_PAGE_AI_PERMISSION_RUNTIME_JS + .contains("data-page-ai-permission-action=\"allow\"")); + assert!(SIDEBAR_PAGE_AI_PERMISSION_RUNTIME_JS + .contains("data-page-ai-permission-action=\"deny\"")); + assert!( + SIDEBAR_PAGE_AI_PERMISSION_RUNTIME_JS.contains("function pageAiHidePermissionDialog") + ); + assert!(SIDEBAR_PAGE_AI_PERMISSION_RUNTIME_JS.contains("if (!message.resolved)")); assert!( !SIDEBAR_PAGE_AI_RUNTIME_JS.contains("(item.resolved ? ' disabled' : '')"), "已决 ACP permission 事件不能继续展示假审批按钮" @@ -536,38 +585,50 @@ mod tests { "ACP PlanUpdate 事件应通过 plan.updated SSE 转发到前端" ); assert!( - SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-plan"), + SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("data-page-ai-plan"), "plan 消息应渲染为 data-page-ai-plan 标记的轻量系统状态面板" ); assert!( - SIDEBAR_PAGE_AI_RUNTIME_JS.contains("执行计划 · "), + SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS.contains("执行计划 · "), "plan 面板标题应显示执行计划和步数" ); } #[test] fn page_ai_session_ui_labels_local_shared_and_cloud_storage() { - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiSessionStorageLabel")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("local_private")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("local_shared")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("convex_acp_runtime_store")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("本地私有")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("共享会话")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("云端会话")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("sessionStorage:")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("permissionLevel:")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("shareId:")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("function pageAiSessionStorageLabel")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("local_private")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("local_shared")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("convex_acp_runtime_store")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("本地私有")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("共享会话")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("云端会话")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("sessionStorage:")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("permissionLevel:")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS.contains("shareId:")); } #[test] fn page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch() { assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("var PAGE_AI_SESSION_STORAGE_VERSION = 3")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pageAiAcpRuntime: 'reasonix'")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiNormalizeAcpRuntimes")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("return ['reasonix', 'hermes']")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function ensurePageAiStateFacade")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("pageAiAcpRuntime: 'reasonix'")); + assert!(!SIDEBAR_TREE_RUNTIME_JS.contains("pageAiAcpRuntime: 'reasonix'")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("createSidebarPageAiPermissionRuntime")); + assert!(SIDEBAR_PAGE_AI_PERMISSION_RUNTIME_JS.contains("function pageAiResolvePermission")); + assert!(SIDEBAR_PAGE_AI_PERMISSION_RUNTIME_JS.contains("resolve-permission")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("createSidebarPageAiProfileRuntime")); + assert!(SIDEBAR_PAGE_AI_PROFILE_RUNTIME_JS.contains("function pageAiNormalizeAcpRuntimes")); + assert!(SIDEBAR_PAGE_AI_PROFILE_RUNTIME_JS.contains("return ['reasonix', 'hermes']")); assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("pageAiNormalizeAcpRuntimes(acpRuntimes)")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pageAiAcpRuntimeSelect.value || 'reasonix'")); - assert!(SIDEBAR_PAGE_AI_RUNTIME_JS + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("pageAiAcpRuntimeSelect.value || 'reasonix'")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function handlePageAiClick")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function handlePageAiChange")); + assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function installPageAiDelegates")); + assert!(SIDEBAR_TREE_RUNTIME_JS.contains("sidebarPageAi.installPageAiDelegates()")); + assert!(!SIDEBAR_TREE_RUNTIME_JS.contains("sidebarPageAi.handlePageAi")); + assert!(!SIDEBAR_TREE_RUNTIME_JS.contains("closestAction(e.target, '[data-page-ai-action")); + assert!(SIDEBAR_PAGE_AI_SESSION_RUNTIME_JS .contains("activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix'")); assert!(!SIDEBAR_PAGE_AI_RUNTIME_JS.contains("默认 (Hermes HTTP)")); } @@ -1009,7 +1070,8 @@ mod tests { assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS .contains("data-mnote-last-mindmap-asset-open-mode', 'local-mindmap-object-shell'")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("buildLocalOnlyOfficeOpenUrl")); - assert!(SIDEBAR_TREE_RUNTIME_JS.contains("isLocalAsset ? onlyOfficeUrl")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS + .contains("var localOfficeUrl = buildLocalOnlyOfficeOpenUrl")); } #[test] @@ -1072,6 +1134,9 @@ mod tests { assert!(SIDEBAR_TREE_RUNTIME_JS.contains("installMnoteDevHotReload")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/dev/hot-reload")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-dev-hot-reload")); + assert!(SIDEBAR_TREE_RUNTIME_JS.contains("import.meta.url")); + assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnoteDevHotReloadEnabled()")); + assert!(!SIDEBAR_TREE_RUNTIME_JS.contains("\n installMnoteDevHotReload();\n")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("window.clearInterval(timer)")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote:primary-document-activated")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote:page-aggregate-synced")); @@ -1591,6 +1656,11 @@ mod tests { .contains("openLocalOfficeFileInActiveTab(detail, 'edit')")); assert!(SIDEBAR_FILETREE_COMMAND_RUNTIME_JS .contains("openEditorAttachmentNewWindow(detail, 'edit')")); + assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS + .contains("var requestedOfficeMode = forceEditMode ? 'edit' : 'view';")); + assert!(!SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains( + "var requestedOfficeMode = forceNewWindow || forceEditMode ? 'edit' : 'view';" + )); assert!(SIDEBAR_FILETREE_OPEN_RUNTIME_JS.contains("forceEditMode ? 'edit' : 'view'")); assert!(SIDEBAR_ATTACHMENT_OPEN_RUNTIME_JS .contains("url.searchParams.set('mode', requestedMode);")); diff --git a/rust/crates/mnote-web/src/ssr/styles.rs b/rust/crates/mnote-web/src/ssr/styles.rs index 21b61915..cc3d3d74 100644 --- a/rust/crates/mnote-web/src/ssr/styles.rs +++ b/rust/crates/mnote-web/src/ssr/styles.rs @@ -3898,6 +3898,7 @@ body { .wolai-page-ai-profile-select select, .wolai-page-ai-context-select select, .wolai-page-ai-skill-search input, +.wolai-page-ai-skill-search select, .wolai-page-ai-agent-profile select { width: 100%; min-width: 0; @@ -4499,25 +4500,48 @@ button.wolai-page-ai-message-text { } .wolai-page-ai-agent-picker, -.wolai-page-ai-context-picker { +.wolai-page-ai-context-picker, +.wolai-page-ai-target-picker { position: relative; flex: 0 0 auto; } .wolai-page-ai-agent-button, -.wolai-page-ai-context-button { +.wolai-page-ai-context-button, +.wolai-page-ai-target-button { font-size: 17px; line-height: 1; } .wolai-page-ai-agent-button[aria-expanded="true"], -.wolai-page-ai-context-button[aria-expanded="true"] { +.wolai-page-ai-context-button[aria-expanded="true"], +.wolai-page-ai-target-button[aria-expanded="true"] { border-color: rgba(27, 28, 28, 0.32); background: #F7F6F4; } +.wolai-page-ai-agent-chip, +.wolai-page-ai-target-chip { + display: inline-flex; + align-items: center; + min-width: 0; + max-width: 180px; + height: 26px; + padding: 0 8px; + border: 1px solid rgba(27, 28, 28, 0.1); + border-radius: 999px; + background: #F7F6F4; + color: #5A5A5A; + font-size: 12px; + line-height: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .wolai-page-ai-agent-popover, -.wolai-page-ai-context-popover { +.wolai-page-ai-context-popover, +.wolai-page-ai-target-popover { position: absolute; left: 0; width: min(320px, calc(100vw - 44px)); @@ -4533,7 +4557,8 @@ button.wolai-page-ai-message-text { } .wolai-page-ai-agent-popover[hidden], -.wolai-page-ai-context-popover[hidden] { +.wolai-page-ai-context-popover[hidden], +.wolai-page-ai-target-popover[hidden] { display: none !important; } @@ -4552,12 +4577,26 @@ button.wolai-page-ai-message-text { gap: 8px; } -.wolai-page-ai-agent-option-list { +.wolai-page-ai-agent-option-list, +.wolai-page-ai-target-option-list { display: grid; gap: 6px; } -.wolai-page-ai-agent-option { +.wolai-page-ai-agent-popover-section { + display: grid; + gap: 6px; +} + +.wolai-page-ai-agent-popover-title { + color: #8B8782; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; +} + +.wolai-page-ai-agent-option, +.wolai-page-ai-target-option { display: grid; gap: 2px; width: 100%; @@ -4570,17 +4609,20 @@ button.wolai-page-ai-message-text { cursor: pointer; } -.wolai-page-ai-agent-option.is-active { +.wolai-page-ai-agent-option.is-active, +.wolai-page-ai-target-option.is-active { border-color: rgba(27, 28, 28, 0.2); background: #F7F6F4; } -.wolai-page-ai-agent-option-label { +.wolai-page-ai-agent-option-label, +.wolai-page-ai-target-option-label { font-size: 12px; font-weight: 600; } -.wolai-page-ai-agent-option-detail { +.wolai-page-ai-agent-option-detail, +.wolai-page-ai-target-option-detail { color: #8B8782; font-size: 11px; } diff --git a/rust/crates/mnote-web/src/transport/convex.rs b/rust/crates/mnote-web/src/transport/convex.rs index 7d566e52..207a4d79 100644 --- a/rust/crates/mnote-web/src/transport/convex.rs +++ b/rust/crates/mnote-web/src/transport/convex.rs @@ -2,9 +2,11 @@ use crate::app::AppConfig; use crate::context::RequestContext; use crate::error::WebError; use bridge_runtime::{ - RuntimeCommandArtifactPlan, RuntimeCommandExecutionPlan, RuntimeQueryExecutionPlan, + build_runtime_command_artifact_plan, RuntimeCommandArtifactPlan, RuntimeCommandExecutionPlan, + RuntimeQueryExecutionPlan, }; use serde_json::Value; +use time::{format_description::well_known::Rfc3339, OffsetDateTime}; #[derive(Debug)] pub struct RetiredCloudCommandExecution { @@ -184,23 +186,38 @@ pub async fn execute_retired_mutation_by_name( pub async fn persist_runtime_command_artifacts( _config: &AppConfig, - context: &RequestContext, + _context: &RequestContext, _artifacts: &RuntimeCommandArtifactPlan, ) -> Result<(), WebError> { - Err(retired_error(context, "convex_artifacts_retired")) + // Convex 已退役。Rust 侧仍会把 artifact plan 返回给调用方和 realtime + // consumer;这里保持 no-op,避免兼容路径因为历史持久化层退役而失败。 + Ok(()) } pub async fn execute_retired_command_plan_with_artifacts( config: &AppConfig, context: &RequestContext, - _runtime_context: &bridge_runtime::RuntimeBridgeContextWire, - _command: &bridge_runtime::RuntimeCommandEnvelopeWire, + runtime_context: &bridge_runtime::RuntimeBridgeContextWire, + command: &bridge_runtime::RuntimeCommandEnvelopeWire, plan: &RuntimeCommandExecutionPlan, ) -> Result { let result = execute_retired_command_plan(config, context, plan).await?; + let now = OffsetDateTime::now_utc() + .format(&Rfc3339) + .unwrap_or_else(|_| "1970-01-01T00:00:00Z".into()); + let artifacts = + build_runtime_command_artifact_plan(runtime_context, command, plan, &result, &now); + let artifact_error = if let Some(artifacts) = artifacts.as_ref() { + persist_runtime_command_artifacts(config, context, artifacts) + .await + .err() + .map(|error| error.message().to_string()) + } else { + None + }; Ok(RetiredCloudCommandExecution { result, - artifacts: None, - artifact_error: None, + artifacts, + artifact_error, }) } diff --git a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.d.ts b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.d.ts index d0dfa278..0d695e61 100644 --- a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.d.ts +++ b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.d.ts @@ -48,33 +48,33 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl export interface InitOutput { readonly memory: WebAssembly.Memory; - readonly mount_mindmap_shell: (a: any, b: any) => [number, number, number]; - readonly unmount_mindmap_shell: (a: number) => [number, number]; readonly mount: (a: any, b: any) => [number, number, number]; + readonly mount_mindmap_shell: (a: any, b: any) => [number, number, number]; readonly unmount: (a: number) => [number, number]; - readonly __wbg_intounderlyingsink_free: (a: number, b: number) => void; - readonly intounderlyingsink_write: (a: number, b: any) => any; - readonly intounderlyingsink_close: (a: number) => any; - readonly intounderlyingsink_abort: (a: number, b: any) => any; + readonly unmount_mindmap_shell: (a: number) => [number, number]; readonly __wbg_intounderlyingbytesource_free: (a: number, b: number) => void; - readonly intounderlyingbytesource_type: (a: number) => number; readonly intounderlyingbytesource_autoAllocateChunkSize: (a: number) => number; - readonly intounderlyingbytesource_start: (a: number, b: any) => void; - readonly intounderlyingbytesource_pull: (a: number, b: any) => any; readonly intounderlyingbytesource_cancel: (a: number) => void; + readonly intounderlyingbytesource_pull: (a: number, b: any) => any; + readonly intounderlyingbytesource_start: (a: number, b: any) => void; + readonly intounderlyingbytesource_type: (a: number) => number; + readonly __wbg_intounderlyingsink_free: (a: number, b: number) => void; + readonly intounderlyingsink_abort: (a: number, b: any) => any; + readonly intounderlyingsink_close: (a: number) => any; + readonly intounderlyingsink_write: (a: number, b: any) => any; readonly __wbg_intounderlyingsource_free: (a: number, b: number) => void; - readonly intounderlyingsource_pull: (a: number, b: any) => any; readonly intounderlyingsource_cancel: (a: number) => void; - readonly wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12: (a: number, b: number, c: any) => [number, number]; - readonly wasm_bindgen__convert__closures_____invoke__h0accad0f5d87788a: (a: number, b: number, c: any, d: any) => void; - readonly wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5: (a: number, b: number, c: any) => void; - readonly wasm_bindgen__convert__closures_____invoke__had771ddc65647798: (a: number, b: number, c: any) => void; - readonly wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a: (a: number, b: number, c: any) => void; - readonly wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4: (a: number, b: number, c: any) => void; - readonly wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf: (a: number, b: number, c: any) => void; - readonly wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f: (a: number, b: number) => void; - readonly wasm_bindgen__convert__closures_____invoke__h2b648e7ac8ec1fb5: (a: number, b: number) => void; - readonly wasm_bindgen__convert__closures_____invoke__h0b7fb40a8610550c: (a: number, b: number) => void; + readonly intounderlyingsource_pull: (a: number, b: any) => any; + readonly wasm_bindgen__convert__closures_____invoke__h854f4676fa692669: (a: number, b: number, c: any) => [number, number]; + readonly wasm_bindgen__convert__closures_____invoke__h1a94ffe1b1ed8350: (a: number, b: number, c: any, d: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h9213ab6f3ef747a1: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h10d35e1548938147: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f_4: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__hdc2c23cc0c047a34: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h68fbbcff54846097: (a: number, b: number) => void; + readonly wasm_bindgen__convert__closures_____invoke__had164c21c04063ef: (a: number, b: number) => void; + readonly wasm_bindgen__convert__closures_____invoke__h5921057d3102c7f1: (a: number, b: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __externref_table_alloc: () => number; diff --git a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js index e408e341..dc01c722 100644 --- a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js +++ b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js @@ -812,7 +812,7 @@ function __wbg_get_imports() { const a = state0.a; state0.a = 0; try { - return wasm_bindgen__convert__closures_____invoke__h0accad0f5d87788a(a, state0.b, arg0, arg1); + return wasm_bindgen__convert__closures_____invoke__h1a94ffe1b1ed8350(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -1232,48 +1232,48 @@ function __wbg_get_imports() { } }, arguments); }, __wbindgen_cast_0000000000000001: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1140, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. - const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5); + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1003, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`. + const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h9213ab6f3ef747a1); return ret; }, __wbindgen_cast_0000000000000002: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1192, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. - const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12); + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1132, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f); return ret; }, __wbindgen_cast_0000000000000003: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 969, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`. - const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__had771ddc65647798); + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1194, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h854f4676fa692669); return ret; }, __wbindgen_cast_0000000000000004: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1090, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. - const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a); + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1100, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h10d35e1548938147); return ret; }, __wbindgen_cast_0000000000000005: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1140, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. - const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4); + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1132, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f_4); return ret; }, __wbindgen_cast_0000000000000006: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 637, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. - const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf); + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 333, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hdc2c23cc0c047a34); return ret; }, __wbindgen_cast_0000000000000007: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1092, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. - const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f); + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1102, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h68fbbcff54846097); return ret; }, __wbindgen_cast_0000000000000008: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1107, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`. - const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2b648e7ac8ec1fb5); + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1117, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`. + const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__had164c21c04063ef); return ret; }, __wbindgen_cast_0000000000000009: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1143, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. - const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0b7fb40a8610550c); + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1134, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h5921057d3102c7f1); return ret; }, __wbindgen_cast_000000000000000a: function(arg0) { @@ -1316,47 +1316,47 @@ function __wbg_get_imports() { }; } -function wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f(arg0, arg1) { - wasm.wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f(arg0, arg1); +function wasm_bindgen__convert__closures_____invoke__h68fbbcff54846097(arg0, arg1) { + wasm.wasm_bindgen__convert__closures_____invoke__h68fbbcff54846097(arg0, arg1); } -function wasm_bindgen__convert__closures_____invoke__h2b648e7ac8ec1fb5(arg0, arg1) { - wasm.wasm_bindgen__convert__closures_____invoke__h2b648e7ac8ec1fb5(arg0, arg1); +function wasm_bindgen__convert__closures_____invoke__had164c21c04063ef(arg0, arg1) { + wasm.wasm_bindgen__convert__closures_____invoke__had164c21c04063ef(arg0, arg1); } -function wasm_bindgen__convert__closures_____invoke__h0b7fb40a8610550c(arg0, arg1) { - wasm.wasm_bindgen__convert__closures_____invoke__h0b7fb40a8610550c(arg0, arg1); +function wasm_bindgen__convert__closures_____invoke__h5921057d3102c7f1(arg0, arg1) { + wasm.wasm_bindgen__convert__closures_____invoke__h5921057d3102c7f1(arg0, arg1); } -function wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5(arg0, arg1, arg2) { - wasm.wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5(arg0, arg1, arg2); +function wasm_bindgen__convert__closures_____invoke__h9213ab6f3ef747a1(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h9213ab6f3ef747a1(arg0, arg1, arg2); } -function wasm_bindgen__convert__closures_____invoke__had771ddc65647798(arg0, arg1, arg2) { - wasm.wasm_bindgen__convert__closures_____invoke__had771ddc65647798(arg0, arg1, arg2); +function wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f(arg0, arg1, arg2); } -function wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a(arg0, arg1, arg2) { - wasm.wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a(arg0, arg1, arg2); +function wasm_bindgen__convert__closures_____invoke__h10d35e1548938147(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h10d35e1548938147(arg0, arg1, arg2); } -function wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4(arg0, arg1, arg2) { - wasm.wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4(arg0, arg1, arg2); +function wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f_4(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f_4(arg0, arg1, arg2); } -function wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf(arg0, arg1, arg2) { - wasm.wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf(arg0, arg1, arg2); +function wasm_bindgen__convert__closures_____invoke__hdc2c23cc0c047a34(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__hdc2c23cc0c047a34(arg0, arg1, arg2); } -function wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12(arg0, arg1, arg2) { - const ret = wasm.wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12(arg0, arg1, arg2); +function wasm_bindgen__convert__closures_____invoke__h854f4676fa692669(arg0, arg1, arg2) { + const ret = wasm.wasm_bindgen__convert__closures_____invoke__h854f4676fa692669(arg0, arg1, arg2); if (ret[1]) { throw takeFromExternrefTable0(ret[0]); } } -function wasm_bindgen__convert__closures_____invoke__h0accad0f5d87788a(arg0, arg1, arg2, arg3) { - wasm.wasm_bindgen__convert__closures_____invoke__h0accad0f5d87788a(arg0, arg1, arg2, arg3); +function wasm_bindgen__convert__closures_____invoke__h1a94ffe1b1ed8350(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures_____invoke__h1a94ffe1b1ed8350(arg0, arg1, arg2, arg3); } diff --git a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm index 4cf5c48c..d094e0b1 100644 Binary files a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm and b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm differ diff --git a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm.d.ts b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm.d.ts index d15b5a0d..ff0b71e1 100644 --- a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm.d.ts +++ b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm.d.ts @@ -1,33 +1,33 @@ /* tslint:disable */ /* eslint-disable */ export const memory: WebAssembly.Memory; -export const mount_mindmap_shell: (a: any, b: any) => [number, number, number]; -export const unmount_mindmap_shell: (a: number) => [number, number]; export const mount: (a: any, b: any) => [number, number, number]; +export const mount_mindmap_shell: (a: any, b: any) => [number, number, number]; export const unmount: (a: number) => [number, number]; -export const __wbg_intounderlyingsink_free: (a: number, b: number) => void; -export const intounderlyingsink_write: (a: number, b: any) => any; -export const intounderlyingsink_close: (a: number) => any; -export const intounderlyingsink_abort: (a: number, b: any) => any; +export const unmount_mindmap_shell: (a: number) => [number, number]; export const __wbg_intounderlyingbytesource_free: (a: number, b: number) => void; -export const intounderlyingbytesource_type: (a: number) => number; export const intounderlyingbytesource_autoAllocateChunkSize: (a: number) => number; -export const intounderlyingbytesource_start: (a: number, b: any) => void; -export const intounderlyingbytesource_pull: (a: number, b: any) => any; export const intounderlyingbytesource_cancel: (a: number) => void; +export const intounderlyingbytesource_pull: (a: number, b: any) => any; +export const intounderlyingbytesource_start: (a: number, b: any) => void; +export const intounderlyingbytesource_type: (a: number) => number; +export const __wbg_intounderlyingsink_free: (a: number, b: number) => void; +export const intounderlyingsink_abort: (a: number, b: any) => any; +export const intounderlyingsink_close: (a: number) => any; +export const intounderlyingsink_write: (a: number, b: any) => any; export const __wbg_intounderlyingsource_free: (a: number, b: number) => void; -export const intounderlyingsource_pull: (a: number, b: any) => any; export const intounderlyingsource_cancel: (a: number) => void; -export const wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12: (a: number, b: number, c: any) => [number, number]; -export const wasm_bindgen__convert__closures_____invoke__h0accad0f5d87788a: (a: number, b: number, c: any, d: any) => void; -export const wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5: (a: number, b: number, c: any) => void; -export const wasm_bindgen__convert__closures_____invoke__had771ddc65647798: (a: number, b: number, c: any) => void; -export const wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a: (a: number, b: number, c: any) => void; -export const wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4: (a: number, b: number, c: any) => void; -export const wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf: (a: number, b: number, c: any) => void; -export const wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f: (a: number, b: number) => void; -export const wasm_bindgen__convert__closures_____invoke__h2b648e7ac8ec1fb5: (a: number, b: number) => void; -export const wasm_bindgen__convert__closures_____invoke__h0b7fb40a8610550c: (a: number, b: number) => void; +export const intounderlyingsource_pull: (a: number, b: any) => any; +export const wasm_bindgen__convert__closures_____invoke__h854f4676fa692669: (a: number, b: number, c: any) => [number, number]; +export const wasm_bindgen__convert__closures_____invoke__h1a94ffe1b1ed8350: (a: number, b: number, c: any, d: any) => void; +export const wasm_bindgen__convert__closures_____invoke__h9213ab6f3ef747a1: (a: number, b: number, c: any) => void; +export const wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f: (a: number, b: number, c: any) => void; +export const wasm_bindgen__convert__closures_____invoke__h10d35e1548938147: (a: number, b: number, c: any) => void; +export const wasm_bindgen__convert__closures_____invoke__h280638b828d42e1f_4: (a: number, b: number, c: any) => void; +export const wasm_bindgen__convert__closures_____invoke__hdc2c23cc0c047a34: (a: number, b: number, c: any) => void; +export const wasm_bindgen__convert__closures_____invoke__h68fbbcff54846097: (a: number, b: number) => void; +export const wasm_bindgen__convert__closures_____invoke__had164c21c04063ef: (a: number, b: number) => void; +export const wasm_bindgen__convert__closures_____invoke__h5921057d3102c7f1: (a: number, b: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __externref_table_alloc: () => number; diff --git a/rust/spikes/leptos-tiptap-spike/generated/island/snippets/leptos-tiptap-c355e6ec24c3df4c/src/js/generated/tiptap_paragraph.js b/rust/spikes/leptos-tiptap-spike/generated/island/snippets/leptos-tiptap-c355e6ec24c3df4c/src/js/generated/tiptap_paragraph.js index fd222c47..a010cd50 100644 --- a/rust/spikes/leptos-tiptap-spike/generated/island/snippets/leptos-tiptap-c355e6ec24c3df4c/src/js/generated/tiptap_paragraph.js +++ b/rust/spikes/leptos-tiptap-spike/generated/island/snippets/leptos-tiptap-c355e6ec24c3df4c/src/js/generated/tiptap_paragraph.js @@ -1,4 +1,4 @@ -var Zb=Object.create;var T0=Object.defineProperty;var Qb=Object.getOwnPropertyDescriptor;var Jb=Object.getOwnPropertyNames;var ew=Object.getPrototypeOf,tw=Object.prototype.hasOwnProperty;var iw=(i,e,t)=>e in i?T0(i,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):i[e]=t;var T=(i,e)=>()=>(i&&(e=i(i=0)),e);var rr=(i,e)=>()=>(e||i((e={exports:{}}).exports,e),e.exports),tt=(i,e)=>{for(var t in e)T0(i,t,{get:e[t],enumerable:!0})},rw=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Jb(e))!tw.call(i,n)&&n!==t&&T0(i,n,{get:()=>e[n],enumerable:!(r=Qb(e,n))||r.enumerable});return i};var pt=(i,e,t)=>(t=i!=null?Zb(ew(i)):{},rw(e||!i||!i.__esModule?T0(t,"default",{value:i,enumerable:!0}):t,i));var U=(i,e,t)=>iw(i,typeof e!="symbol"?e+"":e,t);function Fc(){if(!k0&&(k0=typeof crypto<"u"&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!k0))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return k0(fw)}var k0,fw,N3=T(()=>{fw=new Uint8Array(16)});function E3(i,e=0){return xt[i[e+0]]+xt[i[e+1]]+xt[i[e+2]]+xt[i[e+3]]+"-"+xt[i[e+4]]+xt[i[e+5]]+"-"+xt[i[e+6]]+xt[i[e+7]]+"-"+xt[i[e+8]]+xt[i[e+9]]+"-"+xt[i[e+10]]+xt[i[e+11]]+xt[i[e+12]]+xt[i[e+13]]+xt[i[e+14]]+xt[i[e+15]]}var xt,S3=T(()=>{xt=[];for(let i=0;i<256;++i)xt.push((i+256).toString(16).slice(1))});var mw,qc,A3=T(()=>{mw=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),qc={randomUUID:mw}});function pw(i,e,t){if(qc.randomUUID&&!e&&!i)return qc.randomUUID();i=i||{};let r=i.random||(i.rng||Fc)();if(r[6]=r[6]&15|64,r[8]=r[8]&63|128,e){t=t||0;for(let n=0;n<16;++n)e[t+n]=r[n];return e}return E3(r)}var fo,k3=T(()=>{A3();N3();S3();fo=pw});var Hc=T(()=>{k3()});var k,Uc,eU,$c,$s,Mi,C3,_3,js,L3,$e=T(()=>{k={CHANGE_THEME:"changeTheme",CHANGE_LAYOUT:"changeLayout",MODE:{READONLY:"readonly",EDIT:"edit"},LAYOUT:{LOGICAL_STRUCTURE:"logicalStructure",LOGICAL_STRUCTURE_LEFT:"logicalStructureLeft",MIND_MAP:"mindMap",ORGANIZATION_STRUCTURE:"organizationStructure",CATALOG_ORGANIZATION:"catalogOrganization",TIMELINE:"timeline",TIMELINE2:"timeline2",FISHBONE:"fishbone",FISHBONE2:"fishbone2",RIGHT_FISHBONE:"rightFishbone",RIGHT_FISHBONE2:"rightFishbone2",VERTICAL_TIMELINE:"verticalTimeline",VERTICAL_TIMELINE2:"verticalTimeline2",VERTICAL_TIMELINE3:"verticalTimeline3"},DIR:{UP:"up",LEFT:"left",DOWN:"down",RIGHT:"right"},KEY_DIR:{LEFT:"Left",UP:"Up",RIGHT:"Right",DOWN:"Down"},SHAPE:{RECTANGLE:"rectangle",DIAMOND:"diamond",PARALLELOGRAM:"parallelogram",ROUNDED_RECTANGLE:"roundedRectangle",OCTAGONAL_RECTANGLE:"octagonalRectangle",OUTER_TRIANGULAR_RECTANGLE:"outerTriangularRectangle",INNER_TRIANGULAR_RECTANGLE:"innerTriangularRectangle",ELLIPSE:"ellipse",CIRCLE:"circle"},MOUSE_WHEEL_ACTION:{ZOOM:"zoom",MOVE:"move"},INIT_ROOT_NODE_POSITION:{LEFT:"left",TOP:"top",RIGHT:"right",BOTTOM:"bottom",CENTER:"center"},LAYOUT_GROW_DIR:{LEFT:"left",TOP:"top",RIGHT:"right",BOTTOM:"bottom"},PASTE_TYPE:{CLIP_BOARD:"clipBoard",CANVAS:"canvas"},SCROLL_BAR_DIR:{VERTICAL:"vertical",HORIZONTAL:"horizontal"},CREATE_NEW_NODE_BEHAVIOR:{DEFAULT:"default",NOT_ACTIVE:"notActive",ACTIVE_ONLY:"activeOnly"},TAG_PLACEMENT:{RIGHT:"right",BOTTOM:"bottom"},IMG_PLACEMENT:{LEFT:"left",TOP:"top",RIGHT:"right",BOTTOM:"bottom"}},Uc={[k.INIT_ROOT_NODE_POSITION.LEFT]:0,[k.INIT_ROOT_NODE_POSITION.TOP]:0,[k.INIT_ROOT_NODE_POSITION.RIGHT]:1,[k.INIT_ROOT_NODE_POSITION.BOTTOM]:1,[k.INIT_ROOT_NODE_POSITION.CENTER]:.5},eU=[{name:"\u903B\u8F91\u7ED3\u6784\u56FE",value:k.LAYOUT.LOGICAL_STRUCTURE},{name:"\u5411\u5DE6\u903B\u8F91\u7ED3\u6784\u56FE",value:k.LAYOUT.LOGICAL_STRUCTURE_LEFT},{name:"\u601D\u7EF4\u5BFC\u56FE",value:k.LAYOUT.MIND_MAP},{name:"\u7EC4\u7EC7\u7ED3\u6784\u56FE",value:k.LAYOUT.ORGANIZATION_STRUCTURE},{name:"\u76EE\u5F55\u7EC4\u7EC7\u56FE",value:k.LAYOUT.CATALOG_ORGANIZATION},{name:"\u65F6\u95F4\u8F74",value:k.LAYOUT.TIMELINE},{name:"\u65F6\u95F4\u8F742",value:k.LAYOUT.TIMELINE2},{name:"\u7AD6\u5411\u65F6\u95F4\u8F74",value:k.LAYOUT.VERTICAL_TIMELINE},{name:"\u7AD6\u5411\u65F6\u95F4\u8F742",value:k.LAYOUT.VERTICAL_TIMELINE2},{name:"\u7AD6\u5411\u65F6\u95F4\u8F743",value:k.LAYOUT.VERTICAL_TIMELINE3},{name:"\u9C7C\u9AA8\u56FE",value:k.LAYOUT.FISHBONE},{name:"\u9C7C\u9AA8\u56FE2",value:k.LAYOUT.FISHBONE2},{name:"\u5411\u53F3\u9C7C\u9AA8\u56FE",value:k.LAYOUT.RIGHT_FISHBONE},{name:"\u5411\u53F3\u9C7C\u9AA8\u56FE2",value:k.LAYOUT.RIGHT_FISHBONE2}],$c=[k.LAYOUT.LOGICAL_STRUCTURE,k.LAYOUT.LOGICAL_STRUCTURE_LEFT,k.LAYOUT.MIND_MAP,k.LAYOUT.CATALOG_ORGANIZATION,k.LAYOUT.ORGANIZATION_STRUCTURE,k.LAYOUT.TIMELINE,k.LAYOUT.TIMELINE2,k.LAYOUT.VERTICAL_TIMELINE,k.LAYOUT.VERTICAL_TIMELINE2,k.LAYOUT.VERTICAL_TIMELINE3,k.LAYOUT.FISHBONE,k.LAYOUT.FISHBONE2,k.LAYOUT.RIGHT_FISHBONE,k.LAYOUT.RIGHT_FISHBONE2],$s=["text","image","imageTitle","imageSize","icon","tag","hyperlink","hyperlinkTitle","note","expand","isActive","generalization","richText","resetRichText","uid","activeStyle","associativeLineTargets","associativeLineTargetControlOffsets","associativeLinePoint","associativeLineText","attachmentUrl","attachmentName","notation","outerFrame","number","range","customLeft","customTop","customTextWidth","checkbox","dir","needUpdate","imgMap","nodeLink"],Mi={READ_CLIPBOARD_ERROR:"read_clipboard_error",PARSE_PASTE_DATA_ERROR:"parse_paste_data_error",CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR:"custom_handle_clipboard_text_error",LOAD_CLIPBOARD_IMAGE_ERROR:"load_clipboard_image_error",BEFORE_TEXT_EDIT_ERROR:"before_text_edit_error",EXPORT_ERROR:"export_error",EXPORT_LOAD_IMAGE_ERROR:"export_load_image_error",DATA_CHANGE_DETAIL_EVENT_ERROR:"data_change_detail_event_error"},C3=` +var ww=Object.create;var L0=Object.defineProperty;var Mw=Object.getOwnPropertyDescriptor;var Tw=Object.getOwnPropertyNames;var Nw=Object.getPrototypeOf,Ew=Object.prototype.hasOwnProperty;var Sw=(i,e,t)=>e in i?L0(i,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):i[e]=t;var T=(i,e)=>()=>(i&&(e=i(i=0)),e);var tr=(i,e)=>()=>(e||i((e={exports:{}}).exports,e),e.exports),tt=(i,e)=>{for(var t in e)L0(i,t,{get:e[t],enumerable:!0})},Aw=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Tw(e))!Ew.call(i,n)&&n!==t&&L0(i,n,{get:()=>e[n],enumerable:!(r=Mw(e,n))||r.enumerable});return i};var yt=(i,e,t)=>(t=i!=null?ww(Nw(i)):{},Aw(e||!i||!i.__esModule?L0(t,"default",{value:i,enumerable:!0}):t,i));var U=(i,e,t)=>Sw(i,typeof e!="symbol"?e+"":e,t);function Kc(){if(!O0&&(O0=typeof crypto<"u"&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!O0))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return O0(Bw)}var O0,Bw,P3=T(()=>{Bw=new Uint8Array(16)});function F3(i,e=0){return bt[i[e+0]]+bt[i[e+1]]+bt[i[e+2]]+bt[i[e+3]]+"-"+bt[i[e+4]]+bt[i[e+5]]+"-"+bt[i[e+6]]+bt[i[e+7]]+"-"+bt[i[e+8]]+bt[i[e+9]]+"-"+bt[i[e+10]]+bt[i[e+11]]+bt[i[e+12]]+bt[i[e+13]]+bt[i[e+14]]+bt[i[e+15]]}var bt,q3=T(()=>{bt=[];for(let i=0;i<256;++i)bt.push((i+256).toString(16).slice(1))});var Pw,Zc,H3=T(()=>{Pw=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),Zc={randomUUID:Pw}});function Fw(i,e,t){if(Zc.randomUUID&&!e&&!i)return Zc.randomUUID();i=i||{};let r=i.random||(i.rng||Kc)();if(r[6]=r[6]&15|64,r[8]=r[8]&63|128,e){t=t||0;for(let n=0;n<16;++n)e[t+n]=r[n];return e}return F3(r)}var vo,U3=T(()=>{H3();P3();q3();vo=Fw});var Qc=T(()=>{U3()});var C,Jc,SU,eu,js,Ti,$3,j3,Gs,G3,Ge=T(()=>{C={CHANGE_THEME:"changeTheme",CHANGE_LAYOUT:"changeLayout",MODE:{READONLY:"readonly",EDIT:"edit"},LAYOUT:{LOGICAL_STRUCTURE:"logicalStructure",LOGICAL_STRUCTURE_LEFT:"logicalStructureLeft",MIND_MAP:"mindMap",ORGANIZATION_STRUCTURE:"organizationStructure",CATALOG_ORGANIZATION:"catalogOrganization",TIMELINE:"timeline",TIMELINE2:"timeline2",FISHBONE:"fishbone",FISHBONE2:"fishbone2",RIGHT_FISHBONE:"rightFishbone",RIGHT_FISHBONE2:"rightFishbone2",VERTICAL_TIMELINE:"verticalTimeline",VERTICAL_TIMELINE2:"verticalTimeline2",VERTICAL_TIMELINE3:"verticalTimeline3"},DIR:{UP:"up",LEFT:"left",DOWN:"down",RIGHT:"right"},KEY_DIR:{LEFT:"Left",UP:"Up",RIGHT:"Right",DOWN:"Down"},SHAPE:{RECTANGLE:"rectangle",DIAMOND:"diamond",PARALLELOGRAM:"parallelogram",ROUNDED_RECTANGLE:"roundedRectangle",OCTAGONAL_RECTANGLE:"octagonalRectangle",OUTER_TRIANGULAR_RECTANGLE:"outerTriangularRectangle",INNER_TRIANGULAR_RECTANGLE:"innerTriangularRectangle",ELLIPSE:"ellipse",CIRCLE:"circle"},MOUSE_WHEEL_ACTION:{ZOOM:"zoom",MOVE:"move"},INIT_ROOT_NODE_POSITION:{LEFT:"left",TOP:"top",RIGHT:"right",BOTTOM:"bottom",CENTER:"center"},LAYOUT_GROW_DIR:{LEFT:"left",TOP:"top",RIGHT:"right",BOTTOM:"bottom"},PASTE_TYPE:{CLIP_BOARD:"clipBoard",CANVAS:"canvas"},SCROLL_BAR_DIR:{VERTICAL:"vertical",HORIZONTAL:"horizontal"},CREATE_NEW_NODE_BEHAVIOR:{DEFAULT:"default",NOT_ACTIVE:"notActive",ACTIVE_ONLY:"activeOnly"},TAG_PLACEMENT:{RIGHT:"right",BOTTOM:"bottom"},IMG_PLACEMENT:{LEFT:"left",TOP:"top",RIGHT:"right",BOTTOM:"bottom"}},Jc={[C.INIT_ROOT_NODE_POSITION.LEFT]:0,[C.INIT_ROOT_NODE_POSITION.TOP]:0,[C.INIT_ROOT_NODE_POSITION.RIGHT]:1,[C.INIT_ROOT_NODE_POSITION.BOTTOM]:1,[C.INIT_ROOT_NODE_POSITION.CENTER]:.5},SU=[{name:"\u903B\u8F91\u7ED3\u6784\u56FE",value:C.LAYOUT.LOGICAL_STRUCTURE},{name:"\u5411\u5DE6\u903B\u8F91\u7ED3\u6784\u56FE",value:C.LAYOUT.LOGICAL_STRUCTURE_LEFT},{name:"\u601D\u7EF4\u5BFC\u56FE",value:C.LAYOUT.MIND_MAP},{name:"\u7EC4\u7EC7\u7ED3\u6784\u56FE",value:C.LAYOUT.ORGANIZATION_STRUCTURE},{name:"\u76EE\u5F55\u7EC4\u7EC7\u56FE",value:C.LAYOUT.CATALOG_ORGANIZATION},{name:"\u65F6\u95F4\u8F74",value:C.LAYOUT.TIMELINE},{name:"\u65F6\u95F4\u8F742",value:C.LAYOUT.TIMELINE2},{name:"\u7AD6\u5411\u65F6\u95F4\u8F74",value:C.LAYOUT.VERTICAL_TIMELINE},{name:"\u7AD6\u5411\u65F6\u95F4\u8F742",value:C.LAYOUT.VERTICAL_TIMELINE2},{name:"\u7AD6\u5411\u65F6\u95F4\u8F743",value:C.LAYOUT.VERTICAL_TIMELINE3},{name:"\u9C7C\u9AA8\u56FE",value:C.LAYOUT.FISHBONE},{name:"\u9C7C\u9AA8\u56FE2",value:C.LAYOUT.FISHBONE2},{name:"\u5411\u53F3\u9C7C\u9AA8\u56FE",value:C.LAYOUT.RIGHT_FISHBONE},{name:"\u5411\u53F3\u9C7C\u9AA8\u56FE2",value:C.LAYOUT.RIGHT_FISHBONE2}],eu=[C.LAYOUT.LOGICAL_STRUCTURE,C.LAYOUT.LOGICAL_STRUCTURE_LEFT,C.LAYOUT.MIND_MAP,C.LAYOUT.CATALOG_ORGANIZATION,C.LAYOUT.ORGANIZATION_STRUCTURE,C.LAYOUT.TIMELINE,C.LAYOUT.TIMELINE2,C.LAYOUT.VERTICAL_TIMELINE,C.LAYOUT.VERTICAL_TIMELINE2,C.LAYOUT.VERTICAL_TIMELINE3,C.LAYOUT.FISHBONE,C.LAYOUT.FISHBONE2,C.LAYOUT.RIGHT_FISHBONE,C.LAYOUT.RIGHT_FISHBONE2],js=["text","image","imageTitle","imageSize","icon","tag","hyperlink","hyperlinkTitle","note","expand","isActive","generalization","richText","resetRichText","uid","activeStyle","associativeLineTargets","associativeLineTargetControlOffsets","associativeLinePoint","associativeLineText","attachmentUrl","attachmentName","notation","outerFrame","number","range","customLeft","customTop","customTextWidth","checkbox","dir","needUpdate","imgMap","nodeLink"],Ti={READ_CLIPBOARD_ERROR:"read_clipboard_error",PARSE_PASTE_DATA_ERROR:"parse_paste_data_error",CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR:"custom_handle_clipboard_text_error",LOAD_CLIPBOARD_IMAGE_ERROR:"load_clipboard_image_error",BEFORE_TEXT_EDIT_ERROR:"before_text_edit_error",EXPORT_ERROR:"export_error",EXPORT_LOAD_IMAGE_ERROR:"export_load_image_error",DATA_CHANGE_DETAIL_EVENT_ERROR:"data_change_detail_event_error"},$3=` /* \u9F20\u6807hover\u548C\u6FC0\u6D3B\u65F6\u6E32\u67D3\u7684\u77E9\u5F62 */ .smm-hover-node{ display: none; @@ -19,13 +19,13 @@ var Zb=Object.create;var T0=Object.defineProperty;var Qb=Object.getOwnPropertyDe .smm-text-node-wrap, .smm-expand-btn-text { user-select: none; } -`,_3=["img","br","hr","input","link","meta","area"],js=1.2,L3=["fontFamily","fontSize","fontWeight","fontStyle","textDecoration","color","textAlign"]});function mo(i){this.N=624,this.M=397,this.MATRIX_A=2567483615,this.UPPER_MASK=2147483648,this.LOWER_MASK=2147483647,this.mt=new Array(this.N),this.mti=this.N+1,this.init_genrand(i)}var I3=T(()=>{mo.prototype.init_genrand=function(i){for(this.mt[0]=i>>>0,this.mti=1;this.mti>>30,this.mt[this.mti]=(((i&4294901760)>>>16)*1812433253<<16)+(i&65535)*1812433253+this.mti,this.mt[this.mti]>>>=0};mo.prototype.genrand_int32=function(){var i,e=new Array(0,this.MATRIX_A);if(this.mti>=this.N){var t;for(this.mti==this.N+1&&this.init_genrand(5489),t=0;t>>1^e[i&1];for(;t>>1^e[i&1];i=this.mt[this.N-1]&this.UPPER_MASK|this.mt[0]&this.LOWER_MASK,this.mt[this.N-1]=this.mt[this.M-1]^i>>>1^e[i&1],this.mti=0}return i=this.mt[this.mti++],i^=i>>>11,i^=i<<7&2636928640,i^=i<<15&4022730752,i^=i>>>18,i>>>0}});function fe(i,e){if(Array.isArray(i)){for(let t of i)fe(t,e);return}if(typeof i=="object"){for(let t in i)fe(t,i[t]);return}q3(Object.getOwnPropertyNames(e)),Yc[i]=Object.assign(Yc[i]||{},e)}function Wt(i){return Yc[i]||{}}function gw(){return[...new Set(F3)]}function q3(i){F3.push(...i)}function ru(i,e){let t,r=i.length,n=[];for(t=0;t=0;e--)$3(i.children[e]);return i.id&&(i.id=U3(i.nodeName)),i}function ge(i,e){let t,r;for(i=Array.isArray(i)?i:[i],r=i.length-1;r>=0;r--)for(t in e)i[r].prototype[t]=e[t]}function Fe(i){return function(...e){let t=e[e.length-1];return t&&t.constructor===Object&&!(t instanceof Array)?i.apply(this,e.slice(0,-1)).attr(t):i.apply(this,e)}}function Tw(){return this.parent().children()}function Nw(){return this.parent().index(this)}function Ew(){return this.siblings()[this.position()+1]}function Sw(){return this.siblings()[this.position()-1]}function Aw(){let i=this.position();return this.parent().add(this.remove(),i+1),this}function kw(){let i=this.position();return this.parent().add(this.remove(),i?i-1:0),this}function Cw(){return this.parent().add(this.remove()),this}function _w(){return this.parent().add(this.remove(),0),this}function Lw(i){i=Rt(i),i.remove();let e=this.position();return this.parent().add(i,e),this}function Iw(i){i=Rt(i),i.remove();let e=this.position();return this.parent().add(i,e+1),this}function zw(i){return i=Rt(i),i.before(this),this}function Rw(i){return i=Rt(i),i.after(this),this}function Hw(){let i=this.attr("class");return i==null?[]:i.trim().split(Sr)}function Uw(i){return this.classes().indexOf(i)!==-1}function $w(i){if(!this.hasClass(i)){let e=this.classes();e.push(i),this.attr("class",e.join(" "))}return this}function jw(i){return this.hasClass(i)&&this.attr("class",this.classes().filter(function(e){return e!==i}).join(" ")),this}function Gw(i){return this.hasClass(i)?this.removeClass(i):this.addClass(i)}function Vw(i,e){let t={};if(arguments.length===0)return this.node.style.cssText.split(/\s*;\s*/).filter(function(r){return!!r.length}).forEach(function(r){let n=r.split(/\s*:\s*/);t[n[0]]=n[1]}),t;if(arguments.length<2){if(Array.isArray(i)){for(let r of i){let n=C0(r);t[r]=this.node.style[n]}return t}if(typeof i=="string")return this.node.style[C0(i)];if(typeof i=="object")for(let r in i)this.node.style[C0(r)]=i[r]==null||D3.test(i[r])?"":i[r]}return arguments.length===2&&(this.node.style[C0(i)]=e==null||D3.test(e)?"":e),this}function Ww(){return this.css("display","")}function Yw(){return this.css("display","none")}function Xw(){return this.css("display")!=="none"}function Kw(i,e,t){if(i==null)return this.data(ru(xw(this.node.attributes,r=>r.nodeName.indexOf("data-")===0),r=>r.nodeName.slice(5)));if(i instanceof Array){let r={};for(let n of i)r[n]=this.data(n);return r}else if(typeof i=="object")for(e in i)this.data(e,i[e]);else if(arguments.length<2)try{return JSON.parse(this.attr("data-"+i))}catch{return this.attr("data-"+i)}else this.attr("data-"+i,e===null?null:t===!0||typeof e=="string"||typeof e=="number"?e:JSON.stringify(e));return this}function Zw(i,e){if(typeof arguments[0]=="object")for(let t in i)this.remember(t,i[t]);else{if(arguments.length===1)return this.memory()[i];this.memory()[i]=e}return this}function Qw(){if(arguments.length===0)this._memory={};else for(let i=arguments.length-1;i>=0;i--)delete this.memory()[arguments[i]];return this}function Jw(){return this._memory=this._memory||{}}function eM(i){return i.length===4?["#",i.substring(1,2),i.substring(1,2),i.substring(2,3),i.substring(2,3),i.substring(3,4),i.substring(3,4)].join(""):i}function tM(i){let e=Math.round(i),r=Math.max(0,Math.min(255,e)).toString(16);return r.length===1?"0"+r:r}function Gs(i,e){for(let t=e.length;t--;)if(i[e[t]]==null)return!1;return!0}function iM(i,e){let t=Gs(i,"rgb")?{_a:i.r,_b:i.g,_c:i.b,_d:0,space:"rgb"}:Gs(i,"xyz")?{_a:i.x,_b:i.y,_c:i.z,_d:0,space:"xyz"}:Gs(i,"hsl")?{_a:i.h,_b:i.s,_c:i.l,_d:0,space:"hsl"}:Gs(i,"lab")?{_a:i.l,_b:i.a,_c:i.b,_d:0,space:"lab"}:Gs(i,"lch")?{_a:i.l,_b:i.c,_c:i.h,_d:0,space:"lch"}:Gs(i,"cmyk")?{_a:i.c,_b:i.m,_c:i.y,_d:i.k,space:"cmyk"}:{_a:0,_b:0,_c:0,space:"rgb"};return t.space=e||t.space,t}function rM(i){return i==="lab"||i==="xyz"||i==="lch"}function Vc(i,e,t){return t<0&&(t+=1),t>1&&(t-=1),t<1/6?i+(e-i)*6*t:t<1/2?e:t<2/3?i+(e-i)*(2/3-t)*6:i}function nM(i,e){return new it(i,e).transformO(this.screenCTM().inverseO())}function Vs(i,e,t){return Math.abs(e-i)<(t||1e-6)}function sM(){return new xe(this.node.getCTM())}function aM(){if(typeof this.isRoot=="function"&&!this.isRoot()){let i=this.rect(1,1),e=i.node.getScreenCTM();return i.remove(),new xe(e)}return new xe(this.node.getScreenCTM())}function ln(){if(!ln.nodes){let i=Rt().size(2,0);i.node.style.cssText=["opacity: 0","position: absolute","left: -100%","top: -100%","overflow: hidden"].join(";"),i.attr("focusable","false"),i.attr("aria-hidden","true");let e=i.path().node;ln.nodes={svg:i,path:e}}if(!ln.nodes.svg.node.parentNode){let i=Te.document.body||Te.document.documentElement;ln.nodes.svg.addTo(i)}return ln.nodes}function G3(i){return!i.width&&!i.height&&!i.x&&!i.y}function oM(i){return i===Te.document||(Te.document.documentElement.contains||function(e){for(;e.parentNode;)e=e.parentNode;return e===Te.document}).call(Te.document.documentElement,i)}function V3(i,e,t){let r;try{if(r=e(i.node),G3(r)&&!oM(i.node))throw new Error("Element not in the dom")}catch{r=t(i)}return r}function lM(){let t=V3(this,n=>n.getBBox(),n=>{try{let s=n.clone().addTo(ln().svg).show(),a=s.node.getBBox();return s.remove(),a}catch(s){throw new Error(`Getting bbox of element "${n.node.nodeName}" is not possible: ${s.toString()}`)}});return new Gt(t)}function hM(i){let r=V3(this,s=>s.getBoundingClientRect(),s=>{throw new Error(`Getting rbox of element "${s.node.nodeName}" is not possible`)}),n=new Gt(r);return i?n.transform(i.screenCTM().inverseO()):n.addOffset()}function dM(i,e){let t=this.bbox();return i>t.x&&e>t.y&&i(r[n]=this.attr(n),r),{});if(typeof i=="object"&&i.constructor===Object)for(e in i)this.attr(e,i[e]);else if(e===null)this.node.removeAttribute(i);else{if(e==null)return e=this.node.getAttribute(i),e==null?xM[i]:O3.test(e)?parseFloat(e):e;e=X3.reduce((r,n)=>n(i,r,this),e),typeof e=="number"?e=new ye(e):Ni.isColor(e)?e=new Ni(e):e.constructor===Array&&(e=new hn(e)),i==="leading"?this.leading&&this.leading(e):typeof t=="string"?this.node.setAttributeNS(t,i,e.toString()):this.node.setAttribute(i,e.toString()),this.rebuild&&(i==="font-size"||i==="x")&&this.rebuild()}}return this}function wM(){return this.attr("transform",null)}function MM(){return(this.attr("transform")||"").split(Pw).slice(0,-1).map(function(e){let t=e.trim().split("(");return[t[0],t[1].split(Sr).map(function(r){return parseFloat(r)})]}).reverse().reduce(function(e,t){return t[0]==="matrix"?e.lmultiply(xe.fromArray(t[1])):e[t[0]].apply(e,t[1])},new xe)}function TM(i,e){if(this===i)return this;let t=this.screenCTM(),r=i.screenCTM().inverse();return this.addTo(i,e).untransform().transform(r.multiply(t)),this}function NM(i){return this.toParent(this.root(),i)}function EM(i,e){if(i==null||typeof i=="string"){let n=new xe(this).decompose();return i==null?n:n[i]}xe.isMatrixLike(i)||(i={...i,origin:Xc(i,this)});let t=e===!0?this:e||!1,r=new xe(t).transform(i);return this.attr("transform",r)}function lu(i){return this.attr("rx",i)}function hu(i){return this.attr("ry",i)}function K3(i){return i==null?this.cx()-this.rx():this.cx(i+this.rx())}function Z3(i){return i==null?this.cy()-this.ry():this.cy(i+this.ry())}function Q3(i){return this.attr("cx",i)}function J3(i){return this.attr("cy",i)}function e2(i){return i==null?this.rx()*2:this.rx(new ye(i).divide(2))}function t2(i){return i==null?this.ry()*2:this.ry(new ye(i).divide(2))}function i2(i,e){return(this._element||this).type==="radialGradient"?this.attr({fx:new ye(i),fy:new ye(e)}):this.attr({x1:new ye(i),y1:new ye(e)})}function r2(i,e){return(this._element||this).type==="radialGradient"?this.attr({cx:new ye(i),cy:new ye(e)}):this.attr({x2:new ye(i),y2:new ye(e)})}function CM(i){return i==null?this.bbox().x:this.move(i,this.bbox().y)}function _M(i){return i==null?this.bbox().y:this.move(this.bbox().x,i)}function LM(i){let e=this.bbox();return i==null?e.width:this.size(i,e.height)}function IM(i){let e=this.bbox();return i==null?e.height:this.size(e.width,i)}function Ys(i,e){return function(t){return t==null?this[i]:(this[i]=t,e&&e.call(this),this)}}function P3(){let i=(this._duration||500)/1e3,e=this._overshoot||0,t=1e-10,r=Math.PI,n=Math.log(e/100+t),s=-n/Math.sqrt(r*r+n*n),a=3.9/(s*i);this.d=2*s*a,this.k=a*a}function DM(i){let e=i.segment[0];return Jc[e](i.segment.slice(1),i.p,i.p0)}function eu(i){return i.segment.length&&i.segment.length-1===RM[i.segment[0].toUpperCase()]}function OM(i,e){i.inNumber&&Jn(i,!1);let t=au.test(e);if(t)i.segment=[e];else{let r=i.lastCommand,n=r.toLowerCase(),s=r===n;i.segment=[n==="m"?s?"l":"L":r]}return i.inSegment=!0,i.lastCommand=i.segment[0],t}function Jn(i,e){if(!i.inNumber)throw new Error("Parser Error");i.number&&i.segment.push(parseFloat(i.number)),i.inNumber=e,i.number="",i.pointSeen=!1,i.hasExponent=!1,eu(i)&&tu(i)}function tu(i){i.inSegment=!1,i.absolute&&(i.segment=DM(i)),i.segments.push(i.segment)}function BM(i){if(!i.segment.length)return!1;let e=i.segment[0].toUpperCase()==="A",t=i.segment.length;return e&&(t===4||t===5)}function PM(i){return i.lastToken.toUpperCase()==="E"}function FM(i,e=!0){let t=0,r="",n={segment:[],inNumber:!1,number:"",lastToken:"",inSegment:!1,segments:[],pointSeen:!1,hasExponent:!1,absolute:e,p0:new it,p:new it};for(;n.lastToken=r,r=i.charAt(t++);)if(!(!n.inSegment&&OM(n,r))){if(r==="."){if(n.pointSeen||n.hasExponent){Jn(n,!1),--t;continue}n.inNumber=!0,n.pointSeen=!0,n.number+=r;continue}if(!isNaN(parseInt(r))){if(n.number==="0"||BM(n)){n.inNumber=!0,n.number=r,Jn(n,!0);continue}n.inNumber=!0,n.number+=r;continue}if(r===" "||r===","){n.inNumber&&Jn(n,!1);continue}if(r==="-"){if(n.inNumber&&!PM(n)){Jn(n,!1),--t;continue}n.number+=r,n.inNumber=!0;continue}if(r.toUpperCase()==="E"){n.number+=r,n.hasExponent=!0;continue}if(au.test(r)){if(n.inNumber)Jn(n,!1);else if(eu(n))tu(n);else throw new Error("parser Error");--t}}return n.inNumber&&Jn(n,!1),n.inSegment&&eu(n)&&tu(n),n.segments}function qM(i){let e="";for(let t=0,r=i.length;t{let n;try{n=t.bbox()}catch{return}let s=new xe(t),a=s.translate(i,e).transform(s.inverse()),o=new it(n.x,n.y).transform(a);t.move(o.x,o.y)}),this}function uT(i){return this.dmove(i,0)}function fT(i){return this.dmove(0,i)}function mT(i,e=this.bbox()){return i==null?e.height:this.size(e.width,i,e)}function pT(i=0,e=0,t=this.bbox()){let r=i-t.x,n=e-t.y;return this.dmove(r,n)}function gT(i,e,t=this.bbox()){let r=ta(this,i,e,t),n=r.width/t.width,s=r.height/t.height;return this.children().forEach((a,o)=>{let l=new it(t).transform(new xe(a).inverse());a.scale(n,s,l.x,l.y)}),this}function xT(i,e=this.bbox()){return i==null?e.width:this.size(i,e.height,e)}function yT(i,e=this.bbox()){return i==null?e.x:this.move(i,e.y,e)}function vT(i,e=this.bbox()){return i==null?e.y:this.move(e.x,i,e)}function bT(i,e){if(!i)return"";if(!e)return i;let t=i+"{";for(let r in e)t+=yw(r)+":"+e[r]+";";return t+="}",t}var Yc,F3,nu,vw,_0,Lo,bw,Te,yo,es,su,Gc,Mw,j3,Dw,Ow,Bw,Pw,Fw,z3,R3,D3,O3,qw,Sr,au,Ni,it,xe,Gt,Nr,cM,mM,W3,ts,go,xM,hn,ye,X3,dn,Ei,po,bM,Vt,bo,Dt,SM,Xs,L0,AM,is,rs,ji,sr,kM,du,ns,Ks,zM,wo,Mo,Zs,Zc,Qc,RM,Jc,Wc,Er,n2,Tr,To,No,HM,ss,cu,ar,s2,Gi,as,Ve,xo,Se,XM,KM,I0,Vi,Qs,a2,o2,iu,QM,Eo,So,l2,Ce,Js,Wi,Ao,ea,h2,_e,cn,ko,z0,Co,_o,R0,Ae,yt=T(()=>{Yc={},F3=[];nu="http://www.w3.org/2000/svg",vw="http://www.w3.org/1999/xhtml",_0="http://www.w3.org/2000/xmlns/",Lo="http://www.w3.org/1999/xlink",bw="http://svgjs.dev/svgjs",Te={window:typeof window>"u"?null:window,document:typeof document>"u"?null:document},yo=class{},es={},su="___SYMBOL___ROOT___";Gc=Ti;Mw=1e3;fe("Dom",{siblings:Tw,position:Nw,next:Ew,prev:Sw,forward:Aw,backward:kw,front:Cw,back:_w,before:Lw,after:Iw,insertBefore:zw,insertAfter:Rw});j3=/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,Dw=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,Ow=/rgb\((\d+),(\d+),(\d+)\)/,Bw=/(#[a-z_][a-z0-9\-_]*)/i,Pw=/\)\s*,?\s*/,Fw=/\s/g,z3=/^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i,R3=/^rgb\(/,D3=/^(\s+)?$/,O3=/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,qw=/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,Sr=/[\s,]+/,au=/[MLHVCSQTAZ]/i;fe("Dom",{classes:Hw,hasClass:Uw,addClass:$w,removeClass:jw,toggleClass:Gw});fe("Dom",{css:Vw,show:Ww,hide:Yw,visible:Xw});fe("Dom",{data:Kw});fe("Dom",{remember:Zw,forget:Qw,memory:Jw});Ni=class i{constructor(...e){this.init(...e)}static isColor(e){return e&&(e instanceof i||this.isRgb(e)||this.test(e))}static isRgb(e){return e&&typeof e.r=="number"&&typeof e.g=="number"&&typeof e.b=="number"}static random(e="vibrant",t,r){let{random:n,round:s,sin:a,PI:o}=Math;if(e==="vibrant"){let l=24*n()+57,h=38*n()+45,d=360*n();return new i(l,h,d,"lch")}else if(e==="sine"){t=t??n();let l=s(80*a(2*o*t/.5+.01)+150),h=s(50*a(2*o*t/.5+4.6)+200),d=s(100*a(2*o*t/.5+2.3)+150);return new i(l,h,d)}else if(e==="pastel"){let l=8*n()+86,h=17*n()+9,d=360*n();return new i(l,h,d,"lch")}else if(e==="dark"){let l=10+10*n(),h=50*n()+86,d=360*n();return new i(l,h,d,"lch")}else if(e==="rgb"){let l=255*n(),h=255*n(),d=255*n();return new i(l,h,d)}else if(e==="lab"){let l=100*n(),h=256*n()-128,d=256*n()-128;return new i(l,h,d,"lab")}else if(e==="grey"){let l=255*n();return new i(l,l,l)}else throw new Error("Unsupported random color mode")}static test(e){return typeof e=="string"&&(z3.test(e)||R3.test(e))}cmyk(){let{_a:e,_b:t,_c:r}=this.rgb(),[n,s,a]=[e,t,r].map(f=>f/255),o=Math.min(1-n,1-s,1-a);if(o===1)return new i(0,0,0,1,"cmyk");let l=(1-n-o)/(1-o),h=(1-s-o)/(1-o),d=(1-a-o)/(1-o);return new i(l,h,d,o,"cmyk")}hsl(){let{_a:e,_b:t,_c:r}=this.rgb(),[n,s,a]=[e,t,r].map(x=>x/255),o=Math.max(n,s,a),l=Math.min(n,s,a),h=(o+l)/2,d=o===l,c=o-l,f=d?0:h>.5?c/(2-o-l):c/(o+l),m=d?0:o===n?((s-a)/c+(sparseInt(x));Object.assign(this,{_a:f,_b:m,_c:g,_d:0,space:"rgb"})}else if(z3.test(e)){let c=x=>parseInt(x,16),[,f,m,g]=Dw.exec(eM(e)).map(c);Object.assign(this,{_a:f,_b:m,_c:g,_d:0,space:"rgb"})}else throw Error("Unsupported string format, can't construct Color");let{_a:a,_b:o,_c:l,_d:h}=this,d=this.space==="rgb"?{r:a,g:o,b:l}:this.space==="xyz"?{x:a,y:o,z:l}:this.space==="hsl"?{h:a,s:o,l}:this.space==="lab"?{l:a,a:o,b:l}:this.space==="lch"?{l:a,c:o,h:l}:this.space==="cmyk"?{c:a,m:o,y:l,k:h}:{};Object.assign(this,d)}lab(){let{x:e,y:t,z:r}=this.xyz(),n=116*t-16,s=500*(e-t),a=200*(t-r);return new i(n,s,a,"lab")}lch(){let{l:e,a:t,b:r}=this.lab(),n=Math.sqrt(t**2+r**2),s=180*Math.atan2(r,t)/Math.PI;return s<0&&(s*=-1,s=360-s),new i(e,n,s,"lch")}rgb(){if(this.space==="rgb")return this;if(rM(this.space)){let{x:e,y:t,z:r}=this;if(this.space==="lab"||this.space==="lch"){let{l:m,a:g,b:x}=this;if(this.space==="lch"){let{c:O,h:q}=this,P=Math.PI/180;g=O*Math.cos(P*q),x=O*Math.sin(P*q)}let y=(m+16)/116,b=g/500+y,S=y-x/200,A=16/116,C=.008856,I=7.787;e=.95047*(b**3>C?b**3:(b-A)/I),t=1*(y**3>C?y**3:(y-A)/I),r=1.08883*(S**3>C?S**3:(S-A)/I)}let n=e*3.2406+t*-1.5372+r*-.4986,s=e*-.9689+t*1.8758+r*.0415,a=e*.0557+t*-.204+r*1.057,o=Math.pow,l=.0031308,h=n>l?1.055*o(n,1/2.4)-.055:12.92*n,d=s>l?1.055*o(s,1/2.4)-.055:12.92*s,c=a>l?1.055*o(a,1/2.4)-.055:12.92*a;return new i(255*h,255*d,255*c)}else if(this.space==="hsl"){let{h:e,s:t,l:r}=this;if(e/=360,t/=100,r/=100,t===0)return r*=255,new i(r,r,r);let n=r<.5?r*(1+t):r+t-r*t,s=2*r-n,a=255*Vc(s,n,e+1/3),o=255*Vc(s,n,e),l=255*Vc(s,n,e-1/3);return new i(a,o,l)}else if(this.space==="cmyk"){let{c:e,m:t,y:r,k:n}=this,s=255*(1-Math.min(1,e*(1-n)+n)),a=255*(1-Math.min(1,t*(1-n)+n)),o=255*(1-Math.min(1,r*(1-n)+n));return new i(s,a,o)}else return this}toArray(){let{_a:e,_b:t,_c:r,_d:n,space:s}=this;return[e,t,r,n,s]}toHex(){let[e,t,r]=this._clamped().map(tM);return`#${e}${t}${r}`}toRgb(){let[e,t,r]=this._clamped();return`rgb(${e},${t},${r})`}toString(){return this.toHex()}xyz(){let{_a:e,_b:t,_c:r}=this.rgb(),[n,s,a]=[e,t,r].map(b=>b/255),o=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92,l=s>.04045?Math.pow((s+.055)/1.055,2.4):s/12.92,h=a>.04045?Math.pow((a+.055)/1.055,2.4):a/12.92,d=(o*.4124+l*.3576+h*.1805)/.95047,c=(o*.2126+l*.7152+h*.0722)/1,f=(o*.0193+l*.1192+h*.9505)/1.08883,m=d>.008856?Math.pow(d,1/3):7.787*d+16/116,g=c>.008856?Math.pow(c,1/3):7.787*c+16/116,x=f>.008856?Math.pow(f,1/3):7.787*f+16/116;return new i(m,g,x,"xyz")}_clamped(){let{_a:e,_b:t,_c:r}=this.rgb(),{max:n,min:s,round:a}=Math,o=l=>n(0,s(a(l),255));return[e,t,r].map(o)}},it=class i{constructor(...e){this.init(...e)}clone(){return new i(this)}init(e,t){let r={x:0,y:0},n=Array.isArray(e)?{x:e[0],y:e[1]}:typeof e=="object"?{x:e.x,y:e.y}:{x:e,y:t};return this.x=n.x==null?r.x:n.x,this.y=n.y==null?r.y:n.y,this}toArray(){return[this.x,this.y]}transform(e){return this.clone().transformO(e)}transformO(e){xe.isMatrixLike(e)||(e=new xe(e));let{x:t,y:r}=this;return this.x=e.a*t+e.c*r+e.e,this.y=e.b*t+e.d*r+e.f,this}};xe=class i{constructor(...e){this.init(...e)}static formatTransforms(e){let t=e.flip==="both"||e.flip===!0,r=e.flip&&(t||e.flip==="x")?-1:1,n=e.flip&&(t||e.flip==="y")?-1:1,s=e.skew&&e.skew.length?e.skew[0]:isFinite(e.skew)?e.skew:isFinite(e.skewX)?e.skewX:0,a=e.skew&&e.skew.length?e.skew[1]:isFinite(e.skew)?e.skew:isFinite(e.skewY)?e.skewY:0,o=e.scale&&e.scale.length?e.scale[0]*r:isFinite(e.scale)?e.scale*r:isFinite(e.scaleX)?e.scaleX*r:r,l=e.scale&&e.scale.length?e.scale[1]*n:isFinite(e.scale)?e.scale*n:isFinite(e.scaleY)?e.scaleY*n:n,h=e.shear||0,d=e.rotate||e.theta||0,c=new it(e.origin||e.around||e.ox||e.originX,e.oy||e.originY),f=c.x,m=c.y,g=new it(e.position||e.px||e.positionX||NaN,e.py||e.positionY||NaN),x=g.x,y=g.y,b=new it(e.translate||e.tx||e.translateX,e.ty||e.translateY),S=b.x,A=b.y,C=new it(e.relative||e.rx||e.relativeX,e.ry||e.relativeY),I=C.x,O=C.y;return{scaleX:o,scaleY:l,skewX:s,skewY:a,shear:h,theta:d,rx:I,ry:O,tx:S,ty:A,ox:f,oy:m,px:x,py:y}}static fromArray(e){return{a:e[0],b:e[1],c:e[2],d:e[3],e:e[4],f:e[5]}}static isMatrixLike(e){return e.a!=null||e.b!=null||e.c!=null||e.d!=null||e.e!=null||e.f!=null}static matrixMultiply(e,t,r){let n=e.a*t.a+e.c*t.b,s=e.b*t.a+e.d*t.b,a=e.a*t.c+e.c*t.d,o=e.b*t.c+e.d*t.d,l=e.e+e.a*t.e+e.c*t.f,h=e.f+e.b*t.e+e.d*t.f;return r.a=n,r.b=s,r.c=a,r.d=o,r.e=l,r.f=h,r}around(e,t,r){return this.clone().aroundO(e,t,r)}aroundO(e,t,r){let n=e||0,s=t||0;return this.translateO(-n,-s).lmultiplyO(r).translateO(n,s)}clone(){return new i(this)}decompose(e=0,t=0){let r=this.a,n=this.b,s=this.c,a=this.d,o=this.e,l=this.f,h=r*a-n*s,d=h>0?1:-1,c=d*Math.sqrt(r*r+n*n),f=Math.atan2(d*n,d*r),m=180/Math.PI*f,g=Math.cos(f),x=Math.sin(f),y=(r*s+n*a)/h,b=s*c/(y*r-n)||a*c/(y*n+r),S=o-e+e*g*c+t*(y*g*c-x*b),A=l-t+e*x*c+t*(y*x*c+g*b);return{scaleX:c,scaleY:b,shear:y,rotate:m,translateX:S,translateY:A,originX:e,originY:t,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}}equals(e){if(e===this)return!0;let t=new i(e);return Vs(this.a,t.a)&&Vs(this.b,t.b)&&Vs(this.c,t.c)&&Vs(this.d,t.d)&&Vs(this.e,t.e)&&Vs(this.f,t.f)}flip(e,t){return this.clone().flipO(e,t)}flipO(e,t){return e==="x"?this.scaleO(-1,1,t,0):e==="y"?this.scaleO(1,-1,0,t):this.scaleO(-1,-1,e,t||e)}init(e){let t=i.fromArray([1,0,0,1,0,0]);return e=e instanceof Ei?e.matrixify():typeof e=="string"?i.fromArray(e.split(Sr).map(parseFloat)):Array.isArray(e)?i.fromArray(e):typeof e=="object"&&i.isMatrixLike(e)?e:typeof e=="object"?new i().transform(e):arguments.length===6?i.fromArray([].slice.call(arguments)):t,this.a=e.a!=null?e.a:t.a,this.b=e.b!=null?e.b:t.b,this.c=e.c!=null?e.c:t.c,this.d=e.d!=null?e.d:t.d,this.e=e.e!=null?e.e:t.e,this.f=e.f!=null?e.f:t.f,this}inverse(){return this.clone().inverseO()}inverseO(){let e=this.a,t=this.b,r=this.c,n=this.d,s=this.e,a=this.f,o=e*n-t*r;if(!o)throw new Error("Cannot invert "+this);let l=n/o,h=-t/o,d=-r/o,c=e/o,f=-(l*s+d*a),m=-(h*s+c*a);return this.a=l,this.b=h,this.c=d,this.d=c,this.e=f,this.f=m,this}lmultiply(e){return this.clone().lmultiplyO(e)}lmultiplyO(e){let t=this,r=e instanceof i?e:new i(e);return i.matrixMultiply(r,t,this)}multiply(e){return this.clone().multiplyO(e)}multiplyO(e){let t=this,r=e instanceof i?e:new i(e);return i.matrixMultiply(t,r,this)}rotate(e,t,r){return this.clone().rotateO(e,t,r)}rotateO(e,t=0,r=0){e=jc(e);let n=Math.cos(e),s=Math.sin(e),{a,b:o,c:l,d:h,e:d,f:c}=this;return this.a=a*n-o*s,this.b=o*n+a*s,this.c=l*n-h*s,this.d=h*n+l*s,this.e=d*n-c*s+r*s-t*n+t,this.f=c*n+d*s-t*s-r*n+r,this}scale(e,t,r,n){return this.clone().scaleO(...arguments)}scaleO(e,t=e,r=0,n=0){arguments.length===3&&(n=r,r=t,t=e);let{a:s,b:a,c:o,d:l,e:h,f:d}=this;return this.a=s*e,this.b=a*t,this.c=o*e,this.d=l*t,this.e=h*e-r*e+r,this.f=d*t-n*t+n,this}shear(e,t,r){return this.clone().shearO(e,t,r)}shearO(e,t=0,r=0){let{a:n,b:s,c:a,d:o,e:l,f:h}=this;return this.a=n+s*e,this.c=a+o*e,this.e=l+h*e-r*e,this}skew(e,t,r,n){return this.clone().skewO(...arguments)}skewO(e,t=e,r=0,n=0){arguments.length===3&&(n=r,r=t,t=e),e=jc(e),t=jc(t);let s=Math.tan(e),a=Math.tan(t),{a:o,b:l,c:h,d,e:c,f}=this;return this.a=o+l*s,this.b=l+o*a,this.c=h+d*s,this.d=d+h*a,this.e=c+f*s-n*s,this.f=f+c*a-r*a,this}skewX(e,t,r){return this.skew(e,0,t,r)}skewY(e,t,r){return this.skew(0,e,t,r)}toArray(){return[this.a,this.b,this.c,this.d,this.e,this.f]}toString(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}transform(e){if(i.isMatrixLike(e))return new i(e).multiplyO(this);let t=i.formatTransforms(e),r=this,{x:n,y:s}=new it(t.ox,t.oy).transform(r),a=new i().translateO(t.rx,t.ry).lmultiplyO(r).translateO(-n,-s).scaleO(t.scaleX,t.scaleY).skewO(t.skewX,t.skewY).shearO(t.shear).rotateO(t.theta).translateO(n,s);if(isFinite(t.px)||isFinite(t.py)){let o=new it(n,s).transform(a),l=isFinite(t.px)?t.px-o.x:0,h=isFinite(t.py)?t.py-o.y:0;a.translateO(l,h)}return a.translateO(t.tx,t.ty),a}translate(e,t){return this.clone().translateO(e,t)}translateO(e,t){return this.e+=e||0,this.f+=t||0,this}valueOf(){return{a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}}};be(xe,"Matrix");Gt=class i{constructor(...e){this.init(...e)}addOffset(){return this.x+=Te.window.pageXOffset,this.y+=Te.window.pageYOffset,new i(this)}init(e){let t=[0,0,0,0];return e=typeof e=="string"?e.split(Sr).map(parseFloat):Array.isArray(e)?e:typeof e=="object"?[e.left!=null?e.left:e.x,e.top!=null?e.top:e.y,e.width,e.height]:arguments.length===4?[].slice.call(arguments):t,this.x=e[0]||0,this.y=e[1]||0,this.width=this.w=e[2]||0,this.height=this.h=e[3]||0,this.x2=this.x+this.w,this.y2=this.y+this.h,this.cx=this.x+this.w/2,this.cy=this.y+this.h/2,this}isNulled(){return G3(this)}merge(e){let t=Math.min(this.x,e.x),r=Math.min(this.y,e.y),n=Math.max(this.x+this.width,e.x+e.width)-t,s=Math.max(this.y+this.height,e.y+e.height)-r;return new i(t,r,n,s)}toArray(){return[this.x,this.y,this.width,this.height]}toString(){return this.x+" "+this.y+" "+this.width+" "+this.height}transform(e){e instanceof xe||(e=new xe(e));let t=1/0,r=-1/0,n=1/0,s=-1/0;return[new it(this.x,this.y),new it(this.x2,this.y),new it(this.x,this.y2),new it(this.x2,this.y2)].forEach(function(o){o=o.transform(e),t=Math.min(t,o.x),r=Math.max(r,o.x),n=Math.min(n,o.y),s=Math.max(s,o.y)}),new i(t,n,r-t,s-n)}};fe({viewbox:{viewbox(i,e,t,r){return i==null?new Gt(this.attr("viewBox")):this.attr("viewBox",new Gt(i,e,t,r))},zoom(i,e){let{width:t,height:r}=this.attr(["width","height"]);if((!t&&!r||typeof t=="string"||typeof r=="string")&&(t=this.node.clientWidth,r=this.node.clientHeight),!t||!r)throw new Error("Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element");let n=this.viewbox(),s=t/n.width,a=r/n.height,o=Math.min(s,a);if(i==null)return o;let l=o/i;l===1/0&&(l=Number.MAX_SAFE_INTEGER/100),e=e||new it(t/2/s+n.x,r/2/a+n.y);let h=new Gt(n).transform(new xe({scale:l,origin:e}));return this.viewbox(h)}}});be(Gt,"Box");Nr=class extends Array{constructor(e=[],...t){if(super(e,...t),typeof e=="number")return this;this.length=0,this.push(...e)}};ge([Nr],{each(i,...e){return typeof i=="function"?this.map((t,r,n)=>i.call(t,t,r,n)):this.map(t=>t[i](...e))},toArray(){return Array.prototype.concat.apply([],this)}});cM=["toArray","constructor","each"];Nr.extend=function(i){i=i.reduce((e,t)=>(cM.includes(t)||t[0]==="_"||(e[t]=function(...r){return this.each(t,...r)}),e),{}),ge([Nr],i)};mM=0,W3={};ts=class extends yo{addEventListener(){}dispatch(e,t,r){return gM(this,e,t,r)}dispatchEvent(e){let t=this.getEventHolder().events;if(!t)return!0;let r=t[e.type];for(let n in r)for(let s in r[n])r[n][s](e);return!e.defaultPrevented}fire(e,t,r){return this.dispatch(e,t,r),this}getEventHolder(){return this}getEventTarget(){return this}off(e,t,r){return Ws(this,e,t,r),this}on(e,t,r,n){return Kc(this,e,t,r,n),this}removeEventListener(){}};be(ts,"EventTarget");go={duration:400,ease:">",delay:0},xM={"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","text-anchor":"start"},hn=class extends Array{constructor(...e){super(...e),this.init(...e)}clone(){return new this.constructor(this)}init(e){return typeof e=="number"?this:(this.length=0,this.push(...this.parse(e)),this)}parse(e=[]){return e instanceof Array?e:e.trim().split(Sr).map(parseFloat)}toArray(){return Array.prototype.concat.apply([],this)}toSet(){return new Set(this)}toString(){return this.join(" ")}valueOf(){let e=[];return e.push(...this),e}},ye=class i{constructor(...e){this.init(...e)}convert(e){return new i(this.value,e)}divide(e){return e=new i(e),new i(this/e,this.unit||e.unit)}init(e,t){return t=Array.isArray(e)?e[1]:t,e=Array.isArray(e)?e[0]:e,this.value=0,this.unit=t||"",typeof e=="number"?this.value=isNaN(e)?0:isFinite(e)?e:e<0?-34e37:34e37:typeof e=="string"?(t=e.match(j3),t&&(this.value=parseFloat(t[1]),t[5]==="%"?this.value/=100:t[5]==="s"&&(this.value*=1e3),this.unit=t[5])):e instanceof i&&(this.value=e.valueOf(),this.unit=e.unit),this}minus(e){return e=new i(e),new i(this-e,this.unit||e.unit)}plus(e){return e=new i(e),new i(this+e,this.unit||e.unit)}times(e){return e=new i(e),new i(this*e,this.unit||e.unit)}toArray(){return[this.value,this.unit]}toJSON(){return this.toString()}toString(){return(this.unit==="%"?~~(this.value*1e8)/1e6:this.unit==="s"?this.value/1e3:this.value)+this.unit}valueOf(){return this.value}},X3=[];dn=class i extends ts{constructor(e,t){super(),this.node=e,this.type=e.nodeName,t&&e!==t&&this.attr(t)}add(e,t){return e=Rt(e),e.removeNamespace&&this.node instanceof Te.window.SVGElement&&e.removeNamespace(),t==null?this.node.appendChild(e.node):e.node!==this.node.childNodes[t]&&this.node.insertBefore(e.node,this.node.childNodes[t]),this}addTo(e,t){return Rt(e).put(this,t)}children(){return new Nr(ru(this.node.children,function(e){return Ti(e)}))}clear(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this}clone(e=!0,t=!0){this.writeDataToDom();let r=this.node.cloneNode(e);return t&&(r=$3(r)),new this.constructor(r)}each(e,t){let r=this.children(),n,s;for(n=0,s=r.length;n=0}html(e,t){return this.xml(e,t,vw)}id(e){return typeof e>"u"&&!this.node.id&&(this.node.id=U3(this.type)),this.attr("id",e)}index(e){return[].slice.call(this.node.childNodes).indexOf(e.node)}last(){return Ti(this.node.lastChild)}matches(e){let t=this.node,r=t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector||null;return r&&r.call(t,e)}parent(e){let t=this;if(!t.node.parentNode)return null;if(t=Ti(t.node.parentNode),!e)return t;do if(typeof e=="string"?t.matches(e):t instanceof e)return t;while(t=Ti(t.node.parentNode));return t}put(e,t){return e=Rt(e),this.add(e,t),e}putIn(e,t){return Rt(e).add(this,t)}remove(){return this.parent()&&this.parent().removeElement(this),this}removeElement(e){return this.node.removeChild(e.node),this}replace(e){return e=Rt(e),this.node.parentNode&&this.node.parentNode.replaceChild(e.node,this.node),e}round(e=2,t=null){let r=10**e,n=this.attr(t);for(let s in n)typeof n[s]=="number"&&(n[s]=Math.round(n[s]*r)/r);return this.attr(n),this}svg(e,t){return this.xml(e,t,nu)}toString(){return this.id()}words(e){return this.node.textContent=e,this}wrap(e){let t=this.parent();if(!t)return this.addTo(e);let r=t.index(this);return t.put(e,r).put(this)}writeDataToDom(){return this.each(function(){this.writeDataToDom()}),this}xml(e,t,r){if(typeof e=="boolean"&&(r=t,t=e,e=null),e==null||typeof e=="function"){t=t??!0,this.writeDataToDom();let o=this;if(e!=null){if(o=Ti(o.node.cloneNode(!0)),t){let l=e(o);if(o=l||o,l===!1)return""}o.each(function(){let l=e(this),h=l||this;l===!1?this.remove():l&&this!==h&&this.replace(h)},!0)}return t?o.node.outerHTML:o.node.innerHTML}t=t??!1;let n=vo("wrapper",r),s=Te.document.createDocumentFragment();n.innerHTML=e;for(let o=n.children.length;o--;)s.appendChild(n.firstElementChild);let a=this.parent();return t?this.replace(s)&&a:this.add(s)}};ge(dn,{attr:vM,find:uM,findOne:fM});be(dn,"Dom");Ei=class extends dn{constructor(e,t){super(e,t),this.dom={},this.node.instance=this,e.hasAttribute("svgjs:data")&&this.setData(JSON.parse(e.getAttribute("svgjs:data"))||{})}center(e,t){return this.cx(e).cy(t)}cx(e){return e==null?this.x()+this.width()/2:this.x(e-this.width()/2)}cy(e){return e==null?this.y()+this.height()/2:this.y(e-this.height()/2)}defs(){let e=this.root();return e&&e.defs()}dmove(e,t){return this.dx(e).dy(t)}dx(e=0){return this.x(new ye(e).plus(this.x()))}dy(e=0){return this.y(new ye(e).plus(this.y()))}getEventHolder(){return this}height(e){return this.attr("height",e)}move(e,t){return this.x(e).y(t)}parents(e=this.root()){let t=typeof e=="string";t||(e=Rt(e));let r=new Nr,n=this;for(;(n=n.parent())&&n.node!==Te.document&&n.nodeName!=="#document-fragment"&&(r.push(n),!(!t&&n.node===e.node||t&&n.matches(e)));)if(n.node===this.root().node)return null;return r}reference(e){if(e=this.attr(e),!e)return null;let t=(e+"").match(Bw);return t?Rt(t[1]):null}root(){let e=this.parent(ww(su));return e&&e.root()}setData(e){return this.dom=e,this}size(e,t){let r=ta(this,e,t);return this.width(new ye(r.width)).height(new ye(r.height))}width(e){return this.attr("width",e)}writeDataToDom(){return this.node.removeAttribute("svgjs:data"),Object.keys(this.dom).length&&this.node.setAttribute("svgjs:data",JSON.stringify(this.dom)),super.writeDataToDom()}x(e){return this.attr("x",e)}y(e){return this.attr("y",e)}};ge(Ei,{bbox:lM,rbox:hM,inside:dM,point:nM,ctm:sM,screenCTM:aM});be(Ei,"Element");po={stroke:["color","width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],fill:["color","opacity","rule"],prefix:function(i,e){return e==="color"?i:i+"-"+e}};["fill","stroke"].forEach(function(i){let e={},t;e[i]=function(r){if(typeof r>"u")return this.attr(i);if(typeof r=="string"||r instanceof Ni||Ni.isRgb(r)||r instanceof Ei)this.attr(i,r);else for(t=po[i].length-1;t>=0;t--)r[po[i][t]]!=null&&this.attr(po.prefix(i,po[i][t]),r[po[i][t]]);return this},fe(["Element","Runner"],e)});fe(["Element","Runner"],{matrix:function(i,e,t,r,n,s){return i==null?new xe(this):this.attr("transform",new xe(i,e,t,r,n,s))},rotate:function(i,e,t){return this.transform({rotate:i,ox:e,oy:t},!0)},skew:function(i,e,t,r){return arguments.length===1||arguments.length===3?this.transform({skew:i,ox:e,oy:t},!0):this.transform({skew:[i,e],ox:t,oy:r},!0)},shear:function(i,e,t){return this.transform({shear:i,ox:e,oy:t},!0)},scale:function(i,e,t,r){return arguments.length===1||arguments.length===3?this.transform({scale:i,ox:e,oy:t},!0):this.transform({scale:[i,e],ox:t,oy:r},!0)},translate:function(i,e){return this.transform({translate:[i,e]},!0)},relative:function(i,e){return this.transform({relative:[i,e]},!0)},flip:function(i="both",e="center"){return"xybothtrue".indexOf(i)===-1&&(e=i,i="both"),this.transform({flip:i,origin:e},!0)},opacity:function(i){return this.attr("opacity",i)}});fe("radius",{radius:function(i,e=i){return(this._element||this).type==="radialGradient"?this.attr("r",new ye(i)):this.rx(i).ry(e)}});fe("Path",{length:function(){return this.node.getTotalLength()},pointAt:function(i){return new it(this.node.getPointAtLength(i))}});fe(["Element","Runner"],{font:function(i,e){if(typeof i=="object"){for(e in i)this.font(e,i[e]);return this}return i==="leading"?this.leading(e):i==="anchor"?this.attr("text-anchor",e):i==="size"||i==="family"||i==="weight"||i==="stretch"||i==="variant"||i==="style"?this.attr("font-"+i,e):this.attr(i,e)}});bM=["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","mouseenter","mouseleave","touchstart","touchmove","touchleave","touchend","touchcancel"].reduce(function(i,e){let t=function(r){return r===null?this.off(e):this.on(e,r),this};return i[e]=t,i},{});fe("Element",bM);fe("Element",{untransform:wM,matrixify:MM,toParent:TM,toRoot:NM,transform:EM});Vt=class i extends Ei{flatten(e=this,t){return this.each(function(){if(this instanceof i)return this.flatten().ungroup()}),this}ungroup(e=this.parent(),t=e.index(this)){return t=t===-1?e.children().length:t,this.each(function(r,n){return n[n.length-r-1].toParent(e,t)}),this.remove()}};be(Vt,"Container");bo=class extends Vt{constructor(e,t=e){super(qe("defs",e),t)}flatten(){return this}ungroup(){return this}};be(bo,"Defs");Dt=class extends Ei{};be(Dt,"Shape");SM={__proto__:null,rx:lu,ry:hu,x:K3,y:Z3,cx:Q3,cy:J3,width:e2,height:t2},Xs=class extends Dt{constructor(e,t=e){super(qe("ellipse",e),t)}size(e,t){let r=ta(this,e,t);return this.rx(new ye(r.width).divide(2)).ry(new ye(r.height).divide(2))}};ge(Xs,SM);fe("Container",{ellipse:Fe(function(i=0,e=i){return this.put(new Xs).size(i,e).move(0,0)})});be(Xs,"Ellipse");L0=class extends dn{constructor(e=Te.document.createDocumentFragment()){super(e)}xml(e,t,r){if(typeof e=="boolean"&&(r=t,t=e,e=null),e==null||typeof e=="function"){let n=new dn(vo("wrapper",r));return n.add(this.node.cloneNode(!0)),n.xml(!1,r)}return super.xml(e,!1,r)}};be(L0,"Fragment");AM={__proto__:null,from:i2,to:r2},is=class extends Vt{constructor(e,t){super(qe(e+"Gradient",typeof e=="string"?null:e),t)}attr(e,t,r){return e==="transform"&&(e="gradientTransform"),super.attr(e,t,r)}bbox(){return new Gt}targets(){return ia("svg [fill*="+this.id()+"]")}toString(){return this.url()}update(e){return this.clear(),typeof e=="function"&&e.call(this,this),this}url(){return"url(#"+this.id()+")"}};ge(is,AM);fe({Container:{gradient(...i){return this.defs().gradient(...i)}},Defs:{gradient:Fe(function(i,e){return this.put(new is(i)).update(e)})}});be(is,"Gradient");rs=class extends Vt{constructor(e,t=e){super(qe("pattern",e),t)}attr(e,t,r){return e==="transform"&&(e="patternTransform"),super.attr(e,t,r)}bbox(){return new Gt}targets(){return ia("svg [fill*="+this.id()+"]")}toString(){return this.url()}update(e){return this.clear(),typeof e=="function"&&e.call(this,this),this}url(){return"url(#"+this.id()+")"}};fe({Container:{pattern(...i){return this.defs().pattern(...i)}},Defs:{pattern:Fe(function(i,e,t){return this.put(new rs).update(t).attr({x:0,y:0,width:i,height:e,patternUnits:"userSpaceOnUse"})})}});be(rs,"Pattern");ji=class extends Dt{constructor(e,t=e){super(qe("image",e),t)}load(e,t){if(!e)return this;let r=new Te.window.Image;return Kc(r,"load",function(n){let s=this.parent(rs);this.width()===0&&this.height()===0&&this.size(r.width,r.height),s instanceof rs&&s.width()===0&&s.height()===0&&s.size(this.width(),this.height()),typeof t=="function"&&t.call(this,n)},this),Kc(r,"load error",function(){Ws(r)}),this.attr("href",r.src=e,Lo)}};yM(function(i,e,t){return(i==="fill"||i==="stroke")&&qw.test(e)&&(e=t.root().defs().image(e)),e instanceof ji&&(e=t.root().defs().pattern(0,0,r=>{r.add(e)})),e});fe({Container:{image:Fe(function(i,e){return this.put(new ji).size(0,0).load(i,e)})}});be(ji,"Image");sr=class extends hn{bbox(){let e=-1/0,t=-1/0,r=1/0,n=1/0;return this.forEach(function(s){e=Math.max(s[0],e),t=Math.max(s[1],t),r=Math.min(s[0],r),n=Math.min(s[1],n)}),new Gt(r,n,e-r,t-n)}move(e,t){let r=this.bbox();if(e-=r.x,t-=r.y,!isNaN(e)&&!isNaN(t))for(let n=this.length-1;n>=0;n--)this[n]=[this[n][0]+e,this[n][1]+t];return this}parse(e=[0,0]){let t=[];e instanceof Array?e=Array.prototype.concat.apply([],e):e=e.trim().split(Sr).map(parseFloat),e.length%2!==0&&e.pop();for(let r=0,n=e.length;r=0;r--)n.width&&(this[r][0]=(this[r][0]-n.x)*e/n.width+n.x),n.height&&(this[r][1]=(this[r][1]-n.y)*t/n.height+n.y);return this}toLine(){return{x1:this[0][0],y1:this[0][1],x2:this[1][0],y2:this[1][1]}}toString(){let e=[];for(let t=0,r=this.length;t":function(i){return-Math.cos(i*Math.PI)/2+.5},">":function(i){return Math.sin(i*Math.PI/2)},"<":function(i){return-Math.cos(i*Math.PI/2)+1},bezier:function(i,e,t,r){return function(n){return n<0?i>0?e/i*n:t>0?r/t*n:0:n>1?t<1?(1-r)/(1-t)*n+(r-t)/(1-t):i<1?(1-e)/(1-i)*n+(e-i)/(1-i):1:3*n*(1-n)**2*e+3*n**2*(1-n)*r+n**3}},steps:function(i,e="end"){e=e.split("-").reverse()[0];let t=i;return e==="none"?--t:e==="both"&&++t,(r,n=!1)=>{let s=Math.floor(r*i),a=r*s%1===0;return(e==="start"||e==="both")&&++s,n&&a&&--s,r>=0&&s<0&&(s=0),r<=1&&s>t&&(s=t),s/t}}},wo=class{done(){return!1}},Mo=class extends wo{constructor(e=go.ease){super(),this.ease=zM[e]||e}step(e,t,r){return typeof e!="number"?r<1?e:t:e+(t-e)*this.ease(r)}},Zs=class extends wo{constructor(e){super(),this.stepper=e}done(e){return e.done}step(e,t,r,n){return this.stepper(e,t,r,n)}};Zc=class extends Zs{constructor(e=500,t=0){super(),this.duration(e).overshoot(t)}step(e,t,r,n){if(typeof e=="string")return e;if(n.done=r===1/0,r===1/0)return t;if(r===0)return e;r>100&&(r=16),r/=1e3;let s=n.velocity||0,a=-this.d*s-this.k*(e-t),o=e+s*r+a*r*r/2;return n.velocity=s+a*r,n.done=Math.abs(t-o)+Math.abs(s)<.002,n.done?t:o}};ge(Zc,{duration:Ys("_duration",P3),overshoot:Ys("_overshoot",P3)});Qc=class extends Zs{constructor(e=.1,t=.01,r=0,n=1e3){super(),this.p(e).i(t).d(r).windup(n)}step(e,t,r,n){if(typeof e=="string")return e;if(n.done=r===1/0,r===1/0)return t;if(r===0)return e;let s=t-e,a=(n.integral||0)+s*r,o=(s-(n.error||0))/r,l=this._windup;return l!==!1&&(a=Math.max(-l,Math.min(a,l))),n.error=s,n.integral=a,n.done=Math.abs(s)<.001,n.done?t:e+(this.P*s+this.I*a+this.D*o)}};ge(Qc,{windup:Ys("_windup"),p:Ys("P"),i:Ys("I"),d:Ys("D")});RM={M:2,L:2,H:1,V:1,C:6,S:4,Q:4,T:2,A:7,Z:0},Jc={M:function(i,e,t){return e.x=t.x=i[0],e.y=t.y=i[1],["M",e.x,e.y]},L:function(i,e){return e.x=i[0],e.y=i[1],["L",i[0],i[1]]},H:function(i,e){return e.x=i[0],["H",i[0]]},V:function(i,e){return e.y=i[0],["V",i[0]]},C:function(i,e){return e.x=i[4],e.y=i[5],["C",i[0],i[1],i[2],i[3],i[4],i[5]]},S:function(i,e){return e.x=i[2],e.y=i[3],["S",i[0],i[1],i[2],i[3]]},Q:function(i,e){return e.x=i[2],e.y=i[3],["Q",i[0],i[1],i[2],i[3]]},T:function(i,e){return e.x=i[0],e.y=i[1],["T",i[0],i[1]]},Z:function(i,e,t){return e.x=t.x,e.y=t.y,["Z"]},A:function(i,e){return e.x=i[5],e.y=i[6],["A",i[0],i[1],i[2],i[3],i[4],i[5],i[6]]}},Wc="mlhvqtcsaz".split("");for(let i=0,e=Wc.length;i=0;s--)n=this[s][0],n==="M"||n==="L"||n==="T"?(this[s][1]+=e,this[s][2]+=t):n==="H"?this[s][1]+=e:n==="V"?this[s][1]+=t:n==="C"||n==="S"||n==="Q"?(this[s][1]+=e,this[s][2]+=t,this[s][3]+=e,this[s][4]+=t,n==="C"&&(this[s][5]+=e,this[s][6]+=t)):n==="A"&&(this[s][6]+=e,this[s][7]+=t);return this}parse(e="M0 0"){return Array.isArray(e)&&(e=Array.prototype.concat.apply([],e).toString()),FM(e)}size(e,t){let r=this.bbox(),n,s;for(r.width=r.width===0?1:r.width,r.height=r.height===0?1:r.height,n=this.length-1;n>=0;n--)s=this[n][0],s==="M"||s==="L"||s==="T"?(this[n][1]=(this[n][1]-r.x)*e/r.width+r.x,this[n][2]=(this[n][2]-r.y)*t/r.height+r.y):s==="H"?this[n][1]=(this[n][1]-r.x)*e/r.width+r.x:s==="V"?this[n][1]=(this[n][1]-r.y)*t/r.height+r.y:s==="C"||s==="S"||s==="Q"?(this[n][1]=(this[n][1]-r.x)*e/r.width+r.x,this[n][2]=(this[n][2]-r.y)*t/r.height+r.y,this[n][3]=(this[n][3]-r.x)*e/r.width+r.x,this[n][4]=(this[n][4]-r.y)*t/r.height+r.y,s==="C"&&(this[n][5]=(this[n][5]-r.x)*e/r.width+r.x,this[n][6]=(this[n][6]-r.y)*t/r.height+r.y)):s==="A"&&(this[n][1]=this[n][1]*e/r.width,this[n][2]=this[n][2]*t/r.height,this[n][6]=(this[n][6]-r.x)*e/r.width+r.x,this[n][7]=(this[n][7]-r.y)*t/r.height+r.y);return this}toString(){return qM(this)}},n2=i=>{let e=typeof i;return e==="number"?ye:e==="string"?Ni.isColor(i)?Ni:Sr.test(i)?au.test(i)?Er:hn:j3.test(i)?ye:To:cu.indexOf(i.constructor)>-1?i.constructor:Array.isArray(i)?hn:e==="object"?ss:To},Tr=class{constructor(e){this._stepper=e||new Mo("-"),this._from=null,this._to=null,this._type=null,this._context=null,this._morphObj=null}at(e){return this._morphObj.morph(this._from,this._to,e,this._stepper,this._context)}done(){return this._context.map(this._stepper.done).reduce(function(t,r){return t&&r},!0)}from(e){return e==null?this._from:(this._from=this._set(e),this)}stepper(e){return e==null?this._stepper:(this._stepper=e,this)}to(e){return e==null?this._to:(this._to=this._set(e),this)}type(e){return e==null?this._type:(this._type=e,this)}_set(e){this._type||this.type(n2(e));let t=new this._type(e);return this._type===Ni&&(t=this._to?t[this._to[4]]():this._from?t[this._from[4]]():t),this._type===ss&&(t=this._to?t.align(this._to):this._from?t.align(this._from):t),t=t.toConsumable(),this._morphObj=this._morphObj||new this._type,this._context=this._context||Array.apply(null,Array(t.length)).map(Object).map(function(r){return r.done=!0,r}),t}},To=class{constructor(...e){this.init(...e)}init(e){return e=Array.isArray(e)?e[0]:e,this.value=e,this}toArray(){return[this.value]}valueOf(){return this.value}},No=class i{constructor(...e){this.init(...e)}init(e){return Array.isArray(e)&&(e={scaleX:e[0],scaleY:e[1],shear:e[2],rotate:e[3],translateX:e[4],translateY:e[5],originX:e[6],originY:e[7]}),Object.assign(this,i.defaults,e),this}toArray(){let e=this;return[e.scaleX,e.scaleY,e.shear,e.rotate,e.translateX,e.translateY,e.originX,e.originY]}};No.defaults={scaleX:1,scaleY:1,shear:0,rotate:0,translateX:0,translateY:0,originX:0,originY:0};HM=(i,e)=>i[0]e[0]?1:0,ss=class{constructor(...e){this.init(...e)}align(e){let t=this.values;for(let r=0,n=t.length;rr.concat(n),[]),this}toArray(){return this.values}valueOf(){let e={},t=this.values;for(;t.length;){let r=t.shift(),n=t.shift(),s=t.shift(),a=t.splice(0,s);e[r]=new n(a)}return e}},cu=[To,No,ss];ar=class extends Dt{constructor(e,t=e){super(qe("path",e),t)}array(){return this._array||(this._array=new Er(this.attr("d")))}clear(){return delete this._array,this}height(e){return e==null?this.bbox().height:this.size(this.bbox().width,e)}move(e,t){return this.attr("d",this.array().move(e,t))}plot(e){return e==null?this.array():this.clear().attr("d",typeof e=="string"?e:this._array=new Er(e))}size(e,t){let r=ta(this,e,t);return this.attr("d",this.array().size(r.width,r.height))}width(e){return e==null?this.bbox().width:this.size(e,this.bbox().height)}x(e){return e==null?this.bbox().x:this.move(e,this.bbox().y)}y(e){return e==null?this.bbox().y:this.move(this.bbox().x,e)}};ar.prototype.MorphArray=Er;fe({Container:{path:Fe(function(i){return this.put(new ar).plot(i||new Er)})}});be(ar,"Path");s2={__proto__:null,array:jM,clear:GM,move:VM,plot:WM,size:YM},Gi=class extends Dt{constructor(e,t=e){super(qe("polygon",e),t)}};fe({Container:{polygon:Fe(function(i){return this.put(new Gi).plot(i||new sr)})}});ge(Gi,du);ge(Gi,s2);be(Gi,"Polygon");as=class extends Dt{constructor(e,t=e){super(qe("polyline",e),t)}};fe({Container:{polyline:Fe(function(i){return this.put(new as).plot(i||new sr)})}});ge(as,du);ge(as,s2);be(as,"Polyline");Ve=class extends Dt{constructor(e,t=e){super(qe("rect",e),t)}};ge(Ve,{rx:lu,ry:hu});fe({Container:{rect:Fe(function(i,e){return this.put(new Ve).size(i,e)})}});be(Ve,"Rect");xo=class{constructor(){this._first=null,this._last=null}first(){return this._first&&this._first.value}last(){return this._last&&this._last.value}push(e){let t=typeof e.next<"u"?e:{value:e,next:null,prev:null};return this._last?(t.prev=this._last,this._last.next=t,this._last=t):(this._last=t,this._first=t),t}remove(e){e.prev&&(e.prev.next=e.next),e.next&&(e.next.prev=e.prev),e===this._last&&(this._last=e.prev),e===this._first&&(this._first=e.next),e.prev=null,e.next=null}shift(){let e=this._first;return e?(this._first=e.next,this._first&&(this._first.prev=null),this._last=this._first?this._last:null,e.value):null}},Se={nextDraw:null,frames:new xo,timeouts:new xo,immediates:new xo,timer:()=>Te.window.performance||Te.window.Date,transforms:[],frame(i){let e=Se.frames.push({run:i});return Se.nextDraw===null&&(Se.nextDraw=Te.window.requestAnimationFrame(Se._draw)),e},timeout(i,e){e=e||0;let t=Se.timer().now()+e,r=Se.timeouts.push({run:i,time:t});return Se.nextDraw===null&&(Se.nextDraw=Te.window.requestAnimationFrame(Se._draw)),r},immediate(i){let e=Se.immediates.push(i);return Se.nextDraw===null&&(Se.nextDraw=Te.window.requestAnimationFrame(Se._draw)),e},cancelFrame(i){i!=null&&Se.frames.remove(i)},clearTimeout(i){i!=null&&Se.timeouts.remove(i)},cancelImmediate(i){i!=null&&Se.immediates.remove(i)},_draw(i){let e=null,t=Se.timeouts.last();for(;(e=Se.timeouts.shift())&&(i>=e.time?e.run():Se.timeouts.push(e),e!==t););let r=null,n=Se.frames.last();for(;r!==n&&(r=Se.frames.shift());)r.run(i);let s=null;for(;s=Se.immediates.shift();)s();Se.nextDraw=Se.timeouts.first()||Se.frames.first()?Te.window.requestAnimationFrame(Se._draw):null}},XM=function(i){let e=i.start,t=i.runner.duration(),r=e+t;return{start:e,duration:t,end:r,runner:i.runner}},KM=function(){let i=Te.window;return(i.performance||i.Date).now()},I0=class extends ts{constructor(e=KM){super(),this._timeSource=e,this._startTime=0,this._speed=1,this._persist=0,this._nextFrame=null,this._paused=!0,this._runners=[],this._runnerIds=[],this._lastRunnerId=-1,this._time=0,this._lastSourceTime=0,this._lastStepTime=0,this._step=this._stepFn.bind(this,!1),this._stepImmediate=this._stepFn.bind(this,!0)}active(){return!!this._nextFrame}finish(){return this.time(this.getEndTimeOfTimeline()+1),this.pause()}getEndTime(){let e=this.getLastRunnerInfo(),t=e?e.runner.duration():0;return(e?e.start:this._time)+t}getEndTimeOfTimeline(){let e=this._runners.map(t=>t.start+t.runner.duration());return Math.max(0,...e)}getLastRunnerInfo(){return this.getRunnerInfoById(this._lastRunnerId)}getRunnerInfoById(e){return this._runners[this._runnerIds.indexOf(e)]||null}pause(){return this._paused=!0,this._continue()}persist(e){return e==null?this._persist:(this._persist=e,this)}play(){return this._paused=!1,this.updateTime()._continue()}reverse(e){let t=this.speed();if(e==null)return this.speed(-t);let r=Math.abs(t);return this.speed(e?-r:r)}schedule(e,t,r){if(e==null)return this._runners.map(XM);let n=0,s=this.getEndTime();if(t=t||0,r==null||r==="last"||r==="after")n=s;else if(r==="absolute"||r==="start")n=t,t=0;else if(r==="now")n=this._time;else if(r==="relative"){let l=this.getRunnerInfoById(e.id);l&&(n=l.start+t,t=0)}else if(r==="with-last"){let l=this.getLastRunnerInfo();n=l?l.start:this._time}else throw new Error('Invalid value for the "when" parameter');e.unschedule(),e.timeline(this);let a=e.persist(),o={persist:a===null?this._persist:a,start:n+t,runner:e};return this._lastRunnerId=e.id,this._runners.push(o),this._runners.sort((l,h)=>l.start-h.start),this._runnerIds=this._runners.map(l=>l.runner.id),this.updateTime()._continue(),this}seek(e){return this.time(this._time+e)}source(e){return e==null?this._timeSource:(this._timeSource=e,this)}speed(e){return e==null?this._speed:(this._speed=e,this)}stop(){return this.time(0),this.pause()}time(e){return e==null?this._time:(this._time=e,this._continue(!0))}unschedule(e){let t=this._runnerIds.indexOf(e.id);return t<0?this:(this._runners.splice(t,1),this._runnerIds.splice(t,1),e.timeline(null),this)}updateTime(){return this.active()||(this._lastSourceTime=this._timeSource()),this}_continue(e=!1){return Se.cancelFrame(this._nextFrame),this._nextFrame=null,e?this._stepImmediate():this._paused?this:(this._nextFrame=Se.frame(this._step),this)}_stepFn(e=!1){let t=this._timeSource(),r=t-this._lastSourceTime;e&&(r=0);let n=this._speed*r+(this._time-this._lastStepTime);this._lastSourceTime=t,e||(this._time+=n,this._time=this._time<0?0:this._time),this._lastStepTime=this._time,this.fire("time",this._time);for(let a=this._runners.length;a--;){let o=this._runners[a],l=o.runner;this._time-o.start<=0&&l.reset()}let s=!1;for(let a=0,o=this._runners.length;a0?this._continue():(this.pause(),this.fire("finished")),this}};fe({Element:{timeline:function(i){return i==null?(this._timeline=this._timeline||new I0,this._timeline):(this._timeline=i,this)}}});Vi=class i extends ts{constructor(e){super(),this.id=i.id++,e=e??go.duration,e=typeof e=="function"?new Zs(e):e,this._element=null,this._timeline=null,this.done=!1,this._queue=[],this._duration=typeof e=="number"&&e,this._isDeclarative=e instanceof Zs,this._stepper=this._isDeclarative?e:new Mo,this._history={},this.enabled=!0,this._time=0,this._lastTime=0,this._reseted=!0,this.transforms=new xe,this.transformId=1,this._haveReversed=!1,this._reverse=!1,this._loopsDone=0,this._swing=!1,this._wait=0,this._times=1,this._frameId=null,this._persist=this._isDeclarative?!0:null}static sanitise(e,t,r){let n=1,s=!1,a=0;return e=e||go.duration,t=t||go.delay,r=r||"last",typeof e=="object"&&!(e instanceof wo)&&(t=e.delay||t,r=e.when||r,s=e.swing||s,n=e.times||n,a=e.wait||a,e=e.duration||go.duration),{duration:e,delay:t,swing:s,times:n,wait:a,when:r}}active(e){return e==null?this.enabled:(this.enabled=e,this)}addTransform(e,t){return this.transforms.lmultiplyO(e),this}after(e){return this.on("finished",e)}animate(e,t,r){let n=i.sanitise(e,t,r),s=new i(n.duration);return this._timeline&&s.timeline(this._timeline),this._element&&s.element(this._element),s.loop(n).schedule(n.delay,n.when)}clearTransform(){return this.transforms=new xe,this}clearTransformsFromQueue(){(!this.done||!this._timeline||!this._timeline._runnerIds.includes(this.id))&&(this._queue=this._queue.filter(e=>!e.isTransform))}delay(e){return this.animate(0,e)}duration(){return this._times*(this._wait+this._duration)-this._wait}during(e){return this.queue(null,e)}ease(e){return this._stepper=new Mo(e),this}element(e){return e==null?this._element:(this._element=e,e._prepareRunner(),this)}finish(){return this.step(1/0)}loop(e,t,r){return typeof e=="object"&&(t=e.swing,r=e.wait,e=e.times),this._times=e||1/0,this._swing=t||!1,this._wait=r||0,this._times===!0&&(this._times=1/0),this}loops(e){let t=this._duration+this._wait;if(e==null){let a=Math.floor(this._time/t),l=(this._time-a*t)/this._duration;return Math.min(a+l,this._times)}let r=Math.floor(e),n=e%1,s=t*r+this._duration*n;return this.time(s)}persist(e){return e==null?this._persist:(this._persist=e,this)}position(e){let t=this._time,r=this._duration,n=this._wait,s=this._times,a=this._swing,o=this._reverse,l;if(e==null){let f=function(g){let x=a*Math.floor(g%(2*(n+r))/(n+r)),y=x&&!o||!x&&o,b=Math.pow(-1,y)*(g%(n+r))/r+y;return Math.max(Math.min(b,1),0)},m=s*(n+r)-n;return l=t<=0?Math.round(f(1e-5)):t=0;this._lastPosition=t;let n=this.duration(),s=this._lastTime<=0&&this._time>0,a=this._lastTime=n;this._lastTime=this._time,s&&this.fire("start",this);let o=this._isDeclarative;this.done=!o&&!a&&this._time>=n,this._reseted=!1;let l=!1;return(r||o)&&(this._initialise(r),this.transforms=new xe,l=this._run(o?e:t),this.fire("step",this)),this.done=this.done||l&&o,a&&this.fire("finished",this),this}time(e){if(e==null)return this._time;let t=e-this._time;return this.step(t),this}timeline(e){return typeof e>"u"?this._timeline:(this._timeline=e,this)}unschedule(){let e=this.timeline();return e&&e.unschedule(this),this}_initialise(e){if(!(!e&&!this._isDeclarative))for(let t=0,r=this._queue.length;ti.lmultiplyO(e),o2=i=>i.transforms;iu=class{constructor(){this.runners=[],this.ids=[]}add(e){if(this.runners.includes(e))return;let t=e.id+1;return this.runners.push(e),this.ids.push(t),this}clearBefore(e){let t=this.ids.indexOf(e+1)||1;return this.ids.splice(0,t,0),this.runners.splice(0,t,new Qs).forEach(r=>r.clearTransformsFromQueue()),this}edit(e,t){let r=this.ids.indexOf(e+1);return this.ids.splice(r,1,e+1),this.runners.splice(r,1,t),this}getByID(e){return this.runners[this.ids.indexOf(e+1)]}length(){return this.ids.length}merge(){let e=null;for(let t=0;te.id<=i.id).map(o2).reduce(a2,new xe)},_addRunner(i){this._transformationRunners.add(i),Se.cancelImmediate(this._frameId),this._frameId=Se.immediate(ZM.bind(this))},_prepareRunner(){this._frameId==null&&(this._transformationRunners=new iu().add(new Qs(new xe(this))))}}});QM=(i,e)=>i.filter(t=>!e.includes(t));ge(Vi,{attr(i,e){return this.styleAttr("attr",i,e)},css(i,e){return this.styleAttr("css",i,e)},styleAttr(i,e,t){if(typeof e=="string")return this.styleAttr(i,{[e]:t});let r=e;if(this._tryRetarget(i,r))return this;let n=new Tr(this._stepper).to(r),s=Object.keys(r);return this.queue(function(){n=n.from(this.element()[i](s))},function(a){return this.element()[i](n.at(a).valueOf()),n.done()},function(a){let o=Object.keys(a),l=QM(o,s);if(l.length){let d=this.element()[i](l),c=new ss(n.from()).valueOf();Object.assign(c,d),n.from(c)}let h=new ss(n.to()).valueOf();Object.assign(h,a),n.to(h),s=o,r=a}),this._rememberMorpher(i,n),this},zoom(i,e){if(this._tryRetarget("zoom",i,e))return this;let t=new Tr(this._stepper).to(new ye(i));return this.queue(function(){t=t.from(this.element().zoom())},function(r){return this.element().zoom(t.at(r),e),t.done()},function(r,n){e=n,t.to(r)}),this._rememberMorpher("zoom",t),this},transform(i,e,t){if(e=i.relative||e,this._isDeclarative&&!e&&this._tryRetarget("transform",i))return this;let r=xe.isMatrixLike(i);t=i.affine!=null?i.affine:t??!r;let n=new Tr(this._stepper).type(t?No:xe),s,a,o,l,h;function d(){a=a||this.element(),s=s||Xc(i,a),h=new xe(e?void 0:a),a._addRunner(this),e||a._clearTransformRunnersBefore(this)}function c(m){e||this.clearTransform();let{x:g,y:x}=new it(s).transform(a._currentTransform(this)),y=new xe({...i,origin:[g,x]}),b=this._isDeclarative&&o?o:h;if(t){y=y.decompose(g,x),b=b.decompose(g,x);let A=y.rotate,C=b.rotate,I=[A-360,A,A+360],O=I.map(W=>Math.abs(W-C)),q=Math.min(...O),P=O.indexOf(q);y.rotate=I[P]}e&&(r||(y.rotate=i.rotate||0),this._isDeclarative&&l&&(b.rotate=l)),n.from(b),n.to(y);let S=n.at(m);return l=S.rotate,o=new xe(S),this.addTransform(o),a._addRunner(this),n.done()}function f(m){(m.origin||"center").toString()!==(i.origin||"center").toString()&&(s=Xc(m,a)),i={...m,origin:s}}return this.queue(d,c,f,!0),this._isDeclarative&&this._rememberMorpher("transform",n),this},x(i,e){return this._queueNumber("x",i)},y(i){return this._queueNumber("y",i)},dx(i=0){return this._queueNumberDelta("x",i)},dy(i=0){return this._queueNumberDelta("y",i)},dmove(i,e){return this.dx(i).dy(e)},_queueNumberDelta(i,e){if(e=new ye(e),this._tryRetarget(i,e))return this;let t=new Tr(this._stepper).to(e),r=null;return this.queue(function(){r=this.element()[i](),t.from(r),t.to(r+e)},function(n){return this.element()[i](t.at(n)),t.done()},function(n){t.to(r+new ye(n))}),this._rememberMorpher(i,t),this},_queueObject(i,e){if(this._tryRetarget(i,e))return this;let t=new Tr(this._stepper).to(e);return this.queue(function(){t.from(this.element()[i]())},function(r){return this.element()[i](t.at(r)),t.done()}),this._rememberMorpher(i,t),this},_queueNumber(i,e){return this._queueObject(i,new ye(e))},cx(i){return this._queueNumber("cx",i)},cy(i){return this._queueNumber("cy",i)},move(i,e){return this.x(i).y(e)},center(i,e){return this.cx(i).cy(e)},size(i,e){let t;return(!i||!e)&&(t=this._element.bbox()),i||(i=t.width/t.height*e),e||(e=t.height/t.width*i),this.width(i).height(e)},width(i){return this._queueNumber("width",i)},height(i){return this._queueNumber("height",i)},plot(i,e,t,r){if(arguments.length===4)return this.plot([i,e,t,r]);if(this._tryRetarget("plot",i))return this;let n=new Tr(this._stepper).type(this._element.MorphArray).to(i);return this.queue(function(){n.from(this._element.array())},function(s){return this._element.plot(n.at(s)),n.done()}),this._rememberMorpher("plot",n),this},leading(i){return this._queueNumber("leading",i)},viewbox(i,e,t,r){return this._queueObject("viewbox",new Gt(i,e,t,r))},update(i){return typeof i!="object"?this.update({offset:arguments[0],color:arguments[1],opacity:arguments[2]}):(i.opacity!=null&&this.attr("stop-opacity",i.opacity),i.color!=null&&this.attr("stop-color",i.color),i.offset!=null&&this.attr("offset",i.offset),this)}});ge(Vi,{rx:lu,ry:hu,from:i2,to:r2});be(Vi,"Runner");Eo=class extends Vt{constructor(e,t=e){super(qe("svg",e),t),this.namespace()}defs(){return this.isRoot()?Ti(this.node.querySelector("defs"))||this.put(new bo):this.root().defs()}isRoot(){return!this.node.parentNode||!(this.node.parentNode instanceof Te.window.SVGElement)&&this.node.parentNode.nodeName!=="#document-fragment"}namespace(){return this.isRoot()?this.attr({xmlns:nu,version:"1.1"}).attr("xmlns:xlink",Lo,_0).attr("xmlns:svgjs",bw,_0):this.root().namespace()}removeNamespace(){return this.attr({xmlns:null,version:null}).attr("xmlns:xlink",null,_0).attr("xmlns:svgjs",null,_0)}root(){return this.isRoot()?this:super.root()}};fe({Container:{nested:Fe(function(){return this.put(new Eo)})}});be(Eo,"Svg",!0);So=class extends Vt{constructor(e,t=e){super(qe("symbol",e),t)}};fe({Container:{symbol:Fe(function(){return this.put(new So)})}});be(So,"Symbol");l2={__proto__:null,plain:JM,length:eT,x:tT,y:iT,move:rT,cx:nT,cy:sT,center:aT,ax:oT,ay:lT,amove:hT,build:dT},Ce=class extends Dt{constructor(e,t=e){super(qe("text",e),t),this.dom.leading=new ye(1.3),this._rebuild=!0,this._build=!1}leading(e){return e==null?this.dom.leading:(this.dom.leading=new ye(e),this.rebuild())}rebuild(e){if(typeof e=="boolean"&&(this._rebuild=e),this._rebuild){let t=this,r=0,n=this.dom.leading;this.each(function(s){let a=Te.window.getComputedStyle(this.node).getPropertyValue("font-size"),o=n*new ye(a);this.dom.newLined&&(this.attr("x",t.attr("x")),this.text()===` -`?r+=o:(this.attr("dy",s?o+r:0),r=0))}),this.fire("rebuild")}return this}setData(e){return this.dom=e,this.dom.leading=new ye(e.leading||1.3),this}text(e){if(e===void 0){let t=this.node.childNodes,r=0;e="";for(let n=0,s=t.length;n{bo.prototype.init_genrand=function(i){for(this.mt[0]=i>>>0,this.mti=1;this.mti>>30,this.mt[this.mti]=(((i&4294901760)>>>16)*1812433253<<16)+(i&65535)*1812433253+this.mti,this.mt[this.mti]>>>=0};bo.prototype.genrand_int32=function(){var i,e=new Array(0,this.MATRIX_A);if(this.mti>=this.N){var t;for(this.mti==this.N+1&&this.init_genrand(5489),t=0;t>>1^e[i&1];for(;t>>1^e[i&1];i=this.mt[this.N-1]&this.UPPER_MASK|this.mt[0]&this.LOWER_MASK,this.mt[this.N-1]=this.mt[this.M-1]^i>>>1^e[i&1],this.mti=0}return i=this.mt[this.mti++],i^=i>>>11,i^=i<<7&2636928640,i^=i<<15&4022730752,i^=i>>>18,i>>>0}});function fe(i,e){if(Array.isArray(i)){for(let t of i)fe(t,e);return}if(typeof i=="object"){for(let t in i)fe(t,i[t]);return}e2(Object.getOwnPropertyNames(e)),su[i]=Object.assign(su[i]||{},e)}function Xt(i){return su[i]||{}}function qw(){return[...new Set(J3)]}function e2(i){J3.push(...i)}function mu(i,e){let t,r=i.length,n=[];for(t=0;t=0;e--)r2(i.children[e]);return i.id&&(i.id=i2(i.nodeName)),i}function xe(i,e){let t,r;for(i=Array.isArray(i)?i:[i],r=i.length-1;r>=0;r--)for(t in e)i[r].prototype[t]=e[t]}function He(i){return function(...e){let t=e[e.length-1];return t&&t.constructor===Object&&!(t instanceof Array)?i.apply(this,e.slice(0,-1)).attr(t):i.apply(this,e)}}function Vw(){return this.parent().children()}function Yw(){return this.parent().index(this)}function Xw(){return this.siblings()[this.position()+1]}function Kw(){return this.siblings()[this.position()-1]}function Zw(){let i=this.position();return this.parent().add(this.remove(),i+1),this}function Qw(){let i=this.position();return this.parent().add(this.remove(),i?i-1:0),this}function Jw(){return this.parent().add(this.remove()),this}function eM(){return this.parent().add(this.remove(),0),this}function tM(i){i=Rt(i),i.remove();let e=this.position();return this.parent().add(i,e),this}function iM(i){i=Rt(i),i.remove();let e=this.position();return this.parent().add(i,e+1),this}function rM(i){return i=Rt(i),i.before(this),this}function nM(i){return i=Rt(i),i.after(this),this}function cM(){let i=this.attr("class");return i==null?[]:i.trim().split(Sr)}function uM(i){return this.classes().indexOf(i)!==-1}function fM(i){if(!this.hasClass(i)){let e=this.classes();e.push(i),this.attr("class",e.join(" "))}return this}function mM(i){return this.hasClass(i)&&this.attr("class",this.classes().filter(function(e){return e!==i}).join(" ")),this}function pM(i){return this.hasClass(i)?this.removeClass(i):this.addClass(i)}function gM(i,e){let t={};if(arguments.length===0)return this.node.style.cssText.split(/\s*;\s*/).filter(function(r){return!!r.length}).forEach(function(r){let n=r.split(/\s*:\s*/);t[n[0]]=n[1]}),t;if(arguments.length<2){if(Array.isArray(i)){for(let r of i){let n=B0(r);t[r]=this.node.style[n]}return t}if(typeof i=="string")return this.node.style[B0(i)];if(typeof i=="object")for(let r in i)this.node.style[B0(r)]=i[r]==null||X3.test(i[r])?"":i[r]}return arguments.length===2&&(this.node.style[B0(i)]=e==null||X3.test(e)?"":e),this}function xM(){return this.css("display","")}function yM(){return this.css("display","none")}function vM(){return this.css("display")!=="none"}function bM(i,e,t){if(i==null)return this.data(mu(Hw(this.node.attributes,r=>r.nodeName.indexOf("data-")===0),r=>r.nodeName.slice(5)));if(i instanceof Array){let r={};for(let n of i)r[n]=this.data(n);return r}else if(typeof i=="object")for(e in i)this.data(e,i[e]);else if(arguments.length<2)try{return JSON.parse(this.attr("data-"+i))}catch{return this.attr("data-"+i)}else this.attr("data-"+i,e===null?null:t===!0||typeof e=="string"||typeof e=="number"?e:JSON.stringify(e));return this}function wM(i,e){if(typeof arguments[0]=="object")for(let t in i)this.remember(t,i[t]);else{if(arguments.length===1)return this.memory()[i];this.memory()[i]=e}return this}function MM(){if(arguments.length===0)this._memory={};else for(let i=arguments.length-1;i>=0;i--)delete this.memory()[arguments[i]];return this}function TM(){return this._memory=this._memory||{}}function NM(i){return i.length===4?["#",i.substring(1,2),i.substring(1,2),i.substring(2,3),i.substring(2,3),i.substring(3,4),i.substring(3,4)].join(""):i}function EM(i){let e=Math.round(i),r=Math.max(0,Math.min(255,e)).toString(16);return r.length===1?"0"+r:r}function Ws(i,e){for(let t=e.length;t--;)if(i[e[t]]==null)return!1;return!0}function SM(i,e){let t=Ws(i,"rgb")?{_a:i.r,_b:i.g,_c:i.b,_d:0,space:"rgb"}:Ws(i,"xyz")?{_a:i.x,_b:i.y,_c:i.z,_d:0,space:"xyz"}:Ws(i,"hsl")?{_a:i.h,_b:i.s,_c:i.l,_d:0,space:"hsl"}:Ws(i,"lab")?{_a:i.l,_b:i.a,_c:i.b,_d:0,space:"lab"}:Ws(i,"lch")?{_a:i.l,_b:i.c,_c:i.h,_d:0,space:"lch"}:Ws(i,"cmyk")?{_a:i.c,_b:i.m,_c:i.y,_d:i.k,space:"cmyk"}:{_a:0,_b:0,_c:0,space:"rgb"};return t.space=e||t.space,t}function AM(i){return i==="lab"||i==="xyz"||i==="lch"}function ru(i,e,t){return t<0&&(t+=1),t>1&&(t-=1),t<1/6?i+(e-i)*6*t:t<1/2?e:t<2/3?i+(e-i)*(2/3-t)*6:i}function kM(i,e){return new it(i,e).transformO(this.screenCTM().inverseO())}function Vs(i,e,t){return Math.abs(e-i)<(t||1e-6)}function CM(){return new be(this.node.getCTM())}function _M(){if(typeof this.isRoot=="function"&&!this.isRoot()){let i=this.rect(1,1),e=i.node.getScreenCTM();return i.remove(),new be(e)}return new be(this.node.getScreenCTM())}function on(){if(!on.nodes){let i=Rt().size(2,0);i.node.style.cssText=["opacity: 0","position: absolute","left: -100%","top: -100%","overflow: hidden"].join(";"),i.attr("focusable","false"),i.attr("aria-hidden","true");let e=i.path().node;on.nodes={svg:i,path:e}}if(!on.nodes.svg.node.parentNode){let i=Ne.document.body||Ne.document.documentElement;on.nodes.svg.addTo(i)}return on.nodes}function s2(i){return!i.width&&!i.height&&!i.x&&!i.y}function LM(i){return i===Ne.document||(Ne.document.documentElement.contains||function(e){for(;e.parentNode;)e=e.parentNode;return e===Ne.document}).call(Ne.document.documentElement,i)}function a2(i,e,t){let r;try{if(r=e(i.node),s2(r)&&!LM(i.node))throw new Error("Element not in the dom")}catch{r=t(i)}return r}function IM(){let t=a2(this,n=>n.getBBox(),n=>{try{let s=n.clone().addTo(on().svg).show(),a=s.node.getBBox();return s.remove(),a}catch(s){throw new Error(`Getting bbox of element "${n.node.nodeName}" is not possible: ${s.toString()}`)}});return new Vt(t)}function zM(i){let r=a2(this,s=>s.getBoundingClientRect(),s=>{throw new Error(`Getting rbox of element "${s.node.nodeName}" is not possible`)}),n=new Vt(r);return i?n.transform(i.screenCTM().inverseO()):n.addOffset()}function RM(i,e){let t=this.bbox();return i>t.x&&e>t.y&&i(r[n]=this.attr(n),r),{});if(typeof i=="object"&&i.constructor===Object)for(e in i)this.attr(e,i[e]);else if(e===null)this.node.removeAttribute(i);else{if(e==null)return e=this.node.getAttribute(i),e==null?HM[i]:K3.test(e)?parseFloat(e):e;e=h2.reduce((r,n)=>n(i,r,this),e),typeof e=="number"?e=new we(e):Ei.isColor(e)?e=new Ei(e):e.constructor===Array&&(e=new ln(e)),i==="leading"?this.leading&&this.leading(e):typeof t=="string"?this.node.setAttributeNS(t,i,e.toString()):this.node.setAttribute(i,e.toString()),this.rebuild&&(i==="font-size"||i==="x")&&this.rebuild()}}return this}function GM(){return this.attr("transform",null)}function WM(){return(this.attr("transform")||"").split(lM).slice(0,-1).map(function(e){let t=e.trim().split("(");return[t[0],t[1].split(Sr).map(function(r){return parseFloat(r)})]}).reverse().reduce(function(e,t){return t[0]==="matrix"?e.lmultiply(be.fromArray(t[1])):e[t[0]].apply(e,t[1])},new be)}function VM(i,e){if(this===i)return this;let t=this.screenCTM(),r=i.screenCTM().inverse();return this.addTo(i,e).untransform().transform(r.multiply(t)),this}function YM(i){return this.toParent(this.root(),i)}function XM(i,e){if(i==null||typeof i=="string"){let n=new be(this).decompose();return i==null?n:n[i]}be.isMatrixLike(i)||(i={...i,origin:au(i,this)});let t=e===!0?this:e||!1,r=new be(t).transform(i);return this.attr("transform",r)}function vu(i){return this.attr("rx",i)}function bu(i){return this.attr("ry",i)}function d2(i){return i==null?this.cx()-this.rx():this.cx(i+this.rx())}function c2(i){return i==null?this.cy()-this.ry():this.cy(i+this.ry())}function u2(i){return this.attr("cx",i)}function f2(i){return this.attr("cy",i)}function m2(i){return i==null?this.rx()*2:this.rx(new we(i).divide(2))}function p2(i){return i==null?this.ry()*2:this.ry(new we(i).divide(2))}function g2(i,e){return(this._element||this).type==="radialGradient"?this.attr({fx:new we(i),fy:new we(e)}):this.attr({x1:new we(i),y1:new we(e)})}function x2(i,e){return(this._element||this).type==="radialGradient"?this.attr({cx:new we(i),cy:new we(e)}):this.attr({x2:new we(i),y2:new we(e)})}function JM(i){return i==null?this.bbox().x:this.move(i,this.bbox().y)}function eT(i){return i==null?this.bbox().y:this.move(this.bbox().x,i)}function tT(i){let e=this.bbox();return i==null?e.width:this.size(i,e.height)}function iT(i){let e=this.bbox();return i==null?e.height:this.size(e.width,i)}function Xs(i,e){return function(t){return t==null?this[i]:(this[i]=t,e&&e.call(this),this)}}function Q3(){let i=(this._duration||500)/1e3,e=this._overshoot||0,t=1e-10,r=Math.PI,n=Math.log(e/100+t),s=-n/Math.sqrt(r*r+n*n),a=3.9/(s*i);this.d=2*s*a,this.k=a*a}function sT(i){let e=i.segment[0];return du[e](i.segment.slice(1),i.p,i.p0)}function cu(i){return i.segment.length&&i.segment.length-1===nT[i.segment[0].toUpperCase()]}function aT(i,e){i.inNumber&&Jn(i,!1);let t=xu.test(e);if(t)i.segment=[e];else{let r=i.lastCommand,n=r.toLowerCase(),s=r===n;i.segment=[n==="m"?s?"l":"L":r]}return i.inSegment=!0,i.lastCommand=i.segment[0],t}function Jn(i,e){if(!i.inNumber)throw new Error("Parser Error");i.number&&i.segment.push(parseFloat(i.number)),i.inNumber=e,i.number="",i.pointSeen=!1,i.hasExponent=!1,cu(i)&&uu(i)}function uu(i){i.inSegment=!1,i.absolute&&(i.segment=sT(i)),i.segments.push(i.segment)}function oT(i){if(!i.segment.length)return!1;let e=i.segment[0].toUpperCase()==="A",t=i.segment.length;return e&&(t===4||t===5)}function lT(i){return i.lastToken.toUpperCase()==="E"}function hT(i,e=!0){let t=0,r="",n={segment:[],inNumber:!1,number:"",lastToken:"",inSegment:!1,segments:[],pointSeen:!1,hasExponent:!1,absolute:e,p0:new it,p:new it};for(;n.lastToken=r,r=i.charAt(t++);)if(!(!n.inSegment&&aT(n,r))){if(r==="."){if(n.pointSeen||n.hasExponent){Jn(n,!1),--t;continue}n.inNumber=!0,n.pointSeen=!0,n.number+=r;continue}if(!isNaN(parseInt(r))){if(n.number==="0"||oT(n)){n.inNumber=!0,n.number=r,Jn(n,!0);continue}n.inNumber=!0,n.number+=r;continue}if(r===" "||r===","){n.inNumber&&Jn(n,!1);continue}if(r==="-"){if(n.inNumber&&!lT(n)){Jn(n,!1),--t;continue}n.number+=r,n.inNumber=!0;continue}if(r.toUpperCase()==="E"){n.number+=r,n.hasExponent=!0;continue}if(xu.test(r)){if(n.inNumber)Jn(n,!1);else if(cu(n))uu(n);else throw new Error("parser Error");--t}}return n.inNumber&&Jn(n,!1),n.inSegment&&cu(n)&&uu(n),n.segments}function dT(i){let e="";for(let t=0,r=i.length;t{let n;try{n=t.bbox()}catch{return}let s=new be(t),a=s.translate(i,e).transform(s.inverse()),o=new it(n.x,n.y).transform(a);t.move(o.x,o.y)}),this}function OT(i){return this.dmove(i,0)}function BT(i){return this.dmove(0,i)}function PT(i,e=this.bbox()){return i==null?e.height:this.size(e.width,i,e)}function FT(i=0,e=0,t=this.bbox()){let r=i-t.x,n=e-t.y;return this.dmove(r,n)}function qT(i,e,t=this.bbox()){let r=ia(this,i,e,t),n=r.width/t.width,s=r.height/t.height;return this.children().forEach((a,o)=>{let l=new it(t).transform(new be(a).inverse());a.scale(n,s,l.x,l.y)}),this}function HT(i,e=this.bbox()){return i==null?e.width:this.size(i,e.height,e)}function UT(i,e=this.bbox()){return i==null?e.x:this.move(i,e.y,e)}function $T(i,e=this.bbox()){return i==null?e.y:this.move(e.x,i,e)}function jT(i,e){if(!i)return"";if(!e)return i;let t=i+"{";for(let r in e)t+=Uw(r)+":"+e[r]+";";return t+="}",t}var su,J3,pu,$w,P0,Bo,jw,Ne,No,es,gu,iu,Ww,n2,sM,aM,oM,lM,hM,V3,Y3,X3,K3,dM,Sr,xu,Ei,it,be,Vt,Nr,DM,PM,o2,ts,Mo,HM,ln,we,h2,hn,Si,wo,jM,Yt,So,Dt,KM,Ks,F0,ZM,is,rs,$i,rr,QM,wu,ns,Zs,rT,Ao,ko,Qs,lu,hu,nT,du,nu,Er,y2,Tr,Co,_o,cT,ss,Mu,nr,v2,ji,as,Ve,To,Ae,vT,bT,q0,Gi,Js,b2,w2,fu,MT,Lo,Io,M2,_e,ea,Wi,zo,ta,T2,Le,dn,Ro,H0,Do,Oo,U0,ke,wt=T(()=>{su={},J3=[];pu="http://www.w3.org/2000/svg",$w="http://www.w3.org/1999/xhtml",P0="http://www.w3.org/2000/xmlns/",Bo="http://www.w3.org/1999/xlink",jw="http://svgjs.dev/svgjs",Ne={window:typeof window>"u"?null:window,document:typeof document>"u"?null:document},No=class{},es={},gu="___SYMBOL___ROOT___";iu=Ni;Ww=1e3;fe("Dom",{siblings:Vw,position:Yw,next:Xw,prev:Kw,forward:Zw,backward:Qw,front:Jw,back:eM,before:tM,after:iM,insertBefore:rM,insertAfter:nM});n2=/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,sM=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,aM=/rgb\((\d+),(\d+),(\d+)\)/,oM=/(#[a-z_][a-z0-9\-_]*)/i,lM=/\)\s*,?\s*/,hM=/\s/g,V3=/^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i,Y3=/^rgb\(/,X3=/^(\s+)?$/,K3=/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,dM=/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,Sr=/[\s,]+/,xu=/[MLHVCSQTAZ]/i;fe("Dom",{classes:cM,hasClass:uM,addClass:fM,removeClass:mM,toggleClass:pM});fe("Dom",{css:gM,show:xM,hide:yM,visible:vM});fe("Dom",{data:bM});fe("Dom",{remember:wM,forget:MM,memory:TM});Ei=class i{constructor(...e){this.init(...e)}static isColor(e){return e&&(e instanceof i||this.isRgb(e)||this.test(e))}static isRgb(e){return e&&typeof e.r=="number"&&typeof e.g=="number"&&typeof e.b=="number"}static random(e="vibrant",t,r){let{random:n,round:s,sin:a,PI:o}=Math;if(e==="vibrant"){let l=24*n()+57,h=38*n()+45,d=360*n();return new i(l,h,d,"lch")}else if(e==="sine"){t=t??n();let l=s(80*a(2*o*t/.5+.01)+150),h=s(50*a(2*o*t/.5+4.6)+200),d=s(100*a(2*o*t/.5+2.3)+150);return new i(l,h,d)}else if(e==="pastel"){let l=8*n()+86,h=17*n()+9,d=360*n();return new i(l,h,d,"lch")}else if(e==="dark"){let l=10+10*n(),h=50*n()+86,d=360*n();return new i(l,h,d,"lch")}else if(e==="rgb"){let l=255*n(),h=255*n(),d=255*n();return new i(l,h,d)}else if(e==="lab"){let l=100*n(),h=256*n()-128,d=256*n()-128;return new i(l,h,d,"lab")}else if(e==="grey"){let l=255*n();return new i(l,l,l)}else throw new Error("Unsupported random color mode")}static test(e){return typeof e=="string"&&(V3.test(e)||Y3.test(e))}cmyk(){let{_a:e,_b:t,_c:r}=this.rgb(),[n,s,a]=[e,t,r].map(f=>f/255),o=Math.min(1-n,1-s,1-a);if(o===1)return new i(0,0,0,1,"cmyk");let l=(1-n-o)/(1-o),h=(1-s-o)/(1-o),d=(1-a-o)/(1-o);return new i(l,h,d,o,"cmyk")}hsl(){let{_a:e,_b:t,_c:r}=this.rgb(),[n,s,a]=[e,t,r].map(x=>x/255),o=Math.max(n,s,a),l=Math.min(n,s,a),h=(o+l)/2,d=o===l,c=o-l,f=d?0:h>.5?c/(2-o-l):c/(o+l),m=d?0:o===n?((s-a)/c+(sparseInt(x));Object.assign(this,{_a:f,_b:m,_c:g,_d:0,space:"rgb"})}else if(V3.test(e)){let c=x=>parseInt(x,16),[,f,m,g]=sM.exec(NM(e)).map(c);Object.assign(this,{_a:f,_b:m,_c:g,_d:0,space:"rgb"})}else throw Error("Unsupported string format, can't construct Color");let{_a:a,_b:o,_c:l,_d:h}=this,d=this.space==="rgb"?{r:a,g:o,b:l}:this.space==="xyz"?{x:a,y:o,z:l}:this.space==="hsl"?{h:a,s:o,l}:this.space==="lab"?{l:a,a:o,b:l}:this.space==="lch"?{l:a,c:o,h:l}:this.space==="cmyk"?{c:a,m:o,y:l,k:h}:{};Object.assign(this,d)}lab(){let{x:e,y:t,z:r}=this.xyz(),n=116*t-16,s=500*(e-t),a=200*(t-r);return new i(n,s,a,"lab")}lch(){let{l:e,a:t,b:r}=this.lab(),n=Math.sqrt(t**2+r**2),s=180*Math.atan2(r,t)/Math.PI;return s<0&&(s*=-1,s=360-s),new i(e,n,s,"lch")}rgb(){if(this.space==="rgb")return this;if(AM(this.space)){let{x:e,y:t,z:r}=this;if(this.space==="lab"||this.space==="lch"){let{l:m,a:g,b:x}=this;if(this.space==="lch"){let{c:O,h:q}=this,F=Math.PI/180;g=O*Math.cos(F*q),x=O*Math.sin(F*q)}let y=(m+16)/116,b=g/500+y,E=y-x/200,k=16/116,L=.008856,I=7.787;e=.95047*(b**3>L?b**3:(b-k)/I),t=1*(y**3>L?y**3:(y-k)/I),r=1.08883*(E**3>L?E**3:(E-k)/I)}let n=e*3.2406+t*-1.5372+r*-.4986,s=e*-.9689+t*1.8758+r*.0415,a=e*.0557+t*-.204+r*1.057,o=Math.pow,l=.0031308,h=n>l?1.055*o(n,1/2.4)-.055:12.92*n,d=s>l?1.055*o(s,1/2.4)-.055:12.92*s,c=a>l?1.055*o(a,1/2.4)-.055:12.92*a;return new i(255*h,255*d,255*c)}else if(this.space==="hsl"){let{h:e,s:t,l:r}=this;if(e/=360,t/=100,r/=100,t===0)return r*=255,new i(r,r,r);let n=r<.5?r*(1+t):r+t-r*t,s=2*r-n,a=255*ru(s,n,e+1/3),o=255*ru(s,n,e),l=255*ru(s,n,e-1/3);return new i(a,o,l)}else if(this.space==="cmyk"){let{c:e,m:t,y:r,k:n}=this,s=255*(1-Math.min(1,e*(1-n)+n)),a=255*(1-Math.min(1,t*(1-n)+n)),o=255*(1-Math.min(1,r*(1-n)+n));return new i(s,a,o)}else return this}toArray(){let{_a:e,_b:t,_c:r,_d:n,space:s}=this;return[e,t,r,n,s]}toHex(){let[e,t,r]=this._clamped().map(EM);return`#${e}${t}${r}`}toRgb(){let[e,t,r]=this._clamped();return`rgb(${e},${t},${r})`}toString(){return this.toHex()}xyz(){let{_a:e,_b:t,_c:r}=this.rgb(),[n,s,a]=[e,t,r].map(b=>b/255),o=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92,l=s>.04045?Math.pow((s+.055)/1.055,2.4):s/12.92,h=a>.04045?Math.pow((a+.055)/1.055,2.4):a/12.92,d=(o*.4124+l*.3576+h*.1805)/.95047,c=(o*.2126+l*.7152+h*.0722)/1,f=(o*.0193+l*.1192+h*.9505)/1.08883,m=d>.008856?Math.pow(d,1/3):7.787*d+16/116,g=c>.008856?Math.pow(c,1/3):7.787*c+16/116,x=f>.008856?Math.pow(f,1/3):7.787*f+16/116;return new i(m,g,x,"xyz")}_clamped(){let{_a:e,_b:t,_c:r}=this.rgb(),{max:n,min:s,round:a}=Math,o=l=>n(0,s(a(l),255));return[e,t,r].map(o)}},it=class i{constructor(...e){this.init(...e)}clone(){return new i(this)}init(e,t){let r={x:0,y:0},n=Array.isArray(e)?{x:e[0],y:e[1]}:typeof e=="object"?{x:e.x,y:e.y}:{x:e,y:t};return this.x=n.x==null?r.x:n.x,this.y=n.y==null?r.y:n.y,this}toArray(){return[this.x,this.y]}transform(e){return this.clone().transformO(e)}transformO(e){be.isMatrixLike(e)||(e=new be(e));let{x:t,y:r}=this;return this.x=e.a*t+e.c*r+e.e,this.y=e.b*t+e.d*r+e.f,this}};be=class i{constructor(...e){this.init(...e)}static formatTransforms(e){let t=e.flip==="both"||e.flip===!0,r=e.flip&&(t||e.flip==="x")?-1:1,n=e.flip&&(t||e.flip==="y")?-1:1,s=e.skew&&e.skew.length?e.skew[0]:isFinite(e.skew)?e.skew:isFinite(e.skewX)?e.skewX:0,a=e.skew&&e.skew.length?e.skew[1]:isFinite(e.skew)?e.skew:isFinite(e.skewY)?e.skewY:0,o=e.scale&&e.scale.length?e.scale[0]*r:isFinite(e.scale)?e.scale*r:isFinite(e.scaleX)?e.scaleX*r:r,l=e.scale&&e.scale.length?e.scale[1]*n:isFinite(e.scale)?e.scale*n:isFinite(e.scaleY)?e.scaleY*n:n,h=e.shear||0,d=e.rotate||e.theta||0,c=new it(e.origin||e.around||e.ox||e.originX,e.oy||e.originY),f=c.x,m=c.y,g=new it(e.position||e.px||e.positionX||NaN,e.py||e.positionY||NaN),x=g.x,y=g.y,b=new it(e.translate||e.tx||e.translateX,e.ty||e.translateY),E=b.x,k=b.y,L=new it(e.relative||e.rx||e.relativeX,e.ry||e.relativeY),I=L.x,O=L.y;return{scaleX:o,scaleY:l,skewX:s,skewY:a,shear:h,theta:d,rx:I,ry:O,tx:E,ty:k,ox:f,oy:m,px:x,py:y}}static fromArray(e){return{a:e[0],b:e[1],c:e[2],d:e[3],e:e[4],f:e[5]}}static isMatrixLike(e){return e.a!=null||e.b!=null||e.c!=null||e.d!=null||e.e!=null||e.f!=null}static matrixMultiply(e,t,r){let n=e.a*t.a+e.c*t.b,s=e.b*t.a+e.d*t.b,a=e.a*t.c+e.c*t.d,o=e.b*t.c+e.d*t.d,l=e.e+e.a*t.e+e.c*t.f,h=e.f+e.b*t.e+e.d*t.f;return r.a=n,r.b=s,r.c=a,r.d=o,r.e=l,r.f=h,r}around(e,t,r){return this.clone().aroundO(e,t,r)}aroundO(e,t,r){let n=e||0,s=t||0;return this.translateO(-n,-s).lmultiplyO(r).translateO(n,s)}clone(){return new i(this)}decompose(e=0,t=0){let r=this.a,n=this.b,s=this.c,a=this.d,o=this.e,l=this.f,h=r*a-n*s,d=h>0?1:-1,c=d*Math.sqrt(r*r+n*n),f=Math.atan2(d*n,d*r),m=180/Math.PI*f,g=Math.cos(f),x=Math.sin(f),y=(r*s+n*a)/h,b=s*c/(y*r-n)||a*c/(y*n+r),E=o-e+e*g*c+t*(y*g*c-x*b),k=l-t+e*x*c+t*(y*x*c+g*b);return{scaleX:c,scaleY:b,shear:y,rotate:m,translateX:E,translateY:k,originX:e,originY:t,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}}equals(e){if(e===this)return!0;let t=new i(e);return Vs(this.a,t.a)&&Vs(this.b,t.b)&&Vs(this.c,t.c)&&Vs(this.d,t.d)&&Vs(this.e,t.e)&&Vs(this.f,t.f)}flip(e,t){return this.clone().flipO(e,t)}flipO(e,t){return e==="x"?this.scaleO(-1,1,t,0):e==="y"?this.scaleO(1,-1,0,t):this.scaleO(-1,-1,e,t||e)}init(e){let t=i.fromArray([1,0,0,1,0,0]);return e=e instanceof Si?e.matrixify():typeof e=="string"?i.fromArray(e.split(Sr).map(parseFloat)):Array.isArray(e)?i.fromArray(e):typeof e=="object"&&i.isMatrixLike(e)?e:typeof e=="object"?new i().transform(e):arguments.length===6?i.fromArray([].slice.call(arguments)):t,this.a=e.a!=null?e.a:t.a,this.b=e.b!=null?e.b:t.b,this.c=e.c!=null?e.c:t.c,this.d=e.d!=null?e.d:t.d,this.e=e.e!=null?e.e:t.e,this.f=e.f!=null?e.f:t.f,this}inverse(){return this.clone().inverseO()}inverseO(){let e=this.a,t=this.b,r=this.c,n=this.d,s=this.e,a=this.f,o=e*n-t*r;if(!o)throw new Error("Cannot invert "+this);let l=n/o,h=-t/o,d=-r/o,c=e/o,f=-(l*s+d*a),m=-(h*s+c*a);return this.a=l,this.b=h,this.c=d,this.d=c,this.e=f,this.f=m,this}lmultiply(e){return this.clone().lmultiplyO(e)}lmultiplyO(e){let t=this,r=e instanceof i?e:new i(e);return i.matrixMultiply(r,t,this)}multiply(e){return this.clone().multiplyO(e)}multiplyO(e){let t=this,r=e instanceof i?e:new i(e);return i.matrixMultiply(t,r,this)}rotate(e,t,r){return this.clone().rotateO(e,t,r)}rotateO(e,t=0,r=0){e=tu(e);let n=Math.cos(e),s=Math.sin(e),{a,b:o,c:l,d:h,e:d,f:c}=this;return this.a=a*n-o*s,this.b=o*n+a*s,this.c=l*n-h*s,this.d=h*n+l*s,this.e=d*n-c*s+r*s-t*n+t,this.f=c*n+d*s-t*s-r*n+r,this}scale(e,t,r,n){return this.clone().scaleO(...arguments)}scaleO(e,t=e,r=0,n=0){arguments.length===3&&(n=r,r=t,t=e);let{a:s,b:a,c:o,d:l,e:h,f:d}=this;return this.a=s*e,this.b=a*t,this.c=o*e,this.d=l*t,this.e=h*e-r*e+r,this.f=d*t-n*t+n,this}shear(e,t,r){return this.clone().shearO(e,t,r)}shearO(e,t=0,r=0){let{a:n,b:s,c:a,d:o,e:l,f:h}=this;return this.a=n+s*e,this.c=a+o*e,this.e=l+h*e-r*e,this}skew(e,t,r,n){return this.clone().skewO(...arguments)}skewO(e,t=e,r=0,n=0){arguments.length===3&&(n=r,r=t,t=e),e=tu(e),t=tu(t);let s=Math.tan(e),a=Math.tan(t),{a:o,b:l,c:h,d,e:c,f}=this;return this.a=o+l*s,this.b=l+o*a,this.c=h+d*s,this.d=d+h*a,this.e=c+f*s-n*s,this.f=f+c*a-r*a,this}skewX(e,t,r){return this.skew(e,0,t,r)}skewY(e,t,r){return this.skew(0,e,t,r)}toArray(){return[this.a,this.b,this.c,this.d,this.e,this.f]}toString(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}transform(e){if(i.isMatrixLike(e))return new i(e).multiplyO(this);let t=i.formatTransforms(e),r=this,{x:n,y:s}=new it(t.ox,t.oy).transform(r),a=new i().translateO(t.rx,t.ry).lmultiplyO(r).translateO(-n,-s).scaleO(t.scaleX,t.scaleY).skewO(t.skewX,t.skewY).shearO(t.shear).rotateO(t.theta).translateO(n,s);if(isFinite(t.px)||isFinite(t.py)){let o=new it(n,s).transform(a),l=isFinite(t.px)?t.px-o.x:0,h=isFinite(t.py)?t.py-o.y:0;a.translateO(l,h)}return a.translateO(t.tx,t.ty),a}translate(e,t){return this.clone().translateO(e,t)}translateO(e,t){return this.e+=e||0,this.f+=t||0,this}valueOf(){return{a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}}};Me(be,"Matrix");Vt=class i{constructor(...e){this.init(...e)}addOffset(){return this.x+=Ne.window.pageXOffset,this.y+=Ne.window.pageYOffset,new i(this)}init(e){let t=[0,0,0,0];return e=typeof e=="string"?e.split(Sr).map(parseFloat):Array.isArray(e)?e:typeof e=="object"?[e.left!=null?e.left:e.x,e.top!=null?e.top:e.y,e.width,e.height]:arguments.length===4?[].slice.call(arguments):t,this.x=e[0]||0,this.y=e[1]||0,this.width=this.w=e[2]||0,this.height=this.h=e[3]||0,this.x2=this.x+this.w,this.y2=this.y+this.h,this.cx=this.x+this.w/2,this.cy=this.y+this.h/2,this}isNulled(){return s2(this)}merge(e){let t=Math.min(this.x,e.x),r=Math.min(this.y,e.y),n=Math.max(this.x+this.width,e.x+e.width)-t,s=Math.max(this.y+this.height,e.y+e.height)-r;return new i(t,r,n,s)}toArray(){return[this.x,this.y,this.width,this.height]}toString(){return this.x+" "+this.y+" "+this.width+" "+this.height}transform(e){e instanceof be||(e=new be(e));let t=1/0,r=-1/0,n=1/0,s=-1/0;return[new it(this.x,this.y),new it(this.x2,this.y),new it(this.x,this.y2),new it(this.x2,this.y2)].forEach(function(o){o=o.transform(e),t=Math.min(t,o.x),r=Math.max(r,o.x),n=Math.min(n,o.y),s=Math.max(s,o.y)}),new i(t,n,r-t,s-n)}};fe({viewbox:{viewbox(i,e,t,r){return i==null?new Vt(this.attr("viewBox")):this.attr("viewBox",new Vt(i,e,t,r))},zoom(i,e){let{width:t,height:r}=this.attr(["width","height"]);if((!t&&!r||typeof t=="string"||typeof r=="string")&&(t=this.node.clientWidth,r=this.node.clientHeight),!t||!r)throw new Error("Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element");let n=this.viewbox(),s=t/n.width,a=r/n.height,o=Math.min(s,a);if(i==null)return o;let l=o/i;l===1/0&&(l=Number.MAX_SAFE_INTEGER/100),e=e||new it(t/2/s+n.x,r/2/a+n.y);let h=new Vt(n).transform(new be({scale:l,origin:e}));return this.viewbox(h)}}});Me(Vt,"Box");Nr=class extends Array{constructor(e=[],...t){if(super(e,...t),typeof e=="number")return this;this.length=0,this.push(...e)}};xe([Nr],{each(i,...e){return typeof i=="function"?this.map((t,r,n)=>i.call(t,t,r,n)):this.map(t=>t[i](...e))},toArray(){return Array.prototype.concat.apply([],this)}});DM=["toArray","constructor","each"];Nr.extend=function(i){i=i.reduce((e,t)=>(DM.includes(t)||t[0]==="_"||(e[t]=function(...r){return this.each(t,...r)}),e),{}),xe([Nr],i)};PM=0,o2={};ts=class extends No{addEventListener(){}dispatch(e,t,r){return qM(this,e,t,r)}dispatchEvent(e){let t=this.getEventHolder().events;if(!t)return!0;let r=t[e.type];for(let n in r)for(let s in r[n])r[n][s](e);return!e.defaultPrevented}fire(e,t,r){return this.dispatch(e,t,r),this}getEventHolder(){return this}getEventTarget(){return this}off(e,t,r){return Ys(this,e,t,r),this}on(e,t,r,n){return ou(this,e,t,r,n),this}removeEventListener(){}};Me(ts,"EventTarget");Mo={duration:400,ease:">",delay:0},HM={"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","text-anchor":"start"},ln=class extends Array{constructor(...e){super(...e),this.init(...e)}clone(){return new this.constructor(this)}init(e){return typeof e=="number"?this:(this.length=0,this.push(...this.parse(e)),this)}parse(e=[]){return e instanceof Array?e:e.trim().split(Sr).map(parseFloat)}toArray(){return Array.prototype.concat.apply([],this)}toSet(){return new Set(this)}toString(){return this.join(" ")}valueOf(){let e=[];return e.push(...this),e}},we=class i{constructor(...e){this.init(...e)}convert(e){return new i(this.value,e)}divide(e){return e=new i(e),new i(this/e,this.unit||e.unit)}init(e,t){return t=Array.isArray(e)?e[1]:t,e=Array.isArray(e)?e[0]:e,this.value=0,this.unit=t||"",typeof e=="number"?this.value=isNaN(e)?0:isFinite(e)?e:e<0?-34e37:34e37:typeof e=="string"?(t=e.match(n2),t&&(this.value=parseFloat(t[1]),t[5]==="%"?this.value/=100:t[5]==="s"&&(this.value*=1e3),this.unit=t[5])):e instanceof i&&(this.value=e.valueOf(),this.unit=e.unit),this}minus(e){return e=new i(e),new i(this-e,this.unit||e.unit)}plus(e){return e=new i(e),new i(this+e,this.unit||e.unit)}times(e){return e=new i(e),new i(this*e,this.unit||e.unit)}toArray(){return[this.value,this.unit]}toJSON(){return this.toString()}toString(){return(this.unit==="%"?~~(this.value*1e8)/1e6:this.unit==="s"?this.value/1e3:this.value)+this.unit}valueOf(){return this.value}},h2=[];hn=class i extends ts{constructor(e,t){super(),this.node=e,this.type=e.nodeName,t&&e!==t&&this.attr(t)}add(e,t){return e=Rt(e),e.removeNamespace&&this.node instanceof Ne.window.SVGElement&&e.removeNamespace(),t==null?this.node.appendChild(e.node):e.node!==this.node.childNodes[t]&&this.node.insertBefore(e.node,this.node.childNodes[t]),this}addTo(e,t){return Rt(e).put(this,t)}children(){return new Nr(mu(this.node.children,function(e){return Ni(e)}))}clear(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this}clone(e=!0,t=!0){this.writeDataToDom();let r=this.node.cloneNode(e);return t&&(r=r2(r)),new this.constructor(r)}each(e,t){let r=this.children(),n,s;for(n=0,s=r.length;n=0}html(e,t){return this.xml(e,t,$w)}id(e){return typeof e>"u"&&!this.node.id&&(this.node.id=i2(this.type)),this.attr("id",e)}index(e){return[].slice.call(this.node.childNodes).indexOf(e.node)}last(){return Ni(this.node.lastChild)}matches(e){let t=this.node,r=t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector||null;return r&&r.call(t,e)}parent(e){let t=this;if(!t.node.parentNode)return null;if(t=Ni(t.node.parentNode),!e)return t;do if(typeof e=="string"?t.matches(e):t instanceof e)return t;while(t=Ni(t.node.parentNode));return t}put(e,t){return e=Rt(e),this.add(e,t),e}putIn(e,t){return Rt(e).add(this,t)}remove(){return this.parent()&&this.parent().removeElement(this),this}removeElement(e){return this.node.removeChild(e.node),this}replace(e){return e=Rt(e),this.node.parentNode&&this.node.parentNode.replaceChild(e.node,this.node),e}round(e=2,t=null){let r=10**e,n=this.attr(t);for(let s in n)typeof n[s]=="number"&&(n[s]=Math.round(n[s]*r)/r);return this.attr(n),this}svg(e,t){return this.xml(e,t,pu)}toString(){return this.id()}words(e){return this.node.textContent=e,this}wrap(e){let t=this.parent();if(!t)return this.addTo(e);let r=t.index(this);return t.put(e,r).put(this)}writeDataToDom(){return this.each(function(){this.writeDataToDom()}),this}xml(e,t,r){if(typeof e=="boolean"&&(r=t,t=e,e=null),e==null||typeof e=="function"){t=t??!0,this.writeDataToDom();let o=this;if(e!=null){if(o=Ni(o.node.cloneNode(!0)),t){let l=e(o);if(o=l||o,l===!1)return""}o.each(function(){let l=e(this),h=l||this;l===!1?this.remove():l&&this!==h&&this.replace(h)},!0)}return t?o.node.outerHTML:o.node.innerHTML}t=t??!1;let n=Eo("wrapper",r),s=Ne.document.createDocumentFragment();n.innerHTML=e;for(let o=n.children.length;o--;)s.appendChild(n.firstElementChild);let a=this.parent();return t?this.replace(s)&&a:this.add(s)}};xe(hn,{attr:$M,find:OM,findOne:BM});Me(hn,"Dom");Si=class extends hn{constructor(e,t){super(e,t),this.dom={},this.node.instance=this,e.hasAttribute("svgjs:data")&&this.setData(JSON.parse(e.getAttribute("svgjs:data"))||{})}center(e,t){return this.cx(e).cy(t)}cx(e){return e==null?this.x()+this.width()/2:this.x(e-this.width()/2)}cy(e){return e==null?this.y()+this.height()/2:this.y(e-this.height()/2)}defs(){let e=this.root();return e&&e.defs()}dmove(e,t){return this.dx(e).dy(t)}dx(e=0){return this.x(new we(e).plus(this.x()))}dy(e=0){return this.y(new we(e).plus(this.y()))}getEventHolder(){return this}height(e){return this.attr("height",e)}move(e,t){return this.x(e).y(t)}parents(e=this.root()){let t=typeof e=="string";t||(e=Rt(e));let r=new Nr,n=this;for(;(n=n.parent())&&n.node!==Ne.document&&n.nodeName!=="#document-fragment"&&(r.push(n),!(!t&&n.node===e.node||t&&n.matches(e)));)if(n.node===this.root().node)return null;return r}reference(e){if(e=this.attr(e),!e)return null;let t=(e+"").match(oM);return t?Rt(t[1]):null}root(){let e=this.parent(Gw(gu));return e&&e.root()}setData(e){return this.dom=e,this}size(e,t){let r=ia(this,e,t);return this.width(new we(r.width)).height(new we(r.height))}width(e){return this.attr("width",e)}writeDataToDom(){return this.node.removeAttribute("svgjs:data"),Object.keys(this.dom).length&&this.node.setAttribute("svgjs:data",JSON.stringify(this.dom)),super.writeDataToDom()}x(e){return this.attr("x",e)}y(e){return this.attr("y",e)}};xe(Si,{bbox:IM,rbox:zM,inside:RM,point:kM,ctm:CM,screenCTM:_M});Me(Si,"Element");wo={stroke:["color","width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],fill:["color","opacity","rule"],prefix:function(i,e){return e==="color"?i:i+"-"+e}};["fill","stroke"].forEach(function(i){let e={},t;e[i]=function(r){if(typeof r>"u")return this.attr(i);if(typeof r=="string"||r instanceof Ei||Ei.isRgb(r)||r instanceof Si)this.attr(i,r);else for(t=wo[i].length-1;t>=0;t--)r[wo[i][t]]!=null&&this.attr(wo.prefix(i,wo[i][t]),r[wo[i][t]]);return this},fe(["Element","Runner"],e)});fe(["Element","Runner"],{matrix:function(i,e,t,r,n,s){return i==null?new be(this):this.attr("transform",new be(i,e,t,r,n,s))},rotate:function(i,e,t){return this.transform({rotate:i,ox:e,oy:t},!0)},skew:function(i,e,t,r){return arguments.length===1||arguments.length===3?this.transform({skew:i,ox:e,oy:t},!0):this.transform({skew:[i,e],ox:t,oy:r},!0)},shear:function(i,e,t){return this.transform({shear:i,ox:e,oy:t},!0)},scale:function(i,e,t,r){return arguments.length===1||arguments.length===3?this.transform({scale:i,ox:e,oy:t},!0):this.transform({scale:[i,e],ox:t,oy:r},!0)},translate:function(i,e){return this.transform({translate:[i,e]},!0)},relative:function(i,e){return this.transform({relative:[i,e]},!0)},flip:function(i="both",e="center"){return"xybothtrue".indexOf(i)===-1&&(e=i,i="both"),this.transform({flip:i,origin:e},!0)},opacity:function(i){return this.attr("opacity",i)}});fe("radius",{radius:function(i,e=i){return(this._element||this).type==="radialGradient"?this.attr("r",new we(i)):this.rx(i).ry(e)}});fe("Path",{length:function(){return this.node.getTotalLength()},pointAt:function(i){return new it(this.node.getPointAtLength(i))}});fe(["Element","Runner"],{font:function(i,e){if(typeof i=="object"){for(e in i)this.font(e,i[e]);return this}return i==="leading"?this.leading(e):i==="anchor"?this.attr("text-anchor",e):i==="size"||i==="family"||i==="weight"||i==="stretch"||i==="variant"||i==="style"?this.attr("font-"+i,e):this.attr(i,e)}});jM=["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","mouseenter","mouseleave","touchstart","touchmove","touchleave","touchend","touchcancel"].reduce(function(i,e){let t=function(r){return r===null?this.off(e):this.on(e,r),this};return i[e]=t,i},{});fe("Element",jM);fe("Element",{untransform:GM,matrixify:WM,toParent:VM,toRoot:YM,transform:XM});Yt=class i extends Si{flatten(e=this,t){return this.each(function(){if(this instanceof i)return this.flatten().ungroup()}),this}ungroup(e=this.parent(),t=e.index(this)){return t=t===-1?e.children().length:t,this.each(function(r,n){return n[n.length-r-1].toParent(e,t)}),this.remove()}};Me(Yt,"Container");So=class extends Yt{constructor(e,t=e){super(Ue("defs",e),t)}flatten(){return this}ungroup(){return this}};Me(So,"Defs");Dt=class extends Si{};Me(Dt,"Shape");KM={__proto__:null,rx:vu,ry:bu,x:d2,y:c2,cx:u2,cy:f2,width:m2,height:p2},Ks=class extends Dt{constructor(e,t=e){super(Ue("ellipse",e),t)}size(e,t){let r=ia(this,e,t);return this.rx(new we(r.width).divide(2)).ry(new we(r.height).divide(2))}};xe(Ks,KM);fe("Container",{ellipse:He(function(i=0,e=i){return this.put(new Ks).size(i,e).move(0,0)})});Me(Ks,"Ellipse");F0=class extends hn{constructor(e=Ne.document.createDocumentFragment()){super(e)}xml(e,t,r){if(typeof e=="boolean"&&(r=t,t=e,e=null),e==null||typeof e=="function"){let n=new hn(Eo("wrapper",r));return n.add(this.node.cloneNode(!0)),n.xml(!1,r)}return super.xml(e,!1,r)}};Me(F0,"Fragment");ZM={__proto__:null,from:g2,to:x2},is=class extends Yt{constructor(e,t){super(Ue(e+"Gradient",typeof e=="string"?null:e),t)}attr(e,t,r){return e==="transform"&&(e="gradientTransform"),super.attr(e,t,r)}bbox(){return new Vt}targets(){return ra("svg [fill*="+this.id()+"]")}toString(){return this.url()}update(e){return this.clear(),typeof e=="function"&&e.call(this,this),this}url(){return"url(#"+this.id()+")"}};xe(is,ZM);fe({Container:{gradient(...i){return this.defs().gradient(...i)}},Defs:{gradient:He(function(i,e){return this.put(new is(i)).update(e)})}});Me(is,"Gradient");rs=class extends Yt{constructor(e,t=e){super(Ue("pattern",e),t)}attr(e,t,r){return e==="transform"&&(e="patternTransform"),super.attr(e,t,r)}bbox(){return new Vt}targets(){return ra("svg [fill*="+this.id()+"]")}toString(){return this.url()}update(e){return this.clear(),typeof e=="function"&&e.call(this,this),this}url(){return"url(#"+this.id()+")"}};fe({Container:{pattern(...i){return this.defs().pattern(...i)}},Defs:{pattern:He(function(i,e,t){return this.put(new rs).update(t).attr({x:0,y:0,width:i,height:e,patternUnits:"userSpaceOnUse"})})}});Me(rs,"Pattern");$i=class extends Dt{constructor(e,t=e){super(Ue("image",e),t)}load(e,t){if(!e)return this;let r=new Ne.window.Image;return ou(r,"load",function(n){let s=this.parent(rs);this.width()===0&&this.height()===0&&this.size(r.width,r.height),s instanceof rs&&s.width()===0&&s.height()===0&&s.size(this.width(),this.height()),typeof t=="function"&&t.call(this,n)},this),ou(r,"load error",function(){Ys(r)}),this.attr("href",r.src=e,Bo)}};UM(function(i,e,t){return(i==="fill"||i==="stroke")&&dM.test(e)&&(e=t.root().defs().image(e)),e instanceof $i&&(e=t.root().defs().pattern(0,0,r=>{r.add(e)})),e});fe({Container:{image:He(function(i,e){return this.put(new $i).size(0,0).load(i,e)})}});Me($i,"Image");rr=class extends ln{bbox(){let e=-1/0,t=-1/0,r=1/0,n=1/0;return this.forEach(function(s){e=Math.max(s[0],e),t=Math.max(s[1],t),r=Math.min(s[0],r),n=Math.min(s[1],n)}),new Vt(r,n,e-r,t-n)}move(e,t){let r=this.bbox();if(e-=r.x,t-=r.y,!isNaN(e)&&!isNaN(t))for(let n=this.length-1;n>=0;n--)this[n]=[this[n][0]+e,this[n][1]+t];return this}parse(e=[0,0]){let t=[];e instanceof Array?e=Array.prototype.concat.apply([],e):e=e.trim().split(Sr).map(parseFloat),e.length%2!==0&&e.pop();for(let r=0,n=e.length;r=0;r--)n.width&&(this[r][0]=(this[r][0]-n.x)*e/n.width+n.x),n.height&&(this[r][1]=(this[r][1]-n.y)*t/n.height+n.y);return this}toLine(){return{x1:this[0][0],y1:this[0][1],x2:this[1][0],y2:this[1][1]}}toString(){let e=[];for(let t=0,r=this.length;t":function(i){return-Math.cos(i*Math.PI)/2+.5},">":function(i){return Math.sin(i*Math.PI/2)},"<":function(i){return-Math.cos(i*Math.PI/2)+1},bezier:function(i,e,t,r){return function(n){return n<0?i>0?e/i*n:t>0?r/t*n:0:n>1?t<1?(1-r)/(1-t)*n+(r-t)/(1-t):i<1?(1-e)/(1-i)*n+(e-i)/(1-i):1:3*n*(1-n)**2*e+3*n**2*(1-n)*r+n**3}},steps:function(i,e="end"){e=e.split("-").reverse()[0];let t=i;return e==="none"?--t:e==="both"&&++t,(r,n=!1)=>{let s=Math.floor(r*i),a=r*s%1===0;return(e==="start"||e==="both")&&++s,n&&a&&--s,r>=0&&s<0&&(s=0),r<=1&&s>t&&(s=t),s/t}}},Ao=class{done(){return!1}},ko=class extends Ao{constructor(e=Mo.ease){super(),this.ease=rT[e]||e}step(e,t,r){return typeof e!="number"?r<1?e:t:e+(t-e)*this.ease(r)}},Qs=class extends Ao{constructor(e){super(),this.stepper=e}done(e){return e.done}step(e,t,r,n){return this.stepper(e,t,r,n)}};lu=class extends Qs{constructor(e=500,t=0){super(),this.duration(e).overshoot(t)}step(e,t,r,n){if(typeof e=="string")return e;if(n.done=r===1/0,r===1/0)return t;if(r===0)return e;r>100&&(r=16),r/=1e3;let s=n.velocity||0,a=-this.d*s-this.k*(e-t),o=e+s*r+a*r*r/2;return n.velocity=s+a*r,n.done=Math.abs(t-o)+Math.abs(s)<.002,n.done?t:o}};xe(lu,{duration:Xs("_duration",Q3),overshoot:Xs("_overshoot",Q3)});hu=class extends Qs{constructor(e=.1,t=.01,r=0,n=1e3){super(),this.p(e).i(t).d(r).windup(n)}step(e,t,r,n){if(typeof e=="string")return e;if(n.done=r===1/0,r===1/0)return t;if(r===0)return e;let s=t-e,a=(n.integral||0)+s*r,o=(s-(n.error||0))/r,l=this._windup;return l!==!1&&(a=Math.max(-l,Math.min(a,l))),n.error=s,n.integral=a,n.done=Math.abs(s)<.001,n.done?t:e+(this.P*s+this.I*a+this.D*o)}};xe(hu,{windup:Xs("_windup"),p:Xs("P"),i:Xs("I"),d:Xs("D")});nT={M:2,L:2,H:1,V:1,C:6,S:4,Q:4,T:2,A:7,Z:0},du={M:function(i,e,t){return e.x=t.x=i[0],e.y=t.y=i[1],["M",e.x,e.y]},L:function(i,e){return e.x=i[0],e.y=i[1],["L",i[0],i[1]]},H:function(i,e){return e.x=i[0],["H",i[0]]},V:function(i,e){return e.y=i[0],["V",i[0]]},C:function(i,e){return e.x=i[4],e.y=i[5],["C",i[0],i[1],i[2],i[3],i[4],i[5]]},S:function(i,e){return e.x=i[2],e.y=i[3],["S",i[0],i[1],i[2],i[3]]},Q:function(i,e){return e.x=i[2],e.y=i[3],["Q",i[0],i[1],i[2],i[3]]},T:function(i,e){return e.x=i[0],e.y=i[1],["T",i[0],i[1]]},Z:function(i,e,t){return e.x=t.x,e.y=t.y,["Z"]},A:function(i,e){return e.x=i[5],e.y=i[6],["A",i[0],i[1],i[2],i[3],i[4],i[5],i[6]]}},nu="mlhvqtcsaz".split("");for(let i=0,e=nu.length;i=0;s--)n=this[s][0],n==="M"||n==="L"||n==="T"?(this[s][1]+=e,this[s][2]+=t):n==="H"?this[s][1]+=e:n==="V"?this[s][1]+=t:n==="C"||n==="S"||n==="Q"?(this[s][1]+=e,this[s][2]+=t,this[s][3]+=e,this[s][4]+=t,n==="C"&&(this[s][5]+=e,this[s][6]+=t)):n==="A"&&(this[s][6]+=e,this[s][7]+=t);return this}parse(e="M0 0"){return Array.isArray(e)&&(e=Array.prototype.concat.apply([],e).toString()),hT(e)}size(e,t){let r=this.bbox(),n,s;for(r.width=r.width===0?1:r.width,r.height=r.height===0?1:r.height,n=this.length-1;n>=0;n--)s=this[n][0],s==="M"||s==="L"||s==="T"?(this[n][1]=(this[n][1]-r.x)*e/r.width+r.x,this[n][2]=(this[n][2]-r.y)*t/r.height+r.y):s==="H"?this[n][1]=(this[n][1]-r.x)*e/r.width+r.x:s==="V"?this[n][1]=(this[n][1]-r.y)*t/r.height+r.y:s==="C"||s==="S"||s==="Q"?(this[n][1]=(this[n][1]-r.x)*e/r.width+r.x,this[n][2]=(this[n][2]-r.y)*t/r.height+r.y,this[n][3]=(this[n][3]-r.x)*e/r.width+r.x,this[n][4]=(this[n][4]-r.y)*t/r.height+r.y,s==="C"&&(this[n][5]=(this[n][5]-r.x)*e/r.width+r.x,this[n][6]=(this[n][6]-r.y)*t/r.height+r.y)):s==="A"&&(this[n][1]=this[n][1]*e/r.width,this[n][2]=this[n][2]*t/r.height,this[n][6]=(this[n][6]-r.x)*e/r.width+r.x,this[n][7]=(this[n][7]-r.y)*t/r.height+r.y);return this}toString(){return dT(this)}},y2=i=>{let e=typeof i;return e==="number"?we:e==="string"?Ei.isColor(i)?Ei:Sr.test(i)?xu.test(i)?Er:ln:n2.test(i)?we:Co:Mu.indexOf(i.constructor)>-1?i.constructor:Array.isArray(i)?ln:e==="object"?ss:Co},Tr=class{constructor(e){this._stepper=e||new ko("-"),this._from=null,this._to=null,this._type=null,this._context=null,this._morphObj=null}at(e){return this._morphObj.morph(this._from,this._to,e,this._stepper,this._context)}done(){return this._context.map(this._stepper.done).reduce(function(t,r){return t&&r},!0)}from(e){return e==null?this._from:(this._from=this._set(e),this)}stepper(e){return e==null?this._stepper:(this._stepper=e,this)}to(e){return e==null?this._to:(this._to=this._set(e),this)}type(e){return e==null?this._type:(this._type=e,this)}_set(e){this._type||this.type(y2(e));let t=new this._type(e);return this._type===Ei&&(t=this._to?t[this._to[4]]():this._from?t[this._from[4]]():t),this._type===ss&&(t=this._to?t.align(this._to):this._from?t.align(this._from):t),t=t.toConsumable(),this._morphObj=this._morphObj||new this._type,this._context=this._context||Array.apply(null,Array(t.length)).map(Object).map(function(r){return r.done=!0,r}),t}},Co=class{constructor(...e){this.init(...e)}init(e){return e=Array.isArray(e)?e[0]:e,this.value=e,this}toArray(){return[this.value]}valueOf(){return this.value}},_o=class i{constructor(...e){this.init(...e)}init(e){return Array.isArray(e)&&(e={scaleX:e[0],scaleY:e[1],shear:e[2],rotate:e[3],translateX:e[4],translateY:e[5],originX:e[6],originY:e[7]}),Object.assign(this,i.defaults,e),this}toArray(){let e=this;return[e.scaleX,e.scaleY,e.shear,e.rotate,e.translateX,e.translateY,e.originX,e.originY]}};_o.defaults={scaleX:1,scaleY:1,shear:0,rotate:0,translateX:0,translateY:0,originX:0,originY:0};cT=(i,e)=>i[0]e[0]?1:0,ss=class{constructor(...e){this.init(...e)}align(e){let t=this.values;for(let r=0,n=t.length;rr.concat(n),[]),this}toArray(){return this.values}valueOf(){let e={},t=this.values;for(;t.length;){let r=t.shift(),n=t.shift(),s=t.shift(),a=t.splice(0,s);e[r]=new n(a)}return e}},Mu=[Co,_o,ss];nr=class extends Dt{constructor(e,t=e){super(Ue("path",e),t)}array(){return this._array||(this._array=new Er(this.attr("d")))}clear(){return delete this._array,this}height(e){return e==null?this.bbox().height:this.size(this.bbox().width,e)}move(e,t){return this.attr("d",this.array().move(e,t))}plot(e){return e==null?this.array():this.clear().attr("d",typeof e=="string"?e:this._array=new Er(e))}size(e,t){let r=ia(this,e,t);return this.attr("d",this.array().size(r.width,r.height))}width(e){return e==null?this.bbox().width:this.size(e,this.bbox().height)}x(e){return e==null?this.bbox().x:this.move(e,this.bbox().y)}y(e){return e==null?this.bbox().y:this.move(this.bbox().x,e)}};nr.prototype.MorphArray=Er;fe({Container:{path:He(function(i){return this.put(new nr).plot(i||new Er)})}});Me(nr,"Path");v2={__proto__:null,array:mT,clear:pT,move:gT,plot:xT,size:yT},ji=class extends Dt{constructor(e,t=e){super(Ue("polygon",e),t)}};fe({Container:{polygon:He(function(i){return this.put(new ji).plot(i||new rr)})}});xe(ji,wu);xe(ji,v2);Me(ji,"Polygon");as=class extends Dt{constructor(e,t=e){super(Ue("polyline",e),t)}};fe({Container:{polyline:He(function(i){return this.put(new as).plot(i||new rr)})}});xe(as,wu);xe(as,v2);Me(as,"Polyline");Ve=class extends Dt{constructor(e,t=e){super(Ue("rect",e),t)}};xe(Ve,{rx:vu,ry:bu});fe({Container:{rect:He(function(i,e){return this.put(new Ve).size(i,e)})}});Me(Ve,"Rect");To=class{constructor(){this._first=null,this._last=null}first(){return this._first&&this._first.value}last(){return this._last&&this._last.value}push(e){let t=typeof e.next<"u"?e:{value:e,next:null,prev:null};return this._last?(t.prev=this._last,this._last.next=t,this._last=t):(this._last=t,this._first=t),t}remove(e){e.prev&&(e.prev.next=e.next),e.next&&(e.next.prev=e.prev),e===this._last&&(this._last=e.prev),e===this._first&&(this._first=e.next),e.prev=null,e.next=null}shift(){let e=this._first;return e?(this._first=e.next,this._first&&(this._first.prev=null),this._last=this._first?this._last:null,e.value):null}},Ae={nextDraw:null,frames:new To,timeouts:new To,immediates:new To,timer:()=>Ne.window.performance||Ne.window.Date,transforms:[],frame(i){let e=Ae.frames.push({run:i});return Ae.nextDraw===null&&(Ae.nextDraw=Ne.window.requestAnimationFrame(Ae._draw)),e},timeout(i,e){e=e||0;let t=Ae.timer().now()+e,r=Ae.timeouts.push({run:i,time:t});return Ae.nextDraw===null&&(Ae.nextDraw=Ne.window.requestAnimationFrame(Ae._draw)),r},immediate(i){let e=Ae.immediates.push(i);return Ae.nextDraw===null&&(Ae.nextDraw=Ne.window.requestAnimationFrame(Ae._draw)),e},cancelFrame(i){i!=null&&Ae.frames.remove(i)},clearTimeout(i){i!=null&&Ae.timeouts.remove(i)},cancelImmediate(i){i!=null&&Ae.immediates.remove(i)},_draw(i){let e=null,t=Ae.timeouts.last();for(;(e=Ae.timeouts.shift())&&(i>=e.time?e.run():Ae.timeouts.push(e),e!==t););let r=null,n=Ae.frames.last();for(;r!==n&&(r=Ae.frames.shift());)r.run(i);let s=null;for(;s=Ae.immediates.shift();)s();Ae.nextDraw=Ae.timeouts.first()||Ae.frames.first()?Ne.window.requestAnimationFrame(Ae._draw):null}},vT=function(i){let e=i.start,t=i.runner.duration(),r=e+t;return{start:e,duration:t,end:r,runner:i.runner}},bT=function(){let i=Ne.window;return(i.performance||i.Date).now()},q0=class extends ts{constructor(e=bT){super(),this._timeSource=e,this._startTime=0,this._speed=1,this._persist=0,this._nextFrame=null,this._paused=!0,this._runners=[],this._runnerIds=[],this._lastRunnerId=-1,this._time=0,this._lastSourceTime=0,this._lastStepTime=0,this._step=this._stepFn.bind(this,!1),this._stepImmediate=this._stepFn.bind(this,!0)}active(){return!!this._nextFrame}finish(){return this.time(this.getEndTimeOfTimeline()+1),this.pause()}getEndTime(){let e=this.getLastRunnerInfo(),t=e?e.runner.duration():0;return(e?e.start:this._time)+t}getEndTimeOfTimeline(){let e=this._runners.map(t=>t.start+t.runner.duration());return Math.max(0,...e)}getLastRunnerInfo(){return this.getRunnerInfoById(this._lastRunnerId)}getRunnerInfoById(e){return this._runners[this._runnerIds.indexOf(e)]||null}pause(){return this._paused=!0,this._continue()}persist(e){return e==null?this._persist:(this._persist=e,this)}play(){return this._paused=!1,this.updateTime()._continue()}reverse(e){let t=this.speed();if(e==null)return this.speed(-t);let r=Math.abs(t);return this.speed(e?-r:r)}schedule(e,t,r){if(e==null)return this._runners.map(vT);let n=0,s=this.getEndTime();if(t=t||0,r==null||r==="last"||r==="after")n=s;else if(r==="absolute"||r==="start")n=t,t=0;else if(r==="now")n=this._time;else if(r==="relative"){let l=this.getRunnerInfoById(e.id);l&&(n=l.start+t,t=0)}else if(r==="with-last"){let l=this.getLastRunnerInfo();n=l?l.start:this._time}else throw new Error('Invalid value for the "when" parameter');e.unschedule(),e.timeline(this);let a=e.persist(),o={persist:a===null?this._persist:a,start:n+t,runner:e};return this._lastRunnerId=e.id,this._runners.push(o),this._runners.sort((l,h)=>l.start-h.start),this._runnerIds=this._runners.map(l=>l.runner.id),this.updateTime()._continue(),this}seek(e){return this.time(this._time+e)}source(e){return e==null?this._timeSource:(this._timeSource=e,this)}speed(e){return e==null?this._speed:(this._speed=e,this)}stop(){return this.time(0),this.pause()}time(e){return e==null?this._time:(this._time=e,this._continue(!0))}unschedule(e){let t=this._runnerIds.indexOf(e.id);return t<0?this:(this._runners.splice(t,1),this._runnerIds.splice(t,1),e.timeline(null),this)}updateTime(){return this.active()||(this._lastSourceTime=this._timeSource()),this}_continue(e=!1){return Ae.cancelFrame(this._nextFrame),this._nextFrame=null,e?this._stepImmediate():this._paused?this:(this._nextFrame=Ae.frame(this._step),this)}_stepFn(e=!1){let t=this._timeSource(),r=t-this._lastSourceTime;e&&(r=0);let n=this._speed*r+(this._time-this._lastStepTime);this._lastSourceTime=t,e||(this._time+=n,this._time=this._time<0?0:this._time),this._lastStepTime=this._time,this.fire("time",this._time);for(let a=this._runners.length;a--;){let o=this._runners[a],l=o.runner;this._time-o.start<=0&&l.reset()}let s=!1;for(let a=0,o=this._runners.length;a0?this._continue():(this.pause(),this.fire("finished")),this}};fe({Element:{timeline:function(i){return i==null?(this._timeline=this._timeline||new q0,this._timeline):(this._timeline=i,this)}}});Gi=class i extends ts{constructor(e){super(),this.id=i.id++,e=e??Mo.duration,e=typeof e=="function"?new Qs(e):e,this._element=null,this._timeline=null,this.done=!1,this._queue=[],this._duration=typeof e=="number"&&e,this._isDeclarative=e instanceof Qs,this._stepper=this._isDeclarative?e:new ko,this._history={},this.enabled=!0,this._time=0,this._lastTime=0,this._reseted=!0,this.transforms=new be,this.transformId=1,this._haveReversed=!1,this._reverse=!1,this._loopsDone=0,this._swing=!1,this._wait=0,this._times=1,this._frameId=null,this._persist=this._isDeclarative?!0:null}static sanitise(e,t,r){let n=1,s=!1,a=0;return e=e||Mo.duration,t=t||Mo.delay,r=r||"last",typeof e=="object"&&!(e instanceof Ao)&&(t=e.delay||t,r=e.when||r,s=e.swing||s,n=e.times||n,a=e.wait||a,e=e.duration||Mo.duration),{duration:e,delay:t,swing:s,times:n,wait:a,when:r}}active(e){return e==null?this.enabled:(this.enabled=e,this)}addTransform(e,t){return this.transforms.lmultiplyO(e),this}after(e){return this.on("finished",e)}animate(e,t,r){let n=i.sanitise(e,t,r),s=new i(n.duration);return this._timeline&&s.timeline(this._timeline),this._element&&s.element(this._element),s.loop(n).schedule(n.delay,n.when)}clearTransform(){return this.transforms=new be,this}clearTransformsFromQueue(){(!this.done||!this._timeline||!this._timeline._runnerIds.includes(this.id))&&(this._queue=this._queue.filter(e=>!e.isTransform))}delay(e){return this.animate(0,e)}duration(){return this._times*(this._wait+this._duration)-this._wait}during(e){return this.queue(null,e)}ease(e){return this._stepper=new ko(e),this}element(e){return e==null?this._element:(this._element=e,e._prepareRunner(),this)}finish(){return this.step(1/0)}loop(e,t,r){return typeof e=="object"&&(t=e.swing,r=e.wait,e=e.times),this._times=e||1/0,this._swing=t||!1,this._wait=r||0,this._times===!0&&(this._times=1/0),this}loops(e){let t=this._duration+this._wait;if(e==null){let a=Math.floor(this._time/t),l=(this._time-a*t)/this._duration;return Math.min(a+l,this._times)}let r=Math.floor(e),n=e%1,s=t*r+this._duration*n;return this.time(s)}persist(e){return e==null?this._persist:(this._persist=e,this)}position(e){let t=this._time,r=this._duration,n=this._wait,s=this._times,a=this._swing,o=this._reverse,l;if(e==null){let f=function(g){let x=a*Math.floor(g%(2*(n+r))/(n+r)),y=x&&!o||!x&&o,b=Math.pow(-1,y)*(g%(n+r))/r+y;return Math.max(Math.min(b,1),0)},m=s*(n+r)-n;return l=t<=0?Math.round(f(1e-5)):t=0;this._lastPosition=t;let n=this.duration(),s=this._lastTime<=0&&this._time>0,a=this._lastTime=n;this._lastTime=this._time,s&&this.fire("start",this);let o=this._isDeclarative;this.done=!o&&!a&&this._time>=n,this._reseted=!1;let l=!1;return(r||o)&&(this._initialise(r),this.transforms=new be,l=this._run(o?e:t),this.fire("step",this)),this.done=this.done||l&&o,a&&this.fire("finished",this),this}time(e){if(e==null)return this._time;let t=e-this._time;return this.step(t),this}timeline(e){return typeof e>"u"?this._timeline:(this._timeline=e,this)}unschedule(){let e=this.timeline();return e&&e.unschedule(this),this}_initialise(e){if(!(!e&&!this._isDeclarative))for(let t=0,r=this._queue.length;ti.lmultiplyO(e),w2=i=>i.transforms;fu=class{constructor(){this.runners=[],this.ids=[]}add(e){if(this.runners.includes(e))return;let t=e.id+1;return this.runners.push(e),this.ids.push(t),this}clearBefore(e){let t=this.ids.indexOf(e+1)||1;return this.ids.splice(0,t,0),this.runners.splice(0,t,new Js).forEach(r=>r.clearTransformsFromQueue()),this}edit(e,t){let r=this.ids.indexOf(e+1);return this.ids.splice(r,1,e+1),this.runners.splice(r,1,t),this}getByID(e){return this.runners[this.ids.indexOf(e+1)]}length(){return this.ids.length}merge(){let e=null;for(let t=0;te.id<=i.id).map(w2).reduce(b2,new be)},_addRunner(i){this._transformationRunners.add(i),Ae.cancelImmediate(this._frameId),this._frameId=Ae.immediate(wT.bind(this))},_prepareRunner(){this._frameId==null&&(this._transformationRunners=new fu().add(new Js(new be(this))))}}});MT=(i,e)=>i.filter(t=>!e.includes(t));xe(Gi,{attr(i,e){return this.styleAttr("attr",i,e)},css(i,e){return this.styleAttr("css",i,e)},styleAttr(i,e,t){if(typeof e=="string")return this.styleAttr(i,{[e]:t});let r=e;if(this._tryRetarget(i,r))return this;let n=new Tr(this._stepper).to(r),s=Object.keys(r);return this.queue(function(){n=n.from(this.element()[i](s))},function(a){return this.element()[i](n.at(a).valueOf()),n.done()},function(a){let o=Object.keys(a),l=MT(o,s);if(l.length){let d=this.element()[i](l),c=new ss(n.from()).valueOf();Object.assign(c,d),n.from(c)}let h=new ss(n.to()).valueOf();Object.assign(h,a),n.to(h),s=o,r=a}),this._rememberMorpher(i,n),this},zoom(i,e){if(this._tryRetarget("zoom",i,e))return this;let t=new Tr(this._stepper).to(new we(i));return this.queue(function(){t=t.from(this.element().zoom())},function(r){return this.element().zoom(t.at(r),e),t.done()},function(r,n){e=n,t.to(r)}),this._rememberMorpher("zoom",t),this},transform(i,e,t){if(e=i.relative||e,this._isDeclarative&&!e&&this._tryRetarget("transform",i))return this;let r=be.isMatrixLike(i);t=i.affine!=null?i.affine:t??!r;let n=new Tr(this._stepper).type(t?_o:be),s,a,o,l,h;function d(){a=a||this.element(),s=s||au(i,a),h=new be(e?void 0:a),a._addRunner(this),e||a._clearTransformRunnersBefore(this)}function c(m){e||this.clearTransform();let{x:g,y:x}=new it(s).transform(a._currentTransform(this)),y=new be({...i,origin:[g,x]}),b=this._isDeclarative&&o?o:h;if(t){y=y.decompose(g,x),b=b.decompose(g,x);let k=y.rotate,L=b.rotate,I=[k-360,k,k+360],O=I.map(W=>Math.abs(W-L)),q=Math.min(...O),F=O.indexOf(q);y.rotate=I[F]}e&&(r||(y.rotate=i.rotate||0),this._isDeclarative&&l&&(b.rotate=l)),n.from(b),n.to(y);let E=n.at(m);return l=E.rotate,o=new be(E),this.addTransform(o),a._addRunner(this),n.done()}function f(m){(m.origin||"center").toString()!==(i.origin||"center").toString()&&(s=au(m,a)),i={...m,origin:s}}return this.queue(d,c,f,!0),this._isDeclarative&&this._rememberMorpher("transform",n),this},x(i,e){return this._queueNumber("x",i)},y(i){return this._queueNumber("y",i)},dx(i=0){return this._queueNumberDelta("x",i)},dy(i=0){return this._queueNumberDelta("y",i)},dmove(i,e){return this.dx(i).dy(e)},_queueNumberDelta(i,e){if(e=new we(e),this._tryRetarget(i,e))return this;let t=new Tr(this._stepper).to(e),r=null;return this.queue(function(){r=this.element()[i](),t.from(r),t.to(r+e)},function(n){return this.element()[i](t.at(n)),t.done()},function(n){t.to(r+new we(n))}),this._rememberMorpher(i,t),this},_queueObject(i,e){if(this._tryRetarget(i,e))return this;let t=new Tr(this._stepper).to(e);return this.queue(function(){t.from(this.element()[i]())},function(r){return this.element()[i](t.at(r)),t.done()}),this._rememberMorpher(i,t),this},_queueNumber(i,e){return this._queueObject(i,new we(e))},cx(i){return this._queueNumber("cx",i)},cy(i){return this._queueNumber("cy",i)},move(i,e){return this.x(i).y(e)},center(i,e){return this.cx(i).cy(e)},size(i,e){let t;return(!i||!e)&&(t=this._element.bbox()),i||(i=t.width/t.height*e),e||(e=t.height/t.width*i),this.width(i).height(e)},width(i){return this._queueNumber("width",i)},height(i){return this._queueNumber("height",i)},plot(i,e,t,r){if(arguments.length===4)return this.plot([i,e,t,r]);if(this._tryRetarget("plot",i))return this;let n=new Tr(this._stepper).type(this._element.MorphArray).to(i);return this.queue(function(){n.from(this._element.array())},function(s){return this._element.plot(n.at(s)),n.done()}),this._rememberMorpher("plot",n),this},leading(i){return this._queueNumber("leading",i)},viewbox(i,e,t,r){return this._queueObject("viewbox",new Vt(i,e,t,r))},update(i){return typeof i!="object"?this.update({offset:arguments[0],color:arguments[1],opacity:arguments[2]}):(i.opacity!=null&&this.attr("stop-opacity",i.opacity),i.color!=null&&this.attr("stop-color",i.color),i.offset!=null&&this.attr("offset",i.offset),this)}});xe(Gi,{rx:vu,ry:bu,from:g2,to:x2});Me(Gi,"Runner");Lo=class extends Yt{constructor(e,t=e){super(Ue("svg",e),t),this.namespace()}defs(){return this.isRoot()?Ni(this.node.querySelector("defs"))||this.put(new So):this.root().defs()}isRoot(){return!this.node.parentNode||!(this.node.parentNode instanceof Ne.window.SVGElement)&&this.node.parentNode.nodeName!=="#document-fragment"}namespace(){return this.isRoot()?this.attr({xmlns:pu,version:"1.1"}).attr("xmlns:xlink",Bo,P0).attr("xmlns:svgjs",jw,P0):this.root().namespace()}removeNamespace(){return this.attr({xmlns:null,version:null}).attr("xmlns:xlink",null,P0).attr("xmlns:svgjs",null,P0)}root(){return this.isRoot()?this:super.root()}};fe({Container:{nested:He(function(){return this.put(new Lo)})}});Me(Lo,"Svg",!0);Io=class extends Yt{constructor(e,t=e){super(Ue("symbol",e),t)}};fe({Container:{symbol:He(function(){return this.put(new Io)})}});Me(Io,"Symbol");M2={__proto__:null,plain:TT,length:NT,x:ET,y:ST,move:AT,cx:kT,cy:CT,center:_T,ax:LT,ay:IT,amove:zT,build:RT},_e=class extends Dt{constructor(e,t=e){super(Ue("text",e),t),this.dom.leading=new we(1.3),this._rebuild=!0,this._build=!1}leading(e){return e==null?this.dom.leading:(this.dom.leading=new we(e),this.rebuild())}rebuild(e){if(typeof e=="boolean"&&(this._rebuild=e),this._rebuild){let t=this,r=0,n=this.dom.leading;this.each(function(s){let a=Ne.window.getComputedStyle(this.node).getPropertyValue("font-size"),o=n*new we(a);this.dom.newLined&&(this.attr("x",t.attr("x")),this.text()===` +`?r+=o:(this.attr("dy",s?o+r:0),r=0))}),this.fire("rebuild")}return this}setData(e){return this.dom=e,this.dom.leading=new we(e.leading||1.3),this}text(e){if(e===void 0){let t=this.node.childNodes,r=0;e="";for(let n=0,s=t.length;n(i.attr("href")||"").includes(this.id()))}}});_o.prototype.MorphArray=Er;be(_o,"TextPath");R0=class extends Dt{constructor(e,t=e){super(qe("use",e),t)}use(e,t){return this.attr("href",(t||"")+"#"+e,Lo)}};fe({Container:{use:Fe(function(i,e){return this.put(new R0).use(i,e)})}});be(R0,"Use");Ae=Rt;ge([Eo,So,ji,rs,Ks],Wt("viewbox"));ge([ns,as,Gi,ar],Wt("marker"));ge(Ce,Wt("Text"));ge(ar,Wt("Path"));ge(bo,Wt("Defs"));ge([Ce,Js],Wt("Tspan"));ge([Ve,Xs,is,Vi],Wt("radius"));ge(ts,Wt("EventTarget"));ge(dn,Wt("Dom"));ge(Ei,Wt("Element"));ge(Dt,Wt("Shape"));ge([Vt,L0],Wt("Container"));ge(is,Wt("Gradient"));ge(Vi,Wt("Runner"));Nr.extend(gw());UM([ye,Ni,Gt,xe,hn,sr,Er,it]);$M()});var O0=rr((nU,c2)=>{"use strict";var D0=function(e){return wT(e)&&!MT(e)};function wT(i){return!!i&&typeof i=="object"}function MT(i){var e=Object.prototype.toString.call(i);return e==="[object RegExp]"||e==="[object Date]"||ET(i)}var TT=typeof Symbol=="function"&&Symbol.for,NT=TT?Symbol.for("react.element"):60103;function ET(i){return i.$$typeof===NT}function ST(i){return Array.isArray(i)?[]:{}}function Io(i,e){var t=e&&e.clone===!0;return t&&D0(i)?ra(ST(i),i,e):i}function d2(i,e,t){var r=i.slice();return e.forEach(function(n,s){typeof r[s]>"u"?r[s]=Io(n,t):D0(n)?r[s]=ra(i[s],n,t):i.indexOf(n)===-1&&r.push(Io(n,t))}),r}function AT(i,e,t){var r={};return D0(i)&&Object.keys(i).forEach(function(n){r[n]=Io(i[n],t)}),Object.keys(e).forEach(function(n){!D0(e[n])||!i[n]?r[n]=Io(e[n],t):r[n]=ra(i[n],e[n],t)}),r}function ra(i,e,t){var r=Array.isArray(e),n=Array.isArray(i),s=t||{arrayMerge:d2},a=r===n;if(a)if(r){var o=s.arrayMerge||d2;return o(i,e,t)}else return AT(i,e,t);else return Io(e,t)}ra.all=function(e,t){if(!Array.isArray(e)||e.length<2)throw new Error("first argument should be an array with at least two elements");return e.reduce(function(r,n){return ra(r,n,t)})};var kT=ra;c2.exports=kT});var B0,CT,u2,zo,Ro=T(()=>{B0={paddingX:15,paddingY:5,imgMaxWidth:200,imgMaxHeight:100,iconSize:20,lineWidth:1,lineColor:"#549688",lineDasharray:"none",lineFlow:!1,lineFlowDuration:1,lineFlowForward:!0,lineStyle:"straight",rootLineKeepSameInCurve:!0,rootLineStartPositionKeepSameInCurve:!1,lineRadius:5,showLineMarker:!1,generalizationLineWidth:1,generalizationLineColor:"#549688",generalizationLineMargin:0,generalizationNodeMargin:20,associativeLineWidth:2,associativeLineColor:"rgb(51, 51, 51)",associativeLineActiveWidth:8,associativeLineActiveColor:"rgba(2, 167, 240, 1)",associativeLineDasharray:"6,4",associativeLineTextColor:"rgb(51, 51, 51)",associativeLineTextFontSize:14,associativeLineTextLineHeight:1.2,associativeLineTextFontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",backgroundColor:"#fafafa",backgroundImage:"none",backgroundRepeat:"no-repeat",backgroundPosition:"center center",backgroundSize:"cover",nodeUseLineStyle:!1,root:{shape:"rectangle",fillColor:"#549688",fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",color:"#fff",fontSize:16,fontWeight:"bold",fontStyle:"normal",borderColor:"transparent",borderWidth:0,borderDasharray:"none",borderRadius:5,textDecoration:"none",gradientStyle:!1,startColor:"#549688",endColor:"#fff",startDir:[0,0],endDir:[1,0],lineMarkerDir:"end",hoverRectColor:"",hoverRectRadius:5,textAlign:"left",imgPlacement:"top",tagPlacement:"right"},second:{shape:"rectangle",marginX:100,marginY:40,fillColor:"#fff",fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",color:"#565656",fontSize:16,fontWeight:"normal",fontStyle:"normal",borderColor:"#549688",borderWidth:1,borderDasharray:"none",borderRadius:5,textDecoration:"none",gradientStyle:!1,startColor:"#549688",endColor:"#fff",startDir:[0,0],endDir:[1,0],lineMarkerDir:"end",hoverRectColor:"",hoverRectRadius:5,textAlign:"left",imgPlacement:"top",tagPlacement:"right"},node:{shape:"rectangle",marginX:50,marginY:0,fillColor:"transparent",fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",color:"#6a6d6c",fontSize:14,fontWeight:"normal",fontStyle:"normal",borderColor:"transparent",borderWidth:0,borderRadius:5,borderDasharray:"none",textDecoration:"none",gradientStyle:!1,startColor:"#549688",endColor:"#fff",startDir:[0,0],endDir:[1,0],lineMarkerDir:"end",hoverRectColor:"",hoverRectRadius:5,textAlign:"left",imgPlacement:"top",tagPlacement:"right"},generalization:{shape:"rectangle",marginX:100,marginY:40,fillColor:"#fff",fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",color:"#565656",fontSize:16,fontWeight:"normal",fontStyle:"normal",borderColor:"#549688",borderWidth:1,borderDasharray:"none",borderRadius:5,textDecoration:"none",gradientStyle:!1,startColor:"#549688",endColor:"#fff",startDir:[0,0],endDir:[1,0],hoverRectColor:"",hoverRectRadius:5,textAlign:"left",imgPlacement:"top",tagPlacement:"right"}},CT=["lineWidth","lineColor","lineDasharray","lineStyle","generalizationLineWidth","generalizationLineColor","associativeLineWidth","associativeLineColor","associativeLineActiveWidth","associativeLineActiveColor","associativeLineTextColor","associativeLineTextFontSize","associativeLineTextLineHeight","associativeLineTextFontFamily","backgroundColor","backgroundImage","backgroundRepeat","backgroundPosition","backgroundSize","rootLineKeepSameInCurve","rootLineStartPositionKeepSameInCurve","showLineMarker","lineRadius","hoverRectColor","hoverRectRadius","lineFlow","lineFlowDuration","lineFlowForward","textAlign"],u2=i=>{let e=Object.keys(i);for(let t=0;tr===e[t]))return!1;return!0},zo=["lineColor","lineDasharray","lineWidth","lineMarkerDir","lineFlow","lineFlowDuration","lineFlowForward"]});var f2,de,Xt,U0,na,Kt,uu,ls,fu,m2,Si,p2,Zt,fn,g2,x2,y2,P0,sa,aa,_T,wt,v2,b2,os,Yt,F0,w2,Do,M2,Oo,mu,$0,LT,q0,j0,un,T2,N2,hs,E2,S2,ds,A2,k2,oa,la,Bo,cs,Ot,ha,Ie,G0,mn,H0,pu,gu,C2,V0,_2,L2,I2,xu,Po,yu,vu,z2,R2,bu,D2,IT,cU,pn,us,or,Fo,wu,O2,pe=T(()=>{Hc();$e();I3();yt();f2=pt(O0());Ro();de=(i,e,t,r,n,s=0,a=0,o=[])=>{let l=!1;if(t&&(l=t(i,e,n,s,a,o)),!l&&i.children&&i.children.length>0){let h=s+1;i.children.forEach((d,c)=>{de(d,i,t,r,!1,h,c,[...o,i])})}r&&r(i,e,n,s,a,o)},Xt=(i,e)=>{let t=[i],r=!1;for(e(i,null)==="stop"&&(r=!0);t.length&&!r;){let n=t.shift();n.children&&n.children.length&&n.children.forEach(s=>{r||(t.push(s),e(s,n)==="stop"&&(r=!0))})}},U0=(i,e,t,r)=>{let n=i/e,s=[];if(t&&r)if(i<=t&&e<=r)s=[i,e];else{let a=t/r;n>a?s=[t,t/n]:s=[n*r,r]}else t?i<=t?s=[i,e]:s=[t,t/n]:r&&(e<=r?s=[i,e]:s=[n*r,r]);return s},na=i=>{i=i.replace(/
    /gim,` -`);let e=document.createElement("div");return e.innerHTML=i,i=e.textContent,i},Kt=i=>{try{return JSON.parse(JSON.stringify(i))}catch{return null}},uu=(i,e,t=!1)=>(i.data=Kt(e.data),t&&(i.data.isActive=!1,us(i.data).forEach(n=>{n.isActive=!1})),i.children=[],e.children&&e.children.length>0&&e.children.forEach((r,n)=>{i.children[n]=uu({},r,t)}),Object.keys(e).forEach(r=>{!["data","children"].includes(r)&&!/^_/.test(r)&&(i[r]=e[r])}),i),ls=(i,e,t=!1,r=!0)=>{let n=e.nodeData?e.nodeData:e;return i.data=Kt(n.data),r?delete i.data.uid:i.data.uid||(i.data.uid=wt()),t&&(i.data.isActive=!1),i.children=[],e.children&&e.children.length>0?e.children.forEach((s,a)=>{i.children[a]=ls({},s,t,r)}):e.nodeData&&e.nodeData.children&&e.nodeData.children.length>0&&e.nodeData.children.forEach((s,a)=>{i.children[a]=ls({},s,t,r)}),Object.keys(n).forEach(s=>{!["data","children"].includes(s)&&!/^_/.test(s)&&(i[s]=n[s])}),i},fu=(i,e=!1)=>new Promise((t,r)=>{let n=new Image;n.setAttribute("crossOrigin","anonymous"),n.onload=()=>{try{let s=document.createElement("canvas");s.width=n.width,s.height=n.height,s.getContext("2d").drawImage(n,0,0,n.width,n.height),e?s.toBlob(o=>{t(o)}):t(s.toDataURL())}catch(s){r(s)}},n.onerror=s=>{r(s)},n.src=i}),m2=(i,e)=>{let t=document.createElement("a");t.href=i,t.download=e,t.click()},Si=(i,e=300,t)=>{let r=null;return(...n)=>{r||(r=setTimeout(()=>{i.call(t,...n),r=null},e))}},p2=(i,e=300,t)=>{let r=null;return(...n)=>{r&&clearTimeout(r),r=setTimeout(()=>{r=null,i.apply(t,n)},e)}},Zt=(i,e=()=>{})=>{let t=0,r=i.length;if(r<=0)return e();let n=()=>{if(t>=r){e();return}i[t](),setTimeout(()=>{t++,n()},0)};n()},fn=i=>i*(Math.PI/180),g2=i=>i.replace(/([a-z])([A-Z])/g,(...e)=>e[1]+"-"+e[2].toLowerCase()),x2=function(i,e){let t=!1,r=null,n=()=>{t=!1,e?i.call(e):i()};if(typeof MutationObserver<"u"){let s=1,a=new MutationObserver(n),o=document.createTextNode(s);a.observe(o,{characterData:!0}),r=function(){s=(s+1)%2,o.data=s}}else r=setTimeout;return function(){t||(t=!0,r(n,0))}},y2=(i,e,t=0,r=0)=>{let n=i.elRect,{scaleX:s,scaleY:a,translateX:o,translateY:l}=i.draw.transform(),{left:h,top:d,width:c,height:f}=e,m=(h+c)*s+o,g=(d+f)*a+l;h=h*s+o,d=d*a+l;let x=0,y=0;return h<0+t&&(x=-h+t),m>n.width-t&&(x=-(m-n.width)-t),d<0+r&&(y=-d+r),g>n.height-r&&(y=-(g-n.height)-r),{isOuter:x!==0||y!==0,offsetLeft:x,offsetTop:y}},P0=null,sa=i=>(P0||(P0=document.createElement("div")),P0.innerHTML=i,P0.textContent),aa=i=>new Promise((e,t)=>{let r=new FileReader;r.onload=n=>{e(n.target.result)},r.onerror=n=>{t(n)},r.readAsDataURL(i)}),_T=i=>new Promise(e=>{let t=new Image;t.src=i,t.onload=()=>{e({width:t.width,height:t.height})},t.onerror=()=>{e({width:0,height:0})}}),wt=()=>fo(),v2=i=>new Promise((e,t)=>{let r=new FileReader;r.readAsDataURL(i),r.onload=async n=>{let s=n.target.result,a=await _T(s);e({url:s,size:a})},r.onerror=n=>{t(n)}}),b2=i=>([[" "," "]].forEach(e=>{i=i.replace(new RegExp(e[0],"g"),e[1])}),i),os=i=>Object.prototype.toString.call(i).slice(8,-1),Yt=i=>i==null||i==="",F0=null,w2=i=>{F0||(F0=document.createElement("div")),F0.innerHTML=i;for(let e=F0.childNodes,t=e.length;t--;)if(e[t].nodeType==1)return!0;return!1},Do=null,M2=(i,e,t)=>{Do||(Do=document.createElement("div")),Do.innerHTML=i;let r=n=>{n.childNodes.forEach(a=>{a.nodeType===1?r(a):a.nodeType===3&&n.replaceChild(document.createTextNode(a.nodeValue.replace(new RegExp(e,"g"),t)),a)})};return r(Do),Do.innerHTML},Oo=i=>(i=String(i).replace(/\s+/g,""),["#fff","#ffffff","#FFF","#FFFFFF","rgb(255,255,255)"].includes(i)||/rgba\(255,255,255,[^)]+\)/.test(i)),mu=i=>(i=String(i).replace(/\s+/g,""),["","transparent"].includes(i)||/rgba\(\d+,\d+,\d+,0\)/.test(i)),$0=i=>{let{lineColor:e,root:t,second:r,node:n}=i,s=[e,t.fillColor,t.color,r.fillColor,r.color,n.fillColor,n.color,t.borderColor,r.borderColor,n.borderColor];for(let a=0;a{let e=t=>{t.childNodes.forEach(n=>{n.nodeType===1&&(n.classList.contains("ql-formula")?n.parentNode.removeChild(n):e(n))})};e(i)},q0=null,j0=i=>{q0||(q0=document.createElement("div")),q0.innerHTML=i;let e=q0.childNodes,t="";for(let r=0;r{un||(un=document.createElement("div")),un.innerHTML=i;let e=un.querySelectorAll(".ql-formula");Array.from(e).forEach(n=>{let s=document.createTextNode("$smmformula$");n.parentNode.replaceChild(s,n)});let t=un.childNodes,r=[];for(let n=0;n`

    ${mn(n)}

    `).join(""),e.length>0){i=i.replace(/\$smmformula\$/g,''),un.innerHTML=i;let n=un.querySelectorAll(".smmformula");Array.from(n).forEach((s,a)=>{s.parentNode.replaceChild(e[a],s)}),i=un.innerHTML}return i},N2=(i,e)=>{let t={};return Object.keys(e).forEach(r=>{let n=i[r],s=e[r];if(os(n)!==os(s)){t[r]=s;return}if(os(n)==="Object"){if(JSON.stringify(n)!==JSON.stringify(s)){t[r]=s;return}}else if(n!==s){t[r]=s;return}}),t},hs=i=>/^_/.test(i)?!1:!$s.includes(i),E2=i=>{let e=[...zo],t=Object.keys(i);for(let r=0;ri.reduce((e,t)=>{let r=e.find(n=>n.type===t.type);return r?t.list.forEach(n=>{let s=r.list.find(a=>a.name===n.name);s?s.icon=n.icon:r.list.push(n)}):e.push({...t}),e},[]),ds=i=>{let e=[];return i.forEach(t=>{i.find(r=>r.uid!==t.uid&&r.isAncestor(t))||e.push(t)}),e},A2=i=>{let e={},t={};i.forEach(n=>{let s=n.parent;if(s){let a=s.uid;t[a]=s;let o=n.getIndexInBrothers(),l={node:n,index:o};e[a]?e[a].find(h=>h.index===l.index)||e[a].push(l):e[a]=[l]}});let r=[];return Object.keys(e).forEach(n=>{if(e[n].length>1){let s=e[n].map(a=>a.index).sort((a,o)=>a-o);r.push({node:t[n],range:[s[0],s[s.length-1]]})}else r.push({node:e[n][0].node})}),r},k2=(i,e,t,r,n,s,a,o)=>e>n&&s>i&&r>a&&o>t,oa=i=>{let e=window.getSelection(),t=document.createRange();t.selectNodeContents(i),t.collapse(),e.removeAllRanges(),e.addRange(t)},la=i=>{let e=window.getSelection(),t=document.createRange();t.selectNodeContents(i),e.removeAllRanges(),e.addRange(t)},Bo=(i,e={})=>{e={...e},e&&e.richText&&e.resetRichText&&delete e.resetRichText;let r=n=>{n.forEach(s=>{s.data={...s.data,...e},s.children&&s.children.length>0&&r(s.children)})};return r(i),i},cs=(i,e=!1,t=null,r=!1)=>{let n=s=>{s.forEach(a=>{a.data||(a.data={}),(e||Yt(a.data.uid))&&(a.data.uid=wt()),r&&us(a.data).forEach(l=>{(e||Yt(l.uid))&&(l.uid=wt())}),t&&t(a),a.children&&a.children.length>0&&n(a.children)})};return n(i),i},Ot=i=>i?Array.isArray(i)?i:[i]:[],ha=i=>i.parent?i.parent.nodeData.children.findIndex(e=>e.data.uid===i.uid):0,Ie=(i,e)=>e.findIndex(t=>t.uid===i.uid),G0=i=>{let e=0;for(let n=0;n([["&","&"],["<","<"],[">",">"]].forEach(e=>{i=i.replace(new RegExp(e[0],"g"),e[1])}),i),H0=(i,e)=>{let t=os(i);if(t!==os(e))return!1;if(t==="Object"){let r=Object.keys(i),n=Object.keys(e);if(r.length!==n.length)return!1;for(let s=0;snavigator.clipboard&&typeof navigator.clipboard.read=="function",gu=i=>{navigator.clipboard&&navigator.clipboard.writeText&&navigator.clipboard.writeText(JSON.stringify(i))},C2=async()=>{let i=null,e=null;if(pu()){let t=await navigator.clipboard.read();if(t&&t.length>0)for(let r of t)for(let n of r.types)/^image\//.test(n)?e=await r.getType(n):n==="text/plain"&&(i=await(await r.getType(n)).text())}return{text:i,img:e}},V0=i=>{if(!i||!i.parent)return;let e=ha(i);e!==-1&&i.parent.nodeData.children.splice(e,1)},_2=i=>(_3.forEach(e=>{i=i.replace(new RegExp(`<${e}([^>]*)>`,"g"),`<${e} $1 />`)}),i),L2=(i,e)=>{if(i.length!==e.length)return!1;for(let t=0;tr.uid===i[t].uid))return!1;return!0},I2=()=>{let i=navigator.userAgent.match(/\s+Chrome\/(.*)\s+/);return i&&i[1]?Number.parseFloat(i[1]):""},xu=i=>({simpleMindMap:!0,data:i}),Po=i=>{let e=null;if(typeof i=="string")try{let r=JSON.parse(i);typeof r=="object"&&r.simpleMindMap&&(e=r.data)}catch{}else typeof i=="object"&&i.simpleMindMap&&(e=i.data);let t=!!e;return{isSmm:t,data:t?e:String(i)}},yu=(i,e)=>{i.preventDefault();let t=window.getSelection();if(!t.rangeCount)return;t.deleteFromDocument(),e=e||i.clipboardData.getData("text"),e=mn(e),e=sa(e);let r=e.split(/\n/g),n=document.createDocumentFragment();r.forEach((s,a)=>{let o=document.createTextNode(s);if(n.appendChild(o),a{let e={},t=(r,n)=>{let s=r.data.uid;n&&n.children.push(s),e[s]={isRoot:!n,data:{...r.data},children:[]},r.children&&r.children.length>0&&r.children.forEach(a=>{t(a,e[s])})};return t(i,null),e},z2=(i,e)=>{let t=i.x+i.width/2,r=i.y+i.height/2,n=e.x+e.width/2,s=e.y+e.height/2;return tn&&rn&&r>s?"right-bottom":ts?"left-bottom":tn&&r===s?"right":t===n&&rs?"bottom":"overlap"},R2=({addContentToHeader:i,addContentToFooter:e})=>{let t=[],r=null,n=0,s=null,a=0,o=(l,h)=>{if(typeof l=="function"){let d=l();if(!d)return;let{el:c,cssText:f,height:m}=d;if(c instanceof HTMLElement){or(c);let g=pn({el:c,height:m});h(g,m)}f&&t.push(f)}};return o(i,(l,h)=>{r=l,n=h}),o(e,(l,h)=>{s=l,a=h}),{cssTextList:t,header:r,headerHeight:n,footer:s,footerHeight:a}},bu=(i,e=0,t=0,r=0,n=0,s=!1,a=!1)=>{let o=1/0,l=-1/0,h=1/0,d=-1/0,c=(f,m)=>{if(!(m&&s)&&f.group)try{let{x:g,y:x,width:y,height:b}=f.group.findOne(".smm-node-shape").rbox();gl&&(l=g+y),xd&&(d=x+b)}catch{}!a&&f._generalizationList.length>0&&f._generalizationList.forEach(g=>{c(g.generalizationNode)}),f.children&&f.children.forEach(g=>{c(g)})};return c(i,!0),o=o-e+r,h=h-t+n,l=l-e+r,d=d-t+n,{left:o,top:h,width:l-o,height:d-h}},D2=(i,e=0,t=0,r=0,n=0)=>{let s=1/0,a=-1/0,o=1/0,l=-1/0;return i.forEach(h=>{let{left:d,top:c,width:f,height:m}=bu(h,e,t,r,n,!1,!0);da&&(a=d+f),cl&&(l=c+m)}),{left:s,top:o,width:a-s,height:l-o}},IT=()=>{if(document.documentElement.requestFullScreen)return"fullscreenchange";if(document.documentElement.webkitRequestFullScreen)return"webkitfullscreenchange";if(document.documentElement.mozRequestFullScreen)return"mozfullscreenchange";if(document.documentElement.msRequestFullscreen)return"msfullscreenchange"},cU=IT(),pn=({el:i,width:e,height:t})=>{let r=new ea;return e!==void 0&&r.width(e),t!==void 0&&r.height(t),r.add(i),r},us=i=>{let e=i.generalization;return e?Array.isArray(e)?e:[e]:[]},or=i=>{i.setAttribute("xmlns","http://www.w3.org/1999/xhtml")},Fo=i=>(i=[...i],i.sort((e,t)=>e.sortIndex-t.sortIndex),i),wu=(i,e)=>(0,f2.default)(i,e,{arrayMerge:(t,r)=>r}),O2=i=>{let e={};return L3.forEach(t=>{let r=i.style.merge(t);t==="fontSize"&&(r=r+"px"),e[t]=r}),e}});var B2,Mu,W0,qo,Y0=T(()=>{pe();B2=["backgroundColor","backgroundImage","backgroundRepeat","backgroundPosition","backgroundSize"],Mu=["gradientStyle","startColor","endColor","startDir","endDir","fillColor","borderColor","borderWidth","borderDasharray"],W0=class i{static setBackgroundStyle(e,t){if(!e)return;if(!i.cacheStyle){i.cacheStyle={};let l=window.getComputedStyle(e);B2.forEach(h=>{i.cacheStyle[h]=l[h]})}let{backgroundColor:r,backgroundImage:n,backgroundRepeat:s,backgroundPosition:a,backgroundSize:o}=t;e.style.backgroundColor=r,n&&n!=="none"?(e.style.backgroundImage=`url(${n})`,e.style.backgroundRepeat=s,e.style.backgroundPosition=a,e.style.backgroundSize=o):e.style.backgroundImage="none"}static removeBackgroundStyle(e){i.cacheStyle&&(B2.forEach(t=>{e.style[t]=i.cacheStyle[t]}),i.cacheStyle=null)}constructor(e){this.ctx=e,this._markerPath=null,this._marker=null,this._gradient=null}merge(e,t){let r=this.ctx.mindMap.themeConfig,n=null,s=!1;t?(s=!0,n=r):this.ctx.isGeneralization?n=r.generalization:this.ctx.layerIndex===0?n=r.root:this.ctx.layerIndex===1?n=r.second:n=r.node;let a="";return this.getSelfStyle(e)!==void 0?a=this.getSelfStyle(e):n[e]!==void 0?a=n[e]:a=r[e],s||this.addToEffectiveStyles({[e]:a}),a}getStyle(e,t){return this.merge(e,t)}getSelfStyle(e){return this.ctx.getData(e)}addToEffectiveStyles(e){this.ctx.mindMap.painter&&(this.ctx.effectiveStyles={...this.ctx.effectiveStyles,...e})}rect(e){this.shape(e),e.radius(this.merge("borderRadius"))}shape(e){let t={};Mu.forEach(r=>{t[r]=this.merge(r)}),t.gradientStyle?(this._gradient||(this._gradient=this.ctx.nodeDraw.gradient("linear")),this._gradient.update(r=>{r.stop(0,t.startColor),r.stop(1,t.endColor)}),this._gradient.from(...t.startDir).to(...t.endDir),e.fill(this._gradient)):e.fill({color:t.fillColor}),e.stroke({color:t.borderColor,width:t.borderWidth,dasharray:t.borderDasharray})}text(e){let t={color:this.merge("color"),fontFamily:this.merge("fontFamily"),fontSize:this.merge("fontSize"),fontWeight:this.merge("fontWeight"),fontStyle:this.merge("fontStyle"),textDecoration:this.merge("textDecoration")};e.fill({color:t.color}).css({"font-family":t.fontFamily,"font-size":t.fontSize+"px","font-weight":t.fontWeight,"font-style":t.fontStyle,"text-decoration":t.textDecoration})}domText(e,t=1){let r={color:this.merge("color"),fontFamily:this.merge("fontFamily"),fontSize:this.merge("fontSize"),fontWeight:this.merge("fontWeight"),fontStyle:this.merge("fontStyle"),textDecoration:this.merge("textDecoration"),textAlign:this.merge("textAlign")};e.style.color=r.color,e.style.textDecoration=r.textDecoration,e.style.fontFamily=r.fontFamily,e.style.fontSize=r.fontSize*t+"px",e.style.fontWeight=r.fontWeight||"normal",e.style.fontStyle=r.fontStyle,e.style.textAlign=r.textAlign}tagText(e,t){e.fill({color:"#fff"}).css({"font-size":t.fontSize+"px"})}tagRect(e,t){e.fill({color:t.fill}),t.radius&&e.radius(t.radius)}iconNode(e,t){e.attr({fill:t||this.merge("color")})}line(e,{width:t,color:r,dasharray:n}={},s,a){let{customHandleLine:o}=this.ctx.mindMap.opt;if(typeof o=="function"&&o(this.ctx,e,{width:t,color:r,dasharray:n}),e.stroke({color:r,dasharray:n,width:t}).fill({color:"none"}),s){let l=this.merge("showLineMarker",!0),h=a.style;if(l){h._marker=h._marker||h.createMarker(),h._markerPath.stroke({color:r}).fill({color:r}),e.attr("marker-start",""),e.attr("marker-end","");let d=h.merge("lineMarkerDir");e.marker(d,h._marker)}else h._marker&&(e.attr("marker-start",""),e.attr("marker-end",""),h._marker.remove(),h._marker=null)}}createMarker(){return this.ctx.lineDraw.marker(20,20,e=>{e.ref(8,5),e.size(20,20),e.attr("markerUnits","userSpaceOnUse"),e.attr("orient","auto-start-reverse"),this._markerPath=e.path("M0,0 L2,5 L0,10 L10,5 Z")})}generalizationLine(e){e.stroke({width:this.merge("generalizationLineWidth",!0),color:this.merge("generalizationLineColor",!0)}).fill({color:"none"})}iconBtn(e,t,r){let{color:n,fill:s,fontSize:a,fontColor:o}=this.ctx.mindMap.opt.expandBtnStyle||{color:"#808080",fill:"#fff",fontSize:12,strokeColor:"#333333",fontColor:"#333333"};e.fill({color:n}),t.fill({color:n}),r.fill({color:s}),this.ctx.mindMap.opt.isShowExpandNum&&e.attr({"font-size":a+"px","font-color":o})}hasCustomStyle(){let e=!1;return Object.keys(this.ctx.getData()).forEach(t=>{hs(t)&&(e=!0)}),e}getCustomStyle(){let e={};return Object.keys(this.ctx.getData()).forEach(t=>{hs(t)&&(e[t]=this.ctx.getData(t))}),e}hoverNode(e){let t=this.merge("hoverRectColor")||this.ctx.mindMap.opt.hoverRectColor,r=this.merge("hoverRectRadius");e.radius(r).fill("none").stroke({color:t})}onRemove(){this._marker&&(this._marker.remove(),this._marker=null),this._markerPath&&(this._markerPath.remove(),this._markerPath=null),this._gradient&&(this._gradient.remove(),this._gradient=null)}};W0.cacheStyle=null;qo=W0});var Ho,P2,Tu=T(()=>{yt();$e();Ho=class{constructor(e){this.node=e,this.mindMap=e.mindMap}getShapePadding(e,t,r,n){let s=this.node.getShape(),a=15,o=5,l=e+r*2,h=t+n*2,d=Math.abs(l-h);switch(s){case k.SHAPE.ROUNDED_RECTANGLE:return{paddingX:t>e?(t-e)/2:0,paddingY:0};case k.SHAPE.DIAMOND:return{paddingX:e/2,paddingY:t/2};case k.SHAPE.PARALLELOGRAM:return{paddingX:r<=0?a:0,paddingY:0};case k.SHAPE.OUTER_TRIANGULAR_RECTANGLE:return{paddingX:r<=0?a:0,paddingY:0};case k.SHAPE.INNER_TRIANGULAR_RECTANGLE:return{paddingX:r<=0?a:0,paddingY:0};case k.SHAPE.ELLIPSE:return{paddingX:r<=0?a:0,paddingY:n<=0?o:0};case k.SHAPE.CIRCLE:return{paddingX:h>l?d/2:0,paddingY:ht.name===e)}createShape(){let e=this.node.getShape(),t=null;if(e===k.SHAPE.RECTANGLE?t=this.createRect():e===k.SHAPE.DIAMOND?t=this.createDiamond():e===k.SHAPE.PARALLELOGRAM?t=this.createParallelogram():e===k.SHAPE.ROUNDED_RECTANGLE?t=this.createRoundedRectangle():e===k.SHAPE.OCTAGONAL_RECTANGLE?t=this.createOctagonalRectangle():e===k.SHAPE.OUTER_TRIANGULAR_RECTANGLE?t=this.createOuterTriangularRectangle():e===k.SHAPE.INNER_TRIANGULAR_RECTANGLE?t=this.createInnerTriangularRectangle():e===k.SHAPE.ELLIPSE?t=this.createEllipse():e===k.SHAPE.CIRCLE&&(t=this.createCircle()),!t){let r=this.getShapeFromExtendList(e);r&&(t=r.createShape(this.node))}return t||this.createRect()}getNodeSize(){let e=this.node.getBorderWidth(),{width:t,height:r}=this.node;return t-=e,r-=e,{width:t,height:r}}createPath(e){let{customCreateNodePath:t}=this.mindMap.opt;return t?Ae(t(e)):new ar().plot(e)}createPolygon(e){let{customCreateNodePolygon:t}=this.mindMap.opt;return t?Ae(t(e)):new Gi().plot(e)}createRect(){let{width:e,height:t}=this.getNodeSize(),r=this.node.style.merge("borderRadius"),n=` +`);for(let t=0,r=e.length;t(i.attr("href")||"").includes(this.id()))}}});Oo.prototype.MorphArray=Er;Me(Oo,"TextPath");U0=class extends Dt{constructor(e,t=e){super(Ue("use",e),t)}use(e,t){return this.attr("href",(t||"")+"#"+e,Bo)}};fe({Container:{use:He(function(i,e){return this.put(new U0).use(i,e)})}});Me(U0,"Use");ke=Rt;xe([Lo,Io,$i,rs,Zs],Xt("viewbox"));xe([ns,as,ji,nr],Xt("marker"));xe(_e,Xt("Text"));xe(nr,Xt("Path"));xe(So,Xt("Defs"));xe([_e,ea],Xt("Tspan"));xe([Ve,Ks,is,Gi],Xt("radius"));xe(ts,Xt("EventTarget"));xe(hn,Xt("Dom"));xe(Si,Xt("Element"));xe(Dt,Xt("Shape"));xe([Yt,F0],Xt("Container"));xe(is,Xt("Gradient"));xe(Gi,Xt("Runner"));Nr.extend(qw());uT([we,Ei,Vt,be,ln,rr,Er,it]);fT()});var j0=tr((_U,E2)=>{"use strict";var $0=function(e){return GT(e)&&!WT(e)};function GT(i){return!!i&&typeof i=="object"}function WT(i){var e=Object.prototype.toString.call(i);return e==="[object RegExp]"||e==="[object Date]"||XT(i)}var VT=typeof Symbol=="function"&&Symbol.for,YT=VT?Symbol.for("react.element"):60103;function XT(i){return i.$$typeof===YT}function KT(i){return Array.isArray(i)?[]:{}}function Po(i,e){var t=e&&e.clone===!0;return t&&$0(i)?na(KT(i),i,e):i}function N2(i,e,t){var r=i.slice();return e.forEach(function(n,s){typeof r[s]>"u"?r[s]=Po(n,t):$0(n)?r[s]=na(i[s],n,t):i.indexOf(n)===-1&&r.push(Po(n,t))}),r}function ZT(i,e,t){var r={};return $0(i)&&Object.keys(i).forEach(function(n){r[n]=Po(i[n],t)}),Object.keys(e).forEach(function(n){!$0(e[n])||!i[n]?r[n]=Po(e[n],t):r[n]=na(i[n],e[n],t)}),r}function na(i,e,t){var r=Array.isArray(e),n=Array.isArray(i),s=t||{arrayMerge:N2},a=r===n;if(a)if(r){var o=s.arrayMerge||N2;return o(i,e,t)}else return ZT(i,e,t);else return Po(e,t)}na.all=function(e,t){if(!Array.isArray(e)||e.length<2)throw new Error("first argument should be an array with at least two elements");return e.reduce(function(r,n){return na(r,n,t)})};var QT=na;E2.exports=QT});var G0,JT,S2,Fo,qo=T(()=>{G0={paddingX:15,paddingY:5,imgMaxWidth:200,imgMaxHeight:100,iconSize:20,lineWidth:1,lineColor:"#549688",lineDasharray:"none",lineFlow:!1,lineFlowDuration:1,lineFlowForward:!0,lineStyle:"straight",rootLineKeepSameInCurve:!0,rootLineStartPositionKeepSameInCurve:!1,lineRadius:5,showLineMarker:!1,generalizationLineWidth:1,generalizationLineColor:"#549688",generalizationLineMargin:0,generalizationNodeMargin:20,associativeLineWidth:2,associativeLineColor:"rgb(51, 51, 51)",associativeLineActiveWidth:8,associativeLineActiveColor:"rgba(2, 167, 240, 1)",associativeLineDasharray:"6,4",associativeLineTextColor:"rgb(51, 51, 51)",associativeLineTextFontSize:14,associativeLineTextLineHeight:1.2,associativeLineTextFontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",backgroundColor:"#fafafa",backgroundImage:"none",backgroundRepeat:"no-repeat",backgroundPosition:"center center",backgroundSize:"cover",nodeUseLineStyle:!1,root:{shape:"rectangle",fillColor:"#549688",fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",color:"#fff",fontSize:16,fontWeight:"bold",fontStyle:"normal",borderColor:"transparent",borderWidth:0,borderDasharray:"none",borderRadius:5,textDecoration:"none",gradientStyle:!1,startColor:"#549688",endColor:"#fff",startDir:[0,0],endDir:[1,0],lineMarkerDir:"end",hoverRectColor:"",hoverRectRadius:5,textAlign:"left",imgPlacement:"top",tagPlacement:"right"},second:{shape:"rectangle",marginX:100,marginY:40,fillColor:"#fff",fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",color:"#565656",fontSize:16,fontWeight:"normal",fontStyle:"normal",borderColor:"#549688",borderWidth:1,borderDasharray:"none",borderRadius:5,textDecoration:"none",gradientStyle:!1,startColor:"#549688",endColor:"#fff",startDir:[0,0],endDir:[1,0],lineMarkerDir:"end",hoverRectColor:"",hoverRectRadius:5,textAlign:"left",imgPlacement:"top",tagPlacement:"right"},node:{shape:"rectangle",marginX:50,marginY:0,fillColor:"transparent",fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",color:"#6a6d6c",fontSize:14,fontWeight:"normal",fontStyle:"normal",borderColor:"transparent",borderWidth:0,borderRadius:5,borderDasharray:"none",textDecoration:"none",gradientStyle:!1,startColor:"#549688",endColor:"#fff",startDir:[0,0],endDir:[1,0],lineMarkerDir:"end",hoverRectColor:"",hoverRectRadius:5,textAlign:"left",imgPlacement:"top",tagPlacement:"right"},generalization:{shape:"rectangle",marginX:100,marginY:40,fillColor:"#fff",fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",color:"#565656",fontSize:16,fontWeight:"normal",fontStyle:"normal",borderColor:"#549688",borderWidth:1,borderDasharray:"none",borderRadius:5,textDecoration:"none",gradientStyle:!1,startColor:"#549688",endColor:"#fff",startDir:[0,0],endDir:[1,0],hoverRectColor:"",hoverRectRadius:5,textAlign:"left",imgPlacement:"top",tagPlacement:"right"}},JT=["lineWidth","lineColor","lineDasharray","lineStyle","generalizationLineWidth","generalizationLineColor","associativeLineWidth","associativeLineColor","associativeLineActiveWidth","associativeLineActiveColor","associativeLineTextColor","associativeLineTextFontSize","associativeLineTextLineHeight","associativeLineTextFontFamily","backgroundColor","backgroundImage","backgroundRepeat","backgroundPosition","backgroundSize","rootLineKeepSameInCurve","rootLineStartPositionKeepSameInCurve","showLineMarker","lineRadius","hoverRectColor","hoverRectRadius","lineFlow","lineFlowDuration","lineFlowForward","textAlign"],S2=i=>{let e=Object.keys(i);for(let t=0;tr===e[t]))return!1;return!0},Fo=["lineColor","lineDasharray","lineWidth","lineMarkerDir","lineFlow","lineFlowDuration","lineFlowForward"]});var A2,ue,Zt,K0,sa,Qt,Tu,ls,Nu,k2,Ai,C2,Jt,un,_2,L2,I2,W0,aa,oa,eN,Tt,z2,R2,os,Kt,V0,D2,Ho,O2,Uo,Eu,Z0,tN,Y0,Q0,cn,B2,P2,hs,F2,q2,ds,H2,U2,la,ha,$o,cs,Ot,da,ze,J0,fn,X0,Su,Au,$2,eh,j2,G2,W2,ku,jo,Cu,_u,V2,Y2,Lu,X2,iN,BU,mn,us,sr,Go,Iu,K2,pe=T(()=>{Qc();Ge();W3();wt();A2=yt(j0());qo();ue=(i,e,t,r,n,s=0,a=0,o=[])=>{let l=!1;if(t&&(l=t(i,e,n,s,a,o)),!l&&i.children&&i.children.length>0){let h=s+1;i.children.forEach((d,c)=>{ue(d,i,t,r,!1,h,c,[...o,i])})}r&&r(i,e,n,s,a,o)},Zt=(i,e)=>{let t=[i],r=!1;for(e(i,null)==="stop"&&(r=!0);t.length&&!r;){let n=t.shift();n.children&&n.children.length&&n.children.forEach(s=>{r||(t.push(s),e(s,n)==="stop"&&(r=!0))})}},K0=(i,e,t,r)=>{let n=i/e,s=[];if(t&&r)if(i<=t&&e<=r)s=[i,e];else{let a=t/r;n>a?s=[t,t/n]:s=[n*r,r]}else t?i<=t?s=[i,e]:s=[t,t/n]:r&&(e<=r?s=[i,e]:s=[n*r,r]);return s},sa=i=>{i=i.replace(/
    /gim,` +`);let e=document.createElement("div");return e.innerHTML=i,i=e.textContent,i},Qt=i=>{try{return JSON.parse(JSON.stringify(i))}catch{return null}},Tu=(i,e,t=!1)=>(i.data=Qt(e.data),t&&(i.data.isActive=!1,us(i.data).forEach(n=>{n.isActive=!1})),i.children=[],e.children&&e.children.length>0&&e.children.forEach((r,n)=>{i.children[n]=Tu({},r,t)}),Object.keys(e).forEach(r=>{!["data","children"].includes(r)&&!/^_/.test(r)&&(i[r]=e[r])}),i),ls=(i,e,t=!1,r=!0)=>{let n=e.nodeData?e.nodeData:e;return i.data=Qt(n.data),r?delete i.data.uid:i.data.uid||(i.data.uid=Tt()),t&&(i.data.isActive=!1),i.children=[],e.children&&e.children.length>0?e.children.forEach((s,a)=>{i.children[a]=ls({},s,t,r)}):e.nodeData&&e.nodeData.children&&e.nodeData.children.length>0&&e.nodeData.children.forEach((s,a)=>{i.children[a]=ls({},s,t,r)}),Object.keys(n).forEach(s=>{!["data","children"].includes(s)&&!/^_/.test(s)&&(i[s]=n[s])}),i},Nu=(i,e=!1)=>new Promise((t,r)=>{let n=new Image;n.setAttribute("crossOrigin","anonymous"),n.onload=()=>{try{let s=document.createElement("canvas");s.width=n.width,s.height=n.height,s.getContext("2d").drawImage(n,0,0,n.width,n.height),e?s.toBlob(o=>{t(o)}):t(s.toDataURL())}catch(s){r(s)}},n.onerror=s=>{r(s)},n.src=i}),k2=(i,e)=>{let t=document.createElement("a");t.href=i,t.download=e,t.click()},Ai=(i,e=300,t)=>{let r=null;return(...n)=>{r||(r=setTimeout(()=>{i.call(t,...n),r=null},e))}},C2=(i,e=300,t)=>{let r=null;return(...n)=>{r&&clearTimeout(r),r=setTimeout(()=>{r=null,i.apply(t,n)},e)}},Jt=(i,e=()=>{})=>{let t=0,r=i.length;if(r<=0)return e();let n=()=>{if(t>=r){e();return}i[t](),setTimeout(()=>{t++,n()},0)};n()},un=i=>i*(Math.PI/180),_2=i=>i.replace(/([a-z])([A-Z])/g,(...e)=>e[1]+"-"+e[2].toLowerCase()),L2=function(i,e){let t=!1,r=null,n=()=>{t=!1,e?i.call(e):i()};if(typeof MutationObserver<"u"){let s=1,a=new MutationObserver(n),o=document.createTextNode(s);a.observe(o,{characterData:!0}),r=function(){s=(s+1)%2,o.data=s}}else r=setTimeout;return function(){t||(t=!0,r(n,0))}},I2=(i,e,t=0,r=0)=>{let n=i.elRect,{scaleX:s,scaleY:a,translateX:o,translateY:l}=i.draw.transform(),{left:h,top:d,width:c,height:f}=e,m=(h+c)*s+o,g=(d+f)*a+l;h=h*s+o,d=d*a+l;let x=0,y=0;return h<0+t&&(x=-h+t),m>n.width-t&&(x=-(m-n.width)-t),d<0+r&&(y=-d+r),g>n.height-r&&(y=-(g-n.height)-r),{isOuter:x!==0||y!==0,offsetLeft:x,offsetTop:y}},W0=null,aa=i=>(W0||(W0=document.createElement("div")),W0.innerHTML=i,W0.textContent),oa=i=>new Promise((e,t)=>{let r=new FileReader;r.onload=n=>{e(n.target.result)},r.onerror=n=>{t(n)},r.readAsDataURL(i)}),eN=i=>new Promise(e=>{let t=new Image;t.src=i,t.onload=()=>{e({width:t.width,height:t.height})},t.onerror=()=>{e({width:0,height:0})}}),Tt=()=>vo(),z2=i=>new Promise((e,t)=>{let r=new FileReader;r.readAsDataURL(i),r.onload=async n=>{let s=n.target.result,a=await eN(s);e({url:s,size:a})},r.onerror=n=>{t(n)}}),R2=i=>([[" "," "]].forEach(e=>{i=i.replace(new RegExp(e[0],"g"),e[1])}),i),os=i=>Object.prototype.toString.call(i).slice(8,-1),Kt=i=>i==null||i==="",V0=null,D2=i=>{V0||(V0=document.createElement("div")),V0.innerHTML=i;for(let e=V0.childNodes,t=e.length;t--;)if(e[t].nodeType==1)return!0;return!1},Ho=null,O2=(i,e,t)=>{Ho||(Ho=document.createElement("div")),Ho.innerHTML=i;let r=n=>{n.childNodes.forEach(a=>{a.nodeType===1?r(a):a.nodeType===3&&n.replaceChild(document.createTextNode(a.nodeValue.replace(new RegExp(e,"g"),t)),a)})};return r(Ho),Ho.innerHTML},Uo=i=>(i=String(i).replace(/\s+/g,""),["#fff","#ffffff","#FFF","#FFFFFF","rgb(255,255,255)"].includes(i)||/rgba\(255,255,255,[^)]+\)/.test(i)),Eu=i=>(i=String(i).replace(/\s+/g,""),["","transparent"].includes(i)||/rgba\(\d+,\d+,\d+,0\)/.test(i)),Z0=i=>{let{lineColor:e,root:t,second:r,node:n}=i,s=[e,t.fillColor,t.color,r.fillColor,r.color,n.fillColor,n.color,t.borderColor,r.borderColor,n.borderColor];for(let a=0;a{let e=t=>{t.childNodes.forEach(n=>{n.nodeType===1&&(n.classList.contains("ql-formula")?n.parentNode.removeChild(n):e(n))})};e(i)},Y0=null,Q0=i=>{Y0||(Y0=document.createElement("div")),Y0.innerHTML=i;let e=Y0.childNodes,t="";for(let r=0;r{cn||(cn=document.createElement("div")),cn.innerHTML=i;let e=cn.querySelectorAll(".ql-formula");Array.from(e).forEach(n=>{let s=document.createTextNode("$smmformula$");n.parentNode.replaceChild(s,n)});let t=cn.childNodes,r=[];for(let n=0;n`

    ${fn(n)}

    `).join(""),e.length>0){i=i.replace(/\$smmformula\$/g,''),cn.innerHTML=i;let n=cn.querySelectorAll(".smmformula");Array.from(n).forEach((s,a)=>{s.parentNode.replaceChild(e[a],s)}),i=cn.innerHTML}return i},P2=(i,e)=>{let t={};return Object.keys(e).forEach(r=>{let n=i[r],s=e[r];if(os(n)!==os(s)){t[r]=s;return}if(os(n)==="Object"){if(JSON.stringify(n)!==JSON.stringify(s)){t[r]=s;return}}else if(n!==s){t[r]=s;return}}),t},hs=i=>/^_/.test(i)?!1:!js.includes(i),F2=i=>{let e=[...Fo],t=Object.keys(i);for(let r=0;ri.reduce((e,t)=>{let r=e.find(n=>n.type===t.type);return r?t.list.forEach(n=>{let s=r.list.find(a=>a.name===n.name);s?s.icon=n.icon:r.list.push(n)}):e.push({...t}),e},[]),ds=i=>{let e=[];return i.forEach(t=>{i.find(r=>r.uid!==t.uid&&r.isAncestor(t))||e.push(t)}),e},H2=i=>{let e={},t={};i.forEach(n=>{let s=n.parent;if(s){let a=s.uid;t[a]=s;let o=n.getIndexInBrothers(),l={node:n,index:o};e[a]?e[a].find(h=>h.index===l.index)||e[a].push(l):e[a]=[l]}});let r=[];return Object.keys(e).forEach(n=>{if(e[n].length>1){let s=e[n].map(a=>a.index).sort((a,o)=>a-o);r.push({node:t[n],range:[s[0],s[s.length-1]]})}else r.push({node:e[n][0].node})}),r},U2=(i,e,t,r,n,s,a,o)=>e>n&&s>i&&r>a&&o>t,la=i=>{let e=window.getSelection(),t=document.createRange();t.selectNodeContents(i),t.collapse(),e.removeAllRanges(),e.addRange(t)},ha=i=>{let e=window.getSelection(),t=document.createRange();t.selectNodeContents(i),e.removeAllRanges(),e.addRange(t)},$o=(i,e={})=>{e={...e},e&&e.richText&&e.resetRichText&&delete e.resetRichText;let r=n=>{n.forEach(s=>{s.data={...s.data,...e},s.children&&s.children.length>0&&r(s.children)})};return r(i),i},cs=(i,e=!1,t=null,r=!1)=>{let n=s=>{s.forEach(a=>{a.data||(a.data={}),(e||Kt(a.data.uid))&&(a.data.uid=Tt()),r&&us(a.data).forEach(l=>{(e||Kt(l.uid))&&(l.uid=Tt())}),t&&t(a),a.children&&a.children.length>0&&n(a.children)})};return n(i),i},Ot=i=>i?Array.isArray(i)?i:[i]:[],da=i=>i.parent?i.parent.nodeData.children.findIndex(e=>e.data.uid===i.uid):0,ze=(i,e)=>e.findIndex(t=>t.uid===i.uid),J0=i=>{let e=0;for(let n=0;n([["&","&"],["<","<"],[">",">"]].forEach(e=>{i=i.replace(new RegExp(e[0],"g"),e[1])}),i),X0=(i,e)=>{let t=os(i);if(t!==os(e))return!1;if(t==="Object"){let r=Object.keys(i),n=Object.keys(e);if(r.length!==n.length)return!1;for(let s=0;snavigator.clipboard&&typeof navigator.clipboard.read=="function",Au=i=>{navigator.clipboard&&navigator.clipboard.writeText&&navigator.clipboard.writeText(JSON.stringify(i))},$2=async()=>{let i=null,e=null;if(Su()){let t=await navigator.clipboard.read();if(t&&t.length>0)for(let r of t)for(let n of r.types)/^image\//.test(n)?e=await r.getType(n):n==="text/plain"&&(i=await(await r.getType(n)).text())}return{text:i,img:e}},eh=i=>{if(!i||!i.parent)return;let e=da(i);e!==-1&&i.parent.nodeData.children.splice(e,1)},j2=i=>(j3.forEach(e=>{i=i.replace(new RegExp(`<${e}([^>]*)>`,"g"),`<${e} $1 />`)}),i),G2=(i,e)=>{if(i.length!==e.length)return!1;for(let t=0;tr.uid===i[t].uid))return!1;return!0},W2=()=>{let i=navigator.userAgent.match(/\s+Chrome\/(.*)\s+/);return i&&i[1]?Number.parseFloat(i[1]):""},ku=i=>({simpleMindMap:!0,data:i}),jo=i=>{let e=null;if(typeof i=="string")try{let r=JSON.parse(i);typeof r=="object"&&r.simpleMindMap&&(e=r.data)}catch{}else typeof i=="object"&&i.simpleMindMap&&(e=i.data);let t=!!e;return{isSmm:t,data:t?e:String(i)}},Cu=(i,e)=>{i.preventDefault();let t=window.getSelection();if(!t.rangeCount)return;t.deleteFromDocument(),e=e||i.clipboardData.getData("text"),e=fn(e),e=aa(e);let r=e.split(/\n/g),n=document.createDocumentFragment();r.forEach((s,a)=>{let o=document.createTextNode(s);if(n.appendChild(o),a{let e={},t=(r,n)=>{let s=r.data.uid;n&&n.children.push(s),e[s]={isRoot:!n,data:{...r.data},children:[]},r.children&&r.children.length>0&&r.children.forEach(a=>{t(a,e[s])})};return t(i,null),e},V2=(i,e)=>{let t=i.x+i.width/2,r=i.y+i.height/2,n=e.x+e.width/2,s=e.y+e.height/2;return tn&&rn&&r>s?"right-bottom":ts?"left-bottom":tn&&r===s?"right":t===n&&rs?"bottom":"overlap"},Y2=({addContentToHeader:i,addContentToFooter:e})=>{let t=[],r=null,n=0,s=null,a=0,o=(l,h)=>{if(typeof l=="function"){let d=l();if(!d)return;let{el:c,cssText:f,height:m}=d;if(c instanceof HTMLElement){sr(c);let g=mn({el:c,height:m});h(g,m)}f&&t.push(f)}};return o(i,(l,h)=>{r=l,n=h}),o(e,(l,h)=>{s=l,a=h}),{cssTextList:t,header:r,headerHeight:n,footer:s,footerHeight:a}},Lu=(i,e=0,t=0,r=0,n=0,s=!1,a=!1)=>{let o=1/0,l=-1/0,h=1/0,d=-1/0,c=(f,m)=>{if(!(m&&s)&&f.group)try{let{x:g,y:x,width:y,height:b}=f.group.findOne(".smm-node-shape").rbox();gl&&(l=g+y),xd&&(d=x+b)}catch{}!a&&f._generalizationList.length>0&&f._generalizationList.forEach(g=>{c(g.generalizationNode)}),f.children&&f.children.forEach(g=>{c(g)})};return c(i,!0),o=o-e+r,h=h-t+n,l=l-e+r,d=d-t+n,{left:o,top:h,width:l-o,height:d-h}},X2=(i,e=0,t=0,r=0,n=0)=>{let s=1/0,a=-1/0,o=1/0,l=-1/0;return i.forEach(h=>{let{left:d,top:c,width:f,height:m}=Lu(h,e,t,r,n,!1,!0);da&&(a=d+f),cl&&(l=c+m)}),{left:s,top:o,width:a-s,height:l-o}},iN=()=>{if(document.documentElement.requestFullScreen)return"fullscreenchange";if(document.documentElement.webkitRequestFullScreen)return"webkitfullscreenchange";if(document.documentElement.mozRequestFullScreen)return"mozfullscreenchange";if(document.documentElement.msRequestFullscreen)return"msfullscreenchange"},BU=iN(),mn=({el:i,width:e,height:t})=>{let r=new ta;return e!==void 0&&r.width(e),t!==void 0&&r.height(t),r.add(i),r},us=i=>{let e=i.generalization;return e?Array.isArray(e)?e:[e]:[]},sr=i=>{i.setAttribute("xmlns","http://www.w3.org/1999/xhtml")},Go=i=>(i=[...i],i.sort((e,t)=>e.sortIndex-t.sortIndex),i),Iu=(i,e)=>(0,A2.default)(i,e,{arrayMerge:(t,r)=>r}),K2=i=>{let e={};return G3.forEach(t=>{let r=i.style.merge(t);t==="fontSize"&&(r=r+"px"),e[t]=r}),e}});var Z2,zu,th,Wo,ih=T(()=>{pe();Z2=["backgroundColor","backgroundImage","backgroundRepeat","backgroundPosition","backgroundSize"],zu=["gradientStyle","startColor","endColor","startDir","endDir","fillColor","borderColor","borderWidth","borderDasharray"],th=class i{static setBackgroundStyle(e,t){if(!e)return;if(!i.cacheStyle){i.cacheStyle={};let l=window.getComputedStyle(e);Z2.forEach(h=>{i.cacheStyle[h]=l[h]})}let{backgroundColor:r,backgroundImage:n,backgroundRepeat:s,backgroundPosition:a,backgroundSize:o}=t;e.style.backgroundColor=r,n&&n!=="none"?(e.style.backgroundImage=`url(${n})`,e.style.backgroundRepeat=s,e.style.backgroundPosition=a,e.style.backgroundSize=o):e.style.backgroundImage="none"}static removeBackgroundStyle(e){i.cacheStyle&&(Z2.forEach(t=>{e.style[t]=i.cacheStyle[t]}),i.cacheStyle=null)}constructor(e){this.ctx=e,this._markerPath=null,this._marker=null,this._gradient=null}merge(e,t){let r=this.ctx.mindMap.themeConfig,n=null,s=!1;t?(s=!0,n=r):this.ctx.isGeneralization?n=r.generalization:this.ctx.layerIndex===0?n=r.root:this.ctx.layerIndex===1?n=r.second:n=r.node;let a="";return this.getSelfStyle(e)!==void 0?a=this.getSelfStyle(e):n[e]!==void 0?a=n[e]:a=r[e],s||this.addToEffectiveStyles({[e]:a}),a}getStyle(e,t){return this.merge(e,t)}getSelfStyle(e){return this.ctx.getData(e)}addToEffectiveStyles(e){this.ctx.mindMap.painter&&(this.ctx.effectiveStyles={...this.ctx.effectiveStyles,...e})}rect(e){this.shape(e),e.radius(this.merge("borderRadius"))}shape(e){let t={};zu.forEach(r=>{t[r]=this.merge(r)}),t.gradientStyle?(this._gradient||(this._gradient=this.ctx.nodeDraw.gradient("linear")),this._gradient.update(r=>{r.stop(0,t.startColor),r.stop(1,t.endColor)}),this._gradient.from(...t.startDir).to(...t.endDir),e.fill(this._gradient)):e.fill({color:t.fillColor}),e.stroke({color:t.borderColor,width:t.borderWidth,dasharray:t.borderDasharray})}text(e){let t={color:this.merge("color"),fontFamily:this.merge("fontFamily"),fontSize:this.merge("fontSize"),fontWeight:this.merge("fontWeight"),fontStyle:this.merge("fontStyle"),textDecoration:this.merge("textDecoration")};e.fill({color:t.color}).css({"font-family":t.fontFamily,"font-size":t.fontSize+"px","font-weight":t.fontWeight,"font-style":t.fontStyle,"text-decoration":t.textDecoration})}domText(e,t=1){let r={color:this.merge("color"),fontFamily:this.merge("fontFamily"),fontSize:this.merge("fontSize"),fontWeight:this.merge("fontWeight"),fontStyle:this.merge("fontStyle"),textDecoration:this.merge("textDecoration"),textAlign:this.merge("textAlign")};e.style.color=r.color,e.style.textDecoration=r.textDecoration,e.style.fontFamily=r.fontFamily,e.style.fontSize=r.fontSize*t+"px",e.style.fontWeight=r.fontWeight||"normal",e.style.fontStyle=r.fontStyle,e.style.textAlign=r.textAlign}tagText(e,t){e.fill({color:"#fff"}).css({"font-size":t.fontSize+"px"})}tagRect(e,t){e.fill({color:t.fill}),t.radius&&e.radius(t.radius)}iconNode(e,t){e.attr({fill:t||this.merge("color")})}line(e,{width:t,color:r,dasharray:n}={},s,a){let{customHandleLine:o}=this.ctx.mindMap.opt;if(typeof o=="function"&&o(this.ctx,e,{width:t,color:r,dasharray:n}),e.stroke({color:r,dasharray:n,width:t}).fill({color:"none"}),s){let l=this.merge("showLineMarker",!0),h=a.style;if(l){h._marker=h._marker||h.createMarker(),h._markerPath.stroke({color:r}).fill({color:r}),e.attr("marker-start",""),e.attr("marker-end","");let d=h.merge("lineMarkerDir");e.marker(d,h._marker)}else h._marker&&(e.attr("marker-start",""),e.attr("marker-end",""),h._marker.remove(),h._marker=null)}}createMarker(){return this.ctx.lineDraw.marker(20,20,e=>{e.ref(8,5),e.size(20,20),e.attr("markerUnits","userSpaceOnUse"),e.attr("orient","auto-start-reverse"),this._markerPath=e.path("M0,0 L2,5 L0,10 L10,5 Z")})}generalizationLine(e){e.stroke({width:this.merge("generalizationLineWidth",!0),color:this.merge("generalizationLineColor",!0)}).fill({color:"none"})}iconBtn(e,t,r){let{color:n,fill:s,fontSize:a,fontColor:o}=this.ctx.mindMap.opt.expandBtnStyle||{color:"#808080",fill:"#fff",fontSize:12,strokeColor:"#333333",fontColor:"#333333"};e.fill({color:n}),t.fill({color:n}),r.fill({color:s}),this.ctx.mindMap.opt.isShowExpandNum&&e.attr({"font-size":a+"px","font-color":o})}hasCustomStyle(){let e=!1;return Object.keys(this.ctx.getData()).forEach(t=>{hs(t)&&(e=!0)}),e}getCustomStyle(){let e={};return Object.keys(this.ctx.getData()).forEach(t=>{hs(t)&&(e[t]=this.ctx.getData(t))}),e}hoverNode(e){let t=this.merge("hoverRectColor")||this.ctx.mindMap.opt.hoverRectColor,r=this.merge("hoverRectRadius");e.radius(r).fill("none").stroke({color:t})}onRemove(){this._marker&&(this._marker.remove(),this._marker=null),this._markerPath&&(this._markerPath.remove(),this._markerPath=null),this._gradient&&(this._gradient.remove(),this._gradient=null)}};th.cacheStyle=null;Wo=th});var Vo,Q2,Ru=T(()=>{wt();Ge();Vo=class{constructor(e){this.node=e,this.mindMap=e.mindMap}getShapePadding(e,t,r,n){let s=this.node.getShape(),a=15,o=5,l=e+r*2,h=t+n*2,d=Math.abs(l-h);switch(s){case C.SHAPE.ROUNDED_RECTANGLE:return{paddingX:t>e?(t-e)/2:0,paddingY:0};case C.SHAPE.DIAMOND:return{paddingX:e/2,paddingY:t/2};case C.SHAPE.PARALLELOGRAM:return{paddingX:r<=0?a:0,paddingY:0};case C.SHAPE.OUTER_TRIANGULAR_RECTANGLE:return{paddingX:r<=0?a:0,paddingY:0};case C.SHAPE.INNER_TRIANGULAR_RECTANGLE:return{paddingX:r<=0?a:0,paddingY:0};case C.SHAPE.ELLIPSE:return{paddingX:r<=0?a:0,paddingY:n<=0?o:0};case C.SHAPE.CIRCLE:return{paddingX:h>l?d/2:0,paddingY:ht.name===e)}createShape(){let e=this.node.getShape(),t=null;if(e===C.SHAPE.RECTANGLE?t=this.createRect():e===C.SHAPE.DIAMOND?t=this.createDiamond():e===C.SHAPE.PARALLELOGRAM?t=this.createParallelogram():e===C.SHAPE.ROUNDED_RECTANGLE?t=this.createRoundedRectangle():e===C.SHAPE.OCTAGONAL_RECTANGLE?t=this.createOctagonalRectangle():e===C.SHAPE.OUTER_TRIANGULAR_RECTANGLE?t=this.createOuterTriangularRectangle():e===C.SHAPE.INNER_TRIANGULAR_RECTANGLE?t=this.createInnerTriangularRectangle():e===C.SHAPE.ELLIPSE?t=this.createEllipse():e===C.SHAPE.CIRCLE&&(t=this.createCircle()),!t){let r=this.getShapeFromExtendList(e);r&&(t=r.createShape(this.node))}return t||this.createRect()}getNodeSize(){let e=this.node.getBorderWidth(),{width:t,height:r}=this.node;return t-=e,r-=e,{width:t,height:r}}createPath(e){let{customCreateNodePath:t}=this.mindMap.opt;return t?ke(t(e)):new nr().plot(e)}createPolygon(e){let{customCreateNodePolygon:t}=this.mindMap.opt;return t?ke(t(e)):new ji().plot(e)}createRect(){let{width:e,height:t}=this.getNodeSize(),r=this.node.style.merge("borderRadius"),n=` M${r},0 L${e-r},0 C${e-r},0 ${e},0 ${e},${r} @@ -52,9 +52,9 @@ var Zb=Object.create;var T0=Object.defineProperty;var Qb=Object.getOwnPropertyDe A${r},${n} 0 0,1 ${r},${t} M${r},${t} A${r},${n} 0 0,1 ${r},0 - `;return this.createPath(s)}},P2=[k.SHAPE.RECTANGLE,k.SHAPE.DIAMOND,k.SHAPE.PARALLELOGRAM,k.SHAPE.ROUNDED_RECTANGLE,k.SHAPE.OCTAGONAL_RECTANGLE,k.SHAPE.OUTER_TRIANGULAR_RECTANGLE,k.SHAPE.INNER_TRIANGULAR_RECTANGLE,k.SHAPE.ELLIPSE,k.SHAPE.CIRCLE]});function zT(){let i=this.getData("generalization");return Array.isArray(i)?i:i?[i]:[]}function RT(){return this.formatGetGeneralization().length>0}function DT(){return!!this.formatGetGeneralization().find(e=>!e.range||e.range.length<=0)}function OT(i){return this._generalizationList.findIndex(e=>e.generalizationNode.uid===i.uid)}function BT(){if(this.isGeneralization||!this.checkHasGeneralization())return;let i=0,e=0;this.formatGetGeneralization().forEach((r,n)=>{let s=this._generalizationList[n];s||(s=this._generalizationList[n]={}),s.node=this,s.range=r.range,s.generalizationLine||(s.generalizationLine=this.lineDraw.path()),s.generalizationNode||(s.generalizationNode=new da({data:{inserting:r.inserting,data:r},uid:wt(),renderer:this.renderer,mindMap:this.mindMap,isGeneralization:!0})),delete r.inserting,s.generalizationNode.generalizationBelongNode=this,s.generalizationNode.width>i&&(i=s.generalizationNode.width),s.generalizationNode.height>e&&(e=s.generalizationNode.height),r.isActive&&this.renderer.addNodeToActiveList(s.generalizationNode)}),this._generalizationNodeWidth=i,this._generalizationNodeHeight=e}function PT(){this.isGeneralization||(this.removeGeneralization(),this.createGeneralizationNode())}function FT(i){if(this.isGeneralization)return;this.updateGeneralizationData();let e=this.formatGetGeneralization();if(e.length<=0||this.getData("expand")===!1){this.removeGeneralization();return}e.length!==this._generalizationList.length&&this.removeGeneralization(),this.createGeneralizationNode(),this.renderer.layout.renderGeneralization(this._generalizationList),this._generalizationList.forEach(t=>{this.style.generalizationLine(t.generalizationLine),t.generalizationNode.render(()=>{},i)})}function qT(){let i=this.getChildrenLength(),e=this.formatGetGeneralization(),t=[];e.forEach(r=>{if(!r.range){t.push(r);return}r.range.length>0&&r.range[0]<=i-1&&r.range[1]<=i-1&&t.push(r)}),t.length!==e.length&&this.setData({generalization:t})}function HT(){this.isGeneralization||(this._generalizationList.forEach(i=>{i.generalizationNode.style.onRemove(),i.generalizationLine&&(i.generalizationLine.remove(),i.generalizationLine=null),i.generalizationNode&&(this.renderer.removeNodeFromActiveList(i.generalizationNode),i.generalizationNode.remove(),i.generalizationNode=null)}),this._generalizationList=[],this.generalizationBelongNode&&this.nodeDraw.find(".generalization_"+this.generalizationBelongNode.uid).remove())}function UT(){this.isGeneralization||this._generalizationList.forEach(i=>{i.generalizationLine&&i.generalizationLine.hide(),i.generalizationNode&&i.generalizationNode.hide()})}function $T(){this.isGeneralization||this._generalizationList.forEach(i=>{i.generalizationLine&&i.generalizationLine.show(),i.generalizationNode&&i.generalizationNode.show()})}function jT(i){this._generalizationList.forEach(e=>{e.generalizationLine.opacity(i),e.generalizationNode.group.opacity(i)})}function GT(){let i=this.generalizationBelongNode,e=i.formatGetGeneralization(),t=i.getGeneralizationNodeIndex(this),r=e[t],s=this.getStyle("hoverRectColor")||this.mindMap.opt.hoverRectColor,a=s?{stroke:s}:null;Array.isArray(r.range)&&r.range.length>0?this.mindMap.renderer.highlightNode(i,r.range,a):this.mindMap.renderer.highlightNode(i,null,a)}function VT(){this.mindMap.renderer.closeHighlightNode()}var Nu,F2=T(()=>{X0();pe();Nu={formatGetGeneralization:zT,checkHasGeneralization:RT,checkHasSelfGeneralization:DT,getGeneralizationNodeIndex:OT,createGeneralizationNode:BT,updateGeneralization:PT,updateGeneralizationData:qT,renderGeneralization:FT,removeGeneralization:HT,hideGeneralization:UT,showGeneralization:$T,setGeneralizationOpacity:jT,handleGeneralizationMouseenter:GT,handleGeneralizationMouseleave:VT}});var WT,YT,XT,KT,ZT,Uo,Eu=T(()=>{WT='',YT='',XT='',KT='',ZT='',Uo={open:WT,close:YT,remove:XT,imgAdjust:KT,quickCreateChild:ZT}});function QT(){if(this._openExpandNode)return;let{expandBtnSize:i,expandBtnIcon:e,isShowExpandNum:t}=this.mindMap.opt,{close:r,open:n}=e||{};t?(this._openExpandNode=new Ce,this._openExpandNode.addClass("smm-expand-btn-text"),this._openExpandNode.attr({"text-anchor":"middle","dominant-baseline":"middle",x:i/2,y:2})):(this._openExpandNode=Ae(n||Uo.open).size(i,i),this._openExpandNode.x(0).y(-i/2)),this._closeExpandNode=Ae(r||Uo.close).size(i,i),this._closeExpandNode.x(0).y(-i/2),this._fillExpandNode=new Wi().size(i),this._fillExpandNode.x(0).y(-i/2),this.style.iconBtn(this._openExpandNode,this._closeExpandNode,this._fillExpandNode)}function JT(i=[]){return i.reduce((e,t)=>e+this.sumNode(t.children||[]),i.length)}function eN(){let{expand:i}=this.getData();if(i===this._lastExpandBtnType)return;this._expandBtn&&this._expandBtn.clear(),this.createExpandNodeContent();let e;if(i===!1?(e=this._openExpandNode,this._lastExpandBtnType=!1):(e=this._closeExpandNode,this._lastExpandBtnType=!0),this._expandBtn){let{isShowExpandNum:t,expandBtnStyle:r,expandBtnNumHandler:n}=this.mindMap.opt;if(t)if(i)this._fillExpandNode.stroke("none");else{this._fillExpandNode.stroke({color:r.strokeColor});let s=this.sumNode(this.nodeData.children||[]);if(typeof n=="function"){let a=n(s,this);Yt(a)||(s=a)}e.text(String(s))}this._expandBtn.add(this._fillExpandNode).add(e)}}function tN(){this._expandBtn&&this.renderer.layout.renderExpandBtn(this,this._expandBtn)}function iN(){this.getChildrenLength()<=0||this.isRoot||(this._expandBtn?this.group.add(this._expandBtn):(this._expandBtn=new _e,this._expandBtn.on("mouseover",i=>{i.stopPropagation(),this._expandBtn.css({cursor:"pointer"})}),this._expandBtn.on("mouseout",i=>{i.stopPropagation(),this._expandBtn.css({cursor:"auto"})}),this._expandBtn.on("click",i=>{i.stopPropagation(),this.mindMap.execCommand("SET_NODE_EXPAND",this,!this.getData("expand")),this.mindMap.emit("expand_btn_click",this)}),this._expandBtn.on("dblclick",i=>{i.stopPropagation()}),this._expandBtn.addClass("smm-expand-btn"),this.group.add(this._expandBtn)),this._showExpandBtn=!0,this.updateExpandBtnNode(),this.updateExpandBtnPos())}function rN(){this._expandBtn&&this._showExpandBtn&&(this._expandBtn.remove(),this._showExpandBtn=!1)}function nN(){let{alwaysShowExpandBtn:i,notShowExpandBtn:e}=this.mindMap.opt;i||e||setTimeout(()=>{this.renderExpandBtn()},0)}function sN(){let{alwaysShowExpandBtn:i,notShowExpandBtn:e}=this.mindMap.opt;if(i||this._isMouseenter||e)return;let{isActive:t,expand:r}=this.getData();!t&&r&&setTimeout(()=>{this.removeExpandBtn()},0)}var Su,q2=T(()=>{Eu();yt();pe();Su={createExpandNodeContent:QT,updateExpandBtnNode:eN,updateExpandBtnPos:tN,renderExpandBtn:iN,removeExpandBtn:rN,showExpandBtn:nN,hideExpandBtn:sN,sumNode:JT}});function aN(i={}){this.mindMap.execCommand("SET_NODE_DATA",this,i)}function oN(i,e,t){this.mindMap.execCommand("SET_NODE_TEXT",this,i,e,t)}function lN(i){this.mindMap.execCommand("SET_NODE_IMAGE",this,i)}function hN(i){this.mindMap.execCommand("SET_NODE_ICON",this,i)}function dN(i,e){this.mindMap.execCommand("SET_NODE_HYPERLINK",this,i,e)}function cN(i){this.mindMap.execCommand("SET_NODE_NOTE",this,i)}function uN(i,e){this.mindMap.execCommand("SET_NODE_ATTACHMENT",this,i,e)}function fN(i){this.mindMap.execCommand("SET_NODE_TAG",this,i)}function mN(i){this.mindMap.execCommand("SET_NODE_SHAPE",this,i)}function pN(i,e){this.mindMap.execCommand("SET_NODE_STYLE",this,i,e)}function gN(i){this.mindMap.execCommand("SET_NODE_STYLES",this,i)}var Au,H2=T(()=>{Au={setData:aN,setText:oN,setImage:lN,setIcon:hN,setHyperlink:dN,setNote:cN,setAttachment:uN,setTag:fN,setShape:mN,setStyle:pN,setStyles:gN}});var xN,yN,vN,U2,bN,$o,$2=T(()=>{pe();xN='',yN='',vN='',U2=[{name:"\u4F18\u5148\u7EA7\u56FE\u6807",type:"priority",list:[{name:"1",icon:''},{name:"2",icon:''},{name:"3",icon:''},{name:"4",icon:''},{name:"5",icon:''},{name:"6",icon:''},{name:"7",icon:''},{name:"8",icon:''},{name:"9",icon:''},{name:"10",icon:''}]},{name:"\u8FDB\u5EA6\u56FE\u6807",type:"progress",list:[{name:"1",icon:''},{name:"2",icon:''},{name:"3",icon:''},{name:"4",icon:''},{name:"5",icon:''},{name:"6",icon:''},{name:"7",icon:''},{name:"8",icon:''}]},{name:"\u8868\u60C5\u56FE\u6807",type:"expression",list:[{name:"1",icon:''},{name:"2",icon:''},{name:"3",icon:''},{name:"4",icon:''},{name:"5",icon:''},{name:"6",icon:''},{name:"7",icon:''},{name:"8",icon:''},{name:"9",icon:''},{name:"10",icon:''},{name:"11",icon:''},{name:"12",icon:''},{name:"13",icon:''},{name:"14",icon:''},{name:"15",icon:''},{name:"16",icon:''},{name:"17",icon:''},{name:"18",icon:''},{name:"19",icon:''},{name:"20",icon:''}]},{name:"\u6807\u8BB0\u56FE\u6807",type:"sign",list:[{name:"1",icon:''},{name:"2",icon:''},{name:"3",icon:''},{name:"4",icon:''},{name:"5",icon:''},{name:"6",icon:''},{name:"7",icon:''},{name:"8",icon:''},{name:"9",icon:''},{name:"10",icon:''},{name:"11",icon:''},{name:"12",icon:''},{name:"13",icon:''},{name:"14",icon:''},{name:"15",icon:''},{name:"16",icon:''},{name:"17",icon:''},{name:"18",icon:''},{name:"19",icon:''},{name:"20",icon:''},{name:"21",icon:''},{name:"22",icon:''},{name:"23",icon:''}]}],bN=(i,e=[])=>{let t=i.split("_"),n=S2([...U2,...e]).find(s=>s.type===t[0]);if(n){let s=n.list.find(a=>a.name===t[1]);return s?s.icon:""}else return""},$o={hyperlink:xN,note:yN,attachment:vN,nodeIconList:U2,getNodeIconListIcon:bN}});function MN(){let i=this.getData("image");if(!i)return;i=(this.mindMap.renderer.renderTree.data.imgMap||{})[i]||i;let e=this.getImgShowSize(),t=new ji().load(i).size(...e),{defaultNodeImage:r}=this.mindMap.opt;if(r){let n=new Image;n.onerror=()=>{t.load(r)},n.src=i}return this.getData("imageTitle")&&t.attr("title",this.getData("imageTitle")),t.on("click",n=>{this.mindMap.emit("node_img_click",this,t,n)}),t.on("dblclick",n=>{this.mindMap.emit("node_img_dblclick",this,n,t)}),t.on("mouseenter",n=>{this.mindMap.emit("node_img_mouseenter",this,t,n)}),t.on("mouseleave",n=>{this.mindMap.emit("node_img_mouseleave",this,t,n)}),t.on("mousemove",n=>{this.mindMap.emit("node_img_mousemove",this,t,n)}),{node:t,width:e[0],height:e[1]}}function TN(){let{custom:i,width:e,height:t}=this.getData("imageSize");return i?[e,t]:U0(e,t,this.mindMap.themeConfig.imgMaxWidth,this.mindMap.themeConfig.imgMaxHeight)}function NN(){let i=this.getData();if(!i.icon||i.icon.length<=0)return[];let e=this.mindMap.themeConfig.iconSize;return i.icon.map(t=>{let r=$o.getNodeIconListIcon(t,this.mindMap.opt.iconList||[]),n=null;return/^{this.mindMap.emit("node_icon_click",this,t,s,n)}),n.on("mouseenter",s=>{this.mindMap.emit("node_icon_mouseenter",this,t,s,n)}),n.on("mouseleave",s=>{this.mindMap.emit("node_icon_mouseleave",this,t,s,n)}),{node:n,width:e,height:e}})}function EN(i){let e=this.hasCustomWidth(),t=typeof i=="string"?i:this.getData("text"),{textAutoWrapWidth:r,emptyTextMeasureHeightText:n}=this.mindMap.opt;r=e?this.customTextWidth:r;let s=new _e,a=!1;this.getData("resetRichText")&&(delete this.nodeData.data.resetRichText,a=!0),a&&!Yt(t)&&(w2(t)?t=T2(t):t=`

    ${t}

    `,this.setData({text:t}));let o=[],l=O2(this);Object.keys(l).forEach(y=>{o.push([y,l[y]])}),this.mindMap.commonCaches.measureRichtextNodeTextSizeEl||(this.mindMap.commonCaches.measureRichtextNodeTextSizeEl=document.createElement("div"),this.mindMap.commonCaches.measureRichtextNodeTextSizeEl.style.position="fixed",this.mindMap.commonCaches.measureRichtextNodeTextSizeEl.style.left="-999999px",this.mindMap.el.appendChild(this.mindMap.commonCaches.measureRichtextNodeTextSizeEl));let h=this.mindMap.commonCaches.measureRichtextNodeTextSizeEl;o.forEach(([y,b])=>{h.style[y]=b}),h.style.lineHeight=1.2;let d=`
    ${t}
    `;h.innerHTML=d;let c=h.children[0];c.classList.add("smm-richtext-node-wrap"),or(c),c.style.maxWidth=r+"px",e?c.style.width=this.customTextWidth+"px":c.style.width="";let{width:f,height:m}=c.getBoundingClientRect();if(m<=0){h.innerHTML=`

    ${n}

    `;let y=h.children[0];y.classList.add("smm-richtext-node-wrap"),m=y.getBoundingClientRect().height,h.innerHTML=d}f=Math.min(Math.ceil(f)+1,r),m=Math.ceil(m),s.attr("data-width",f),s.attr("data-height",m);let g=pn({el:h.children[0],width:f,height:m}),x={"line-height":1.2};return o.forEach(([y,b])=>{x[g2(y)]=b}),g.css(x),s.add(g),{node:s,nodeContent:g,width:f,height:m}}function SN(i){if(this.getData("needUpdate")&&delete this.nodeData.data.needUpdate,this.getData("richText"))return this.createRichTextNode(i);let e=typeof i=="string"?i:this.getData("text");this.getData("resetRichText")&&delete this.nodeData.data.resetRichText;let t=new _e,r=this.getStyle("fontSize",!1),n=this.getStyle("textAlign",!1),s=[];Yt(e)||(s=String(e).split(/\n/gim));let{textAutoWrapWidth:a,emptyTextMeasureHeightText:o}=this.mindMap.opt,l=s.length>1;s.forEach((c,f)=>{let m=c.split(""),g=[],x=[];for(;m.length;){let y=m.shift(),b=[...x,y].join("");wN(b,this.style).width<=a?x.push(y):(g.push(x.join("")),x=[y])}x.length>0&&g.push(x.join("")),g.length>1&&(l=!0),s[f]=g.join(` + `;return this.createPath(s)}},Q2=[C.SHAPE.RECTANGLE,C.SHAPE.DIAMOND,C.SHAPE.PARALLELOGRAM,C.SHAPE.ROUNDED_RECTANGLE,C.SHAPE.OCTAGONAL_RECTANGLE,C.SHAPE.OUTER_TRIANGULAR_RECTANGLE,C.SHAPE.INNER_TRIANGULAR_RECTANGLE,C.SHAPE.ELLIPSE,C.SHAPE.CIRCLE]});function rN(){let i=this.getData("generalization");return Array.isArray(i)?i:i?[i]:[]}function nN(){return this.formatGetGeneralization().length>0}function sN(){return!!this.formatGetGeneralization().find(e=>!e.range||e.range.length<=0)}function aN(i){return this._generalizationList.findIndex(e=>e.generalizationNode.uid===i.uid)}function oN(){if(this.isGeneralization||!this.checkHasGeneralization())return;let i=0,e=0;this.formatGetGeneralization().forEach((r,n)=>{let s=this._generalizationList[n];s||(s=this._generalizationList[n]={}),s.node=this,s.range=r.range,s.generalizationLine||(s.generalizationLine=this.lineDraw.path()),s.generalizationNode||(s.generalizationNode=new ca({data:{inserting:r.inserting,data:r},uid:Tt(),renderer:this.renderer,mindMap:this.mindMap,isGeneralization:!0})),delete r.inserting,s.generalizationNode.generalizationBelongNode=this,s.generalizationNode.width>i&&(i=s.generalizationNode.width),s.generalizationNode.height>e&&(e=s.generalizationNode.height),r.isActive&&this.renderer.addNodeToActiveList(s.generalizationNode)}),this._generalizationNodeWidth=i,this._generalizationNodeHeight=e}function lN(){this.isGeneralization||(this.removeGeneralization(),this.createGeneralizationNode())}function hN(i){if(this.isGeneralization)return;this.updateGeneralizationData();let e=this.formatGetGeneralization();if(e.length<=0||this.getData("expand")===!1){this.removeGeneralization();return}e.length!==this._generalizationList.length&&this.removeGeneralization(),this.createGeneralizationNode(),this.renderer.layout.renderGeneralization(this._generalizationList),this._generalizationList.forEach(t=>{this.style.generalizationLine(t.generalizationLine),t.generalizationNode.render(()=>{},i)})}function dN(){let i=this.getChildrenLength(),e=this.formatGetGeneralization(),t=[];e.forEach(r=>{if(!r.range){t.push(r);return}r.range.length>0&&r.range[0]<=i-1&&r.range[1]<=i-1&&t.push(r)}),t.length!==e.length&&this.setData({generalization:t})}function cN(){this.isGeneralization||(this._generalizationList.forEach(i=>{i.generalizationNode.style.onRemove(),i.generalizationLine&&(i.generalizationLine.remove(),i.generalizationLine=null),i.generalizationNode&&(this.renderer.removeNodeFromActiveList(i.generalizationNode),i.generalizationNode.remove(),i.generalizationNode=null)}),this._generalizationList=[],this.generalizationBelongNode&&this.nodeDraw.find(".generalization_"+this.generalizationBelongNode.uid).remove())}function uN(){this.isGeneralization||this._generalizationList.forEach(i=>{i.generalizationLine&&i.generalizationLine.hide(),i.generalizationNode&&i.generalizationNode.hide()})}function fN(){this.isGeneralization||this._generalizationList.forEach(i=>{i.generalizationLine&&i.generalizationLine.show(),i.generalizationNode&&i.generalizationNode.show()})}function mN(i){this._generalizationList.forEach(e=>{e.generalizationLine.opacity(i),e.generalizationNode.group.opacity(i)})}function pN(){let i=this.generalizationBelongNode,e=i.formatGetGeneralization(),t=i.getGeneralizationNodeIndex(this),r=e[t],s=this.getStyle("hoverRectColor")||this.mindMap.opt.hoverRectColor,a=s?{stroke:s}:null;Array.isArray(r.range)&&r.range.length>0?this.mindMap.renderer.highlightNode(i,r.range,a):this.mindMap.renderer.highlightNode(i,null,a)}function gN(){this.mindMap.renderer.closeHighlightNode()}var Du,J2=T(()=>{rh();pe();Du={formatGetGeneralization:rN,checkHasGeneralization:nN,checkHasSelfGeneralization:sN,getGeneralizationNodeIndex:aN,createGeneralizationNode:oN,updateGeneralization:lN,updateGeneralizationData:dN,renderGeneralization:hN,removeGeneralization:cN,hideGeneralization:uN,showGeneralization:fN,setGeneralizationOpacity:mN,handleGeneralizationMouseenter:pN,handleGeneralizationMouseleave:gN}});var xN,yN,vN,bN,wN,Yo,Ou=T(()=>{xN='',yN='',vN='',bN='',wN='',Yo={open:xN,close:yN,remove:vN,imgAdjust:bN,quickCreateChild:wN}});function MN(){if(this._openExpandNode)return;let{expandBtnSize:i,expandBtnIcon:e,isShowExpandNum:t}=this.mindMap.opt,{close:r,open:n}=e||{};t?(this._openExpandNode=new _e,this._openExpandNode.addClass("smm-expand-btn-text"),this._openExpandNode.attr({"text-anchor":"middle","dominant-baseline":"middle",x:i/2,y:2})):(this._openExpandNode=ke(n||Yo.open).size(i,i),this._openExpandNode.x(0).y(-i/2)),this._closeExpandNode=ke(r||Yo.close).size(i,i),this._closeExpandNode.x(0).y(-i/2),this._fillExpandNode=new Wi().size(i),this._fillExpandNode.x(0).y(-i/2),this.style.iconBtn(this._openExpandNode,this._closeExpandNode,this._fillExpandNode)}function TN(i=[]){return i.reduce((e,t)=>e+this.sumNode(t.children||[]),i.length)}function NN(){let{expand:i}=this.getData();if(i===this._lastExpandBtnType)return;this._expandBtn&&this._expandBtn.clear(),this.createExpandNodeContent();let e;if(i===!1?(e=this._openExpandNode,this._lastExpandBtnType=!1):(e=this._closeExpandNode,this._lastExpandBtnType=!0),this._expandBtn){let{isShowExpandNum:t,expandBtnStyle:r,expandBtnNumHandler:n}=this.mindMap.opt;if(t)if(i)this._fillExpandNode.stroke("none");else{this._fillExpandNode.stroke({color:r.strokeColor});let s=this.sumNode(this.nodeData.children||[]);if(typeof n=="function"){let a=n(s,this);Kt(a)||(s=a)}e.text(String(s))}this._expandBtn.add(this._fillExpandNode).add(e)}}function EN(){this._expandBtn&&this.renderer.layout.renderExpandBtn(this,this._expandBtn)}function SN(){this.getChildrenLength()<=0||this.isRoot||(this._expandBtn?this.group.add(this._expandBtn):(this._expandBtn=new Le,this._expandBtn.on("mouseover",i=>{i.stopPropagation(),this._expandBtn.css({cursor:"pointer"})}),this._expandBtn.on("mouseout",i=>{i.stopPropagation(),this._expandBtn.css({cursor:"auto"})}),this._expandBtn.on("click",i=>{i.stopPropagation(),this.mindMap.execCommand("SET_NODE_EXPAND",this,!this.getData("expand")),this.mindMap.emit("expand_btn_click",this)}),this._expandBtn.on("dblclick",i=>{i.stopPropagation()}),this._expandBtn.addClass("smm-expand-btn"),this.group.add(this._expandBtn)),this._showExpandBtn=!0,this.updateExpandBtnNode(),this.updateExpandBtnPos())}function AN(){this._expandBtn&&this._showExpandBtn&&(this._expandBtn.remove(),this._showExpandBtn=!1)}function kN(){let{alwaysShowExpandBtn:i,notShowExpandBtn:e}=this.mindMap.opt;i||e||setTimeout(()=>{this.renderExpandBtn()},0)}function CN(){let{alwaysShowExpandBtn:i,notShowExpandBtn:e}=this.mindMap.opt;if(i||this._isMouseenter||e)return;let{isActive:t,expand:r}=this.getData();!t&&r&&setTimeout(()=>{this.removeExpandBtn()},0)}var Bu,e6=T(()=>{Ou();wt();pe();Bu={createExpandNodeContent:MN,updateExpandBtnNode:NN,updateExpandBtnPos:EN,renderExpandBtn:SN,removeExpandBtn:AN,showExpandBtn:kN,hideExpandBtn:CN,sumNode:TN}});function _N(i={}){this.mindMap.execCommand("SET_NODE_DATA",this,i)}function LN(i,e,t){this.mindMap.execCommand("SET_NODE_TEXT",this,i,e,t)}function IN(i){this.mindMap.execCommand("SET_NODE_IMAGE",this,i)}function zN(i){this.mindMap.execCommand("SET_NODE_ICON",this,i)}function RN(i,e){this.mindMap.execCommand("SET_NODE_HYPERLINK",this,i,e)}function DN(i){this.mindMap.execCommand("SET_NODE_NOTE",this,i)}function ON(i,e){this.mindMap.execCommand("SET_NODE_ATTACHMENT",this,i,e)}function BN(i){this.mindMap.execCommand("SET_NODE_TAG",this,i)}function PN(i){this.mindMap.execCommand("SET_NODE_SHAPE",this,i)}function FN(i,e){this.mindMap.execCommand("SET_NODE_STYLE",this,i,e)}function qN(i){this.mindMap.execCommand("SET_NODE_STYLES",this,i)}var Pu,t6=T(()=>{Pu={setData:_N,setText:LN,setImage:IN,setIcon:zN,setHyperlink:RN,setNote:DN,setAttachment:ON,setTag:BN,setShape:PN,setStyle:FN,setStyles:qN}});var HN,UN,$N,i6,jN,Xo,r6=T(()=>{pe();HN='',UN='',$N='',i6=[{name:"\u4F18\u5148\u7EA7\u56FE\u6807",type:"priority",list:[{name:"1",icon:''},{name:"2",icon:''},{name:"3",icon:''},{name:"4",icon:''},{name:"5",icon:''},{name:"6",icon:''},{name:"7",icon:''},{name:"8",icon:''},{name:"9",icon:''},{name:"10",icon:''}]},{name:"\u8FDB\u5EA6\u56FE\u6807",type:"progress",list:[{name:"1",icon:''},{name:"2",icon:''},{name:"3",icon:''},{name:"4",icon:''},{name:"5",icon:''},{name:"6",icon:''},{name:"7",icon:''},{name:"8",icon:''}]},{name:"\u8868\u60C5\u56FE\u6807",type:"expression",list:[{name:"1",icon:''},{name:"2",icon:''},{name:"3",icon:''},{name:"4",icon:''},{name:"5",icon:''},{name:"6",icon:''},{name:"7",icon:''},{name:"8",icon:''},{name:"9",icon:''},{name:"10",icon:''},{name:"11",icon:''},{name:"12",icon:''},{name:"13",icon:''},{name:"14",icon:''},{name:"15",icon:''},{name:"16",icon:''},{name:"17",icon:''},{name:"18",icon:''},{name:"19",icon:''},{name:"20",icon:''}]},{name:"\u6807\u8BB0\u56FE\u6807",type:"sign",list:[{name:"1",icon:''},{name:"2",icon:''},{name:"3",icon:''},{name:"4",icon:''},{name:"5",icon:''},{name:"6",icon:''},{name:"7",icon:''},{name:"8",icon:''},{name:"9",icon:''},{name:"10",icon:''},{name:"11",icon:''},{name:"12",icon:''},{name:"13",icon:''},{name:"14",icon:''},{name:"15",icon:''},{name:"16",icon:''},{name:"17",icon:''},{name:"18",icon:''},{name:"19",icon:''},{name:"20",icon:''},{name:"21",icon:''},{name:"22",icon:''},{name:"23",icon:''}]}],jN=(i,e=[])=>{let t=i.split("_"),n=q2([...i6,...e]).find(s=>s.type===t[0]);if(n){let s=n.list.find(a=>a.name===t[1]);return s?s.icon:""}else return""},Xo={hyperlink:HN,note:UN,attachment:$N,nodeIconList:i6,getNodeIconListIcon:jN}});function WN(){let i=this.getData("image");if(!i)return;i=(this.mindMap.renderer.renderTree.data.imgMap||{})[i]||i;let e=this.getImgShowSize(),t=new $i().load(i).size(...e),{defaultNodeImage:r}=this.mindMap.opt;if(r){let n=new Image;n.onerror=()=>{t.load(r)},n.src=i}return this.getData("imageTitle")&&t.attr("title",this.getData("imageTitle")),t.on("click",n=>{this.mindMap.emit("node_img_click",this,t,n)}),t.on("dblclick",n=>{this.mindMap.emit("node_img_dblclick",this,n,t)}),t.on("mouseenter",n=>{this.mindMap.emit("node_img_mouseenter",this,t,n)}),t.on("mouseleave",n=>{this.mindMap.emit("node_img_mouseleave",this,t,n)}),t.on("mousemove",n=>{this.mindMap.emit("node_img_mousemove",this,t,n)}),{node:t,width:e[0],height:e[1]}}function VN(){let{custom:i,width:e,height:t}=this.getData("imageSize");return i?[e,t]:K0(e,t,this.mindMap.themeConfig.imgMaxWidth,this.mindMap.themeConfig.imgMaxHeight)}function YN(){let i=this.getData();if(!i.icon||i.icon.length<=0)return[];let e=this.mindMap.themeConfig.iconSize;return i.icon.map(t=>{let r=Xo.getNodeIconListIcon(t,this.mindMap.opt.iconList||[]),n=null;return/^{this.mindMap.emit("node_icon_click",this,t,s,n)}),n.on("mouseenter",s=>{this.mindMap.emit("node_icon_mouseenter",this,t,s,n)}),n.on("mouseleave",s=>{this.mindMap.emit("node_icon_mouseleave",this,t,s,n)}),{node:n,width:e,height:e}})}function XN(i){let e=this.hasCustomWidth(),t=typeof i=="string"?i:this.getData("text"),{textAutoWrapWidth:r,emptyTextMeasureHeightText:n}=this.mindMap.opt;r=e?this.customTextWidth:r;let s=new Le,a=!1;this.getData("resetRichText")&&(delete this.nodeData.data.resetRichText,a=!0),a&&!Kt(t)&&(D2(t)?t=B2(t):t=`

    ${t}

    `,this.setData({text:t}));let o=[],l=K2(this);Object.keys(l).forEach(y=>{o.push([y,l[y]])}),this.mindMap.commonCaches.measureRichtextNodeTextSizeEl||(this.mindMap.commonCaches.measureRichtextNodeTextSizeEl=document.createElement("div"),this.mindMap.commonCaches.measureRichtextNodeTextSizeEl.style.position="fixed",this.mindMap.commonCaches.measureRichtextNodeTextSizeEl.style.left="-999999px",this.mindMap.el.appendChild(this.mindMap.commonCaches.measureRichtextNodeTextSizeEl));let h=this.mindMap.commonCaches.measureRichtextNodeTextSizeEl;o.forEach(([y,b])=>{h.style[y]=b}),h.style.lineHeight=1.2;let d=`
    ${t}
    `;h.innerHTML=d;let c=h.children[0];c.classList.add("smm-richtext-node-wrap"),sr(c),c.style.maxWidth=r+"px",e?c.style.width=this.customTextWidth+"px":c.style.width="";let{width:f,height:m}=c.getBoundingClientRect();if(m<=0){h.innerHTML=`

    ${n}

    `;let y=h.children[0];y.classList.add("smm-richtext-node-wrap"),m=y.getBoundingClientRect().height,h.innerHTML=d}f=Math.min(Math.ceil(f)+1,r),m=Math.ceil(m),s.attr("data-width",f),s.attr("data-height",m);let g=mn({el:h.children[0],width:f,height:m}),x={"line-height":1.2};return o.forEach(([y,b])=>{x[_2(y)]=b}),g.css(x),s.add(g),{node:s,nodeContent:g,width:f,height:m}}function KN(i){if(this.getData("needUpdate")&&delete this.nodeData.data.needUpdate,this.getData("richText"))return this.createRichTextNode(i);let e=typeof i=="string"?i:this.getData("text");this.getData("resetRichText")&&delete this.nodeData.data.resetRichText;let t=new Le,r=this.getStyle("fontSize",!1),n=this.getStyle("textAlign",!1),s=[];Kt(e)||(s=String(e).split(/\n/gim));let{textAutoWrapWidth:a,emptyTextMeasureHeightText:o}=this.mindMap.opt,l=s.length>1;s.forEach((c,f)=>{let m=c.split(""),g=[],x=[];for(;m.length;){let y=m.shift(),b=[...x,y].join("");GN(b,this.style).width<=a?x.push(y):(g.push(x.join("")),x=[y])}x.length>0&&g.push(x.join("")),g.length>1&&(l=!0),s[f]=g.join(` `)}),s=s.join(` -`).replace(/\n$/g,"").split(/\n/gim),s.forEach((c,f)=>{c===""&&(c="\uFEFF");let m=new Ce().text(c);m.addClass("smm-text-node-wrap"),m.attr("text-anchor",{left:"start",center:"middle",right:"end"}[n]||"start"),this.style.text(m),m.y(r*js*f+(js-1)*r/2),t.add(m)});let{width:h,height:d}=t.bbox();if(d<=0){let c=new Ce().text(o);this.style.text(c),d=c.bbox().height}return h=Math.min(Math.ceil(h),a),d=Math.ceil(d),t.attr("data-width",h),t.attr("data-height",d),t.attr("data-ismultiLine",l||s.length>1),{node:t,width:h,height:d}}function AN(){let{hyperlink:i,hyperlinkTitle:e}=this.getData();if(!i)return;let{customHyperlinkJump:t,hyperlinkIcon:r}=this.mindMap.opt,{icon:n,style:s}=r,a=this.getNodeIconSize("hyperlinkIcon"),o=new Ae().size(a,a),l=new cn().to(i).target("_blank");l.node.addEventListener("click",d=>{typeof t=="function"&&(d.preventDefault(),t(i,this))}),e&&o.add(Ae(`${e}`)),l.rect(a,a).fill({color:"transparent"});let h=Ae(n||$o.hyperlink).size(a,a);return this.style.iconNode(h,s.color),l.add(h),o.add(l),{node:o,width:a,height:a}}function kN(){let i=this.getData("tag");if(!i||i.length<=0)return[];let{maxTag:e,tagsColorMap:t}=this.mindMap.opt;t=t||{};let r=[];return i.slice(0,e).forEach((n,s)=>{let a="",o={...j2};typeof n=="string"?a=n:(a=n.text,o={...j2,...n.style});let l=typeof o.width<"u",h=new _e;h.on("click",()=>{this.mindMap.emit("node_tag_click",this,n,s,h)});let d=new Ce().text(a);this.style.tagText(d,o);let{width:c,height:f}=d.bbox(),m=l?o.width:c+o.paddingX*2,g=l?Math.max(m,c):m,x=Math.max(o.height,f);l?d.x((g-c)/2):d.x(l?0:o.paddingX),d.cy(-x/2);let y=new Ve().size(m,o.height).cy(-x/2);l&&y.x((g-m)/2),this.style.tagRect(y,{...o,fill:o.fill||t[d.node.textContent]||G0(d.node.textContent)}),h.add(y).add(d),r.push({node:h,width:g,height:x})}),r}function CN(){if(!this.getData("note"))return null;let{icon:i,style:e}=this.mindMap.opt.noteIcon,t=this.getNodeIconSize("noteIcon"),r=new Ae().attr("cursor","pointer").addClass("smm-node-note").size(t,t);r.add(new Ve().size(t,t).fill({color:"transparent"}));let n=Ae(i||$o.note).size(t,t);return this.style.iconNode(n,e.color),r.add(n),this.mindMap.opt.customNoteContentShow||(this.noteEl||(this.noteEl=document.createElement("div"),this.noteEl.style.cssText=` +`).replace(/\n$/g,"").split(/\n/gim),s.forEach((c,f)=>{c===""&&(c="\uFEFF");let m=new _e().text(c);m.addClass("smm-text-node-wrap"),m.attr("text-anchor",{left:"start",center:"middle",right:"end"}[n]||"start"),this.style.text(m),m.y(r*Gs*f+(Gs-1)*r/2),t.add(m)});let{width:h,height:d}=t.bbox();if(d<=0){let c=new _e().text(o);this.style.text(c),d=c.bbox().height}return h=Math.min(Math.ceil(h),a),d=Math.ceil(d),t.attr("data-width",h),t.attr("data-height",d),t.attr("data-ismultiLine",l||s.length>1),{node:t,width:h,height:d}}function ZN(){let{hyperlink:i,hyperlinkTitle:e}=this.getData();if(!i)return;let{customHyperlinkJump:t,hyperlinkIcon:r}=this.mindMap.opt,{icon:n,style:s}=r,a=this.getNodeIconSize("hyperlinkIcon"),o=new ke().size(a,a),l=new dn().to(i).target("_blank");l.node.addEventListener("click",d=>{typeof t=="function"&&(d.preventDefault(),t(i,this))}),e&&o.add(ke(`${e}`)),l.rect(a,a).fill({color:"transparent"});let h=ke(n||Xo.hyperlink).size(a,a);return this.style.iconNode(h,s.color),l.add(h),o.add(l),{node:o,width:a,height:a}}function QN(){let i=this.getData("tag");if(!i||i.length<=0)return[];let{maxTag:e,tagsColorMap:t}=this.mindMap.opt;t=t||{};let r=[];return i.slice(0,e).forEach((n,s)=>{let a="",o={...n6};typeof n=="string"?a=n:(a=n.text,o={...n6,...n.style});let l=typeof o.width<"u",h=new Le;h.on("click",()=>{this.mindMap.emit("node_tag_click",this,n,s,h)});let d=new _e().text(a);this.style.tagText(d,o);let{width:c,height:f}=d.bbox(),m=l?o.width:c+o.paddingX*2,g=l?Math.max(m,c):m,x=Math.max(o.height,f);l?d.x((g-c)/2):d.x(l?0:o.paddingX),d.cy(-x/2);let y=new Ve().size(m,o.height).cy(-x/2);l&&y.x((g-m)/2),this.style.tagRect(y,{...o,fill:o.fill||t[d.node.textContent]||J0(d.node.textContent)}),h.add(y).add(d),r.push({node:h,width:g,height:x})}),r}function JN(){if(!this.getData("note"))return null;let{icon:i,style:e}=this.mindMap.opt.noteIcon,t=this.getNodeIconSize("noteIcon"),r=new ke().attr("cursor","pointer").addClass("smm-node-note").size(t,t);r.add(new Ve().size(t,t).fill({color:"transparent"}));let n=ke(i||Xo.note).size(t,t);return this.style.iconNode(n,e.color),r.add(n),this.mindMap.opt.customNoteContentShow||(this.noteEl||(this.noteEl=document.createElement("div"),this.noteEl.style.cssText=` position: fixed; padding: 10px; border-radius: 5px; @@ -62,24 +62,24 @@ var Zb=Object.create;var T0=Object.defineProperty;var Qb=Object.getOwnPropertyDe display: none; background-color: #fff; z-index: ${this.mindMap.opt.nodeNoteTooltipZIndex} - `,(this.mindMap.opt.customInnerElsAppendTo||document.body).appendChild(this.noteEl)),this.noteEl.innerText=this.getData("note")),r.on("mouseover",()=>{let{left:s,top:a}=this.getNoteContentPosition();this.mindMap.opt.customNoteContentShow?this.mindMap.opt.customNoteContentShow.show(this.getData("note"),s,a,this):(this.noteEl.style.left=s+"px",this.noteEl.style.top=a+"px",this.noteEl.style.display="block")}),r.on("mouseout",()=>{this.mindMap.opt.customNoteContentShow?this.mindMap.opt.customNoteContentShow.hide():this.noteEl.style.display="none"}),r.on("click",s=>{this.mindMap.emit("node_note_click",this,s,r)}),r.on("dblclick",s=>{this.mindMap.emit("node_note_dblclick",this,s,r)}),{node:r,width:t,height:t}}function _N(){let{attachmentUrl:i,attachmentName:e}=this.getData();if(!i)return;let t=this.getNodeIconSize("attachmentIcon"),{icon:r,style:n}=this.mindMap.opt.attachmentIcon,s=new Ae().attr("cursor","pointer").size(t,t);e&&s.add(Ae(`${e}`)),s.add(new Ve().size(t,t).fill({color:"transparent"}));let a=Ae(r||$o.attachment).size(t,t);return this.style.iconNode(a,n.color),s.add(a),s.on("click",o=>{this.mindMap.emit("node_attachmentClick",this,o,s)}),s.on("contextmenu",o=>{this.mindMap.emit("node_attachmentContextmenu",this,o,s)}),{node:s,width:t,height:t}}function LN(i){let{style:e}=this.mindMap.opt[i];return Yt(e.size)?this.mindMap.themeConfig.iconSize:e.size}function IN(){let i=this.getNodeIconSize("noteIcon"),{scaleY:e}=this.mindMap.view.getTransformData().transform,t=i*e,{left:r,top:n}=this._noteData.node.node.getBoundingClientRect();return n+=t,{left:r,top:n}}function zN(i){this.mindMap.commonCaches.measureCustomNodeContentSizeEl||(this.mindMap.commonCaches.measureCustomNodeContentSizeEl=document.createElement("div"),this.mindMap.commonCaches.measureCustomNodeContentSizeEl.style.cssText=` + `,(this.mindMap.opt.customInnerElsAppendTo||document.body).appendChild(this.noteEl)),this.noteEl.innerText=this.getData("note")),r.on("mouseover",()=>{let{left:s,top:a}=this.getNoteContentPosition();this.mindMap.opt.customNoteContentShow?this.mindMap.opt.customNoteContentShow.show(this.getData("note"),s,a,this):(this.noteEl.style.left=s+"px",this.noteEl.style.top=a+"px",this.noteEl.style.display="block")}),r.on("mouseout",()=>{this.mindMap.opt.customNoteContentShow?this.mindMap.opt.customNoteContentShow.hide():this.noteEl.style.display="none"}),r.on("click",s=>{this.mindMap.emit("node_note_click",this,s,r)}),r.on("dblclick",s=>{this.mindMap.emit("node_note_dblclick",this,s,r)}),{node:r,width:t,height:t}}function eE(){let{attachmentUrl:i,attachmentName:e}=this.getData();if(!i)return;let t=this.getNodeIconSize("attachmentIcon"),{icon:r,style:n}=this.mindMap.opt.attachmentIcon,s=new ke().attr("cursor","pointer").size(t,t);e&&s.add(ke(`${e}`)),s.add(new Ve().size(t,t).fill({color:"transparent"}));let a=ke(r||Xo.attachment).size(t,t);return this.style.iconNode(a,n.color),s.add(a),s.on("click",o=>{this.mindMap.emit("node_attachmentClick",this,o,s)}),s.on("contextmenu",o=>{this.mindMap.emit("node_attachmentContextmenu",this,o,s)}),{node:s,width:t,height:t}}function tE(i){let{style:e}=this.mindMap.opt[i];return Kt(e.size)?this.mindMap.themeConfig.iconSize:e.size}function iE(){let i=this.getNodeIconSize("noteIcon"),{scaleY:e}=this.mindMap.view.getTransformData().transform,t=i*e,{left:r,top:n}=this._noteData.node.node.getBoundingClientRect();return n+=t,{left:r,top:n}}function rE(i){this.mindMap.commonCaches.measureCustomNodeContentSizeEl||(this.mindMap.commonCaches.measureCustomNodeContentSizeEl=document.createElement("div"),this.mindMap.commonCaches.measureCustomNodeContentSizeEl.style.cssText=` position: fixed; left: -99999px; top: -99999px; - `,this.mindMap.el.appendChild(this.mindMap.commonCaches.measureCustomNodeContentSizeEl)),this.mindMap.commonCaches.measureCustomNodeContentSizeEl.innerHTML="",this.mindMap.commonCaches.measureCustomNodeContentSizeEl.appendChild(i);let e=this.mindMap.commonCaches.measureCustomNodeContentSizeEl.getBoundingClientRect();return{width:e.width,height:e.height}}function RN(){return!!this._customNodeContent}var wN,j2,ku,G2=T(()=>{pe();yt();$2();$e();wN=(i,e)=>{let t=new _e,r=new Ce().text(i);return e.text(r),t.add(r),t.bbox()},j2={radius:3,fontSize:12,fill:"",height:20,paddingX:8};ku={createImgNode:MN,getImgShowSize:TN,createIconNode:NN,createRichTextNode:EN,createTextNode:SN,createHyperlinkNode:AN,createTagNode:kN,createNoteNode:CN,createAttachmentNode:_N,getNoteContentPosition:IN,getNodeIconSize:LN,measureCustomNodeContentSize:zN,isUseCustomNodeContent:RN}});function DN(){if(this.getChildrenLength()<=0||this.isRoot)return;let{alwaysShowExpandBtn:i,notShowExpandBtn:e,expandBtnSize:t}=this.mindMap.opt;if(!i&&!e){let{width:r,height:n}=this;this._unVisibleRectRegionNode||(this._unVisibleRectRegionNode=new Ve,this._unVisibleRectRegionNode.fill({color:"transparent"})),this.group.add(this._unVisibleRectRegionNode),this.renderer.layout.renderExpandBtnRect(this._unVisibleRectRegionNode,t,r,n,this)}}function ON(){this._unVisibleRectRegionNode&&(this._unVisibleRectRegionNode.remove(),this._unVisibleRectRegionNode=null)}function BN(){this.needRerenderExpandBtnPlaceholderRect&&(this.needRerenderExpandBtnPlaceholderRect=!1,this.renderExpandBtnPlaceholderRect()),this.getChildrenLength()>0?this._unVisibleRectRegionNode||this.renderExpandBtnPlaceholderRect():this._unVisibleRectRegionNode&&this.clearExpandBtnPlaceholderRect()}var Cu,V2=T(()=>{yt();Cu={renderExpandBtnPlaceholderRect:DN,clearExpandBtnPlaceholderRect:ON,updateExpandBtnPlaceholderRect:BN}});function PN(){this.checkEnableDragModifyNodeWidth()&&(this._dragHandleNodes=null,this.dragHandleWidth=4,this.dragHandleMousedownX=0,this.isDragHandleMousedown=!1,this.dragHandleIndex=0,this.dragHandleMousedownCustomTextWidth=0,this.dragHandleMousedownBodyCursor="",this.dragHandleMousedownLeft=0,this.onDragMousemoveHandle=this.onDragMousemoveHandle.bind(this),window.addEventListener("mousemove",this.onDragMousemoveHandle),this.onDragMouseupHandle=this.onDragMouseupHandle.bind(this),window.addEventListener("mouseup",this.onDragMouseupHandle),this.mindMap.on("node_mouseup",this.onDragMouseupHandle))}function FN(i){if(!this.isDragHandleMousedown)return;i.stopPropagation(),i.preventDefault();let{minNodeTextModifyWidth:e,maxNodeTextModifyWidth:t,isUseCustomNodeContent:r,customCreateNodeContent:n}=this.mindMap.opt,s=r&&n&&this._customNodeContent;document.body.style.cursor="ew-resize",this.group.css({cursor:"ew-resize"});let{scaleX:a}=this.mindMap.draw.transform(),o=i.clientX-this.dragHandleMousedownX,l=this.dragHandleMousedownCustomTextWidth+(this.dragHandleIndex===0?-o:o)/a;if(l=Math.max(l,e),t!==-1&&(l=Math.min(l,t)),!s&&this.getData("image")){let h=this.getImgShowSize();this._rectInfo.textContentWidth-this.customTextWidth+l<=h[0]&&(l=h[0]+this.customTextWidth-this._rectInfo.textContentWidth)}this.customTextWidth=l,this.dragHandleIndex===0&&(this.left=this.dragHandleMousedownLeft+o/a),this.reRender(s?[]:["text"],{ignoreUpdateCustomTextWidth:!0})}function qN(){this.isDragHandleMousedown&&(document.body.style.cursor=this.dragHandleMousedownBodyCursor,this.group.css({cursor:"default"}),this.isDragHandleMousedown=!1,this.dragHandleMousedownX=0,this.dragHandleIndex=0,this.dragHandleMousedownCustomTextWidth=0,this.setData({customTextWidth:this.customTextWidth}),this.mindMap.render(),this.mindMap.emit("dragModifyNodeWidthEnd",this))}function HN(){let i=[new Ve,new Ve];return i.forEach((e,t)=>{e.size(this.dragHandleWidth,this.height).fill({color:"transparent"}).css({cursor:"ew-resize"}),e.on("mousedown",r=>{r.stopPropagation(),r.preventDefault(),this.dragHandleMousedownX=r.clientX,this.dragHandleIndex=t,this.dragHandleMousedownCustomTextWidth=this.customTextWidth===void 0?this._textData?this._textData.width:this.width:this.customTextWidth,this.dragHandleMousedownBodyCursor=document.body.style.cursor,this.dragHandleMousedownLeft=this.left,this.isDragHandleMousedown=!0})}),i}function UN(){this.checkEnableDragModifyNodeWidth()&&(this._dragHandleNodes||(this._dragHandleNodes=this.createDragHandleNode()),this.getData("isActive")?(this._dragHandleNodes.forEach(i=>{i.height(this.height),this.group.add(i)}),this._dragHandleNodes[1].x(this.width-this.dragHandleWidth)):this._dragHandleNodes.forEach(i=>{i.remove()}))}var _u,W2=T(()=>{yt();_u={initDragHandle:PN,onDragMousemoveHandle:FN,onDragMouseupHandle:qN,createDragHandleNode:HN,updateDragHandle:UN}});function $N(){this.mindMap.cooperate&&(this._userListGroup=new _e,this.group.add(this._userListGroup))}function jN(i){let{avatarSize:e,fontSize:t}=this.mindMap.opt.cooperateStyle,r=new _e,n=i.isMore?i.name:String(i.name)[0],s=new Wi().size(e,e);s.fill({color:i.color||G0(n)});let a=new Ce().text(n).fill({color:"#fff"}).css({"font-size":t+"px"}).dx(-t/2).dy((e-t)/2);return r.add(s).add(a),r}function GN(i){let{avatarSize:e}=this.mindMap.opt.cooperateStyle;return new ji().load(i.avatar).size(e,e)}function VN(){if(!this._userListGroup)return;let{avatarSize:i}=this.mindMap.opt.cooperateStyle;this._userListGroup.clear();let e=this.userList.length,t=Math.floor(this.width/i),r=[];e>t?r.push(...this.userList.slice(0,t-1),{isMore:!0,name:"+"+(e-t+1)}):r.push(...this.userList),r.forEach((n,s)=>{let a=null;n.avatar?a=this.createImageAvatar(n):a=this.createTextAvatar(n),a.on("click",o=>{this.mindMap.emit("node_cooperate_avatar_click",n,this,a,o)}),a.on("mouseenter",o=>{this.mindMap.emit("node_cooperate_avatar_mouseenter",n,this,a,o)}),a.on("mouseleave",o=>{this.mindMap.emit("node_cooperate_avatar_mouseleave",n,this,a,o)}),a.x(s*i).cy(-i/2),this._userListGroup.add(a)})}function WN(i){this.userList.find(e=>e.id==i.id)||(this.userList.push(i),this.updateUserListNode())}function YN(i){let e=this.userList.findIndex(t=>t.id==i.id);e!==-1&&(this.userList.splice(e,1),this.updateUserListNode())}function XN(){this.userList=[],this.updateUserListNode()}var Lu,Y2=T(()=>{yt();pe();Lu={createUserListNode:$N,updateUserListNode:VN,createTextAvatar:jN,createImageAvatar:GN,addUser:WN,removeUser:YN,emptyUser:XN}});function KN(){this.isGeneralization||(this._quickCreateChildBtn=null,this._showQuickCreateChildBtn=!1)}function ZN(){if(!(this.isGeneralization||this.getChildrenLength()>0)){if(this._quickCreateChildBtn)this.group.add(this._quickCreateChildBtn);else{let{quickCreateChildBtnIcon:i,expandBtnStyle:e,expandBtnSize:t}=this.mindMap.opt,{icon:r,style:n}=i,{color:s,fill:a}=e||{color:"#808080",fill:"#fff"};s=n.color||s;let o=Ae(r||Uo.quickCreateChild).size(t,t);o.css({cursor:"pointer"}),o.x(0).y(-t/2),this.style.iconNode(o,s);let l=new Wi().size(t);l.x(0).y(-t/2),l.fill({color:a}).css({cursor:"pointer"}),this._quickCreateChildBtn=new _e,this._quickCreateChildBtn.add(l).add(o),this._quickCreateChildBtn.on("click",h=>{h.stopPropagation(),this.mindMap.emit("quick_create_btn_click",this);let{customQuickCreateChildBtnClick:d}=this.mindMap.opt;if(typeof d=="function"){d(this);return}this.mindMap.execCommand("INSERT_CHILD_NODE",!0,[this])}),this._quickCreateChildBtn.on("dblclick",h=>{h.stopPropagation()}),this._quickCreateChildBtn.addClass("smm-quick-create-child-btn"),this.group.add(this._quickCreateChildBtn)}this._showQuickCreateChildBtn=!0,this.renderer.layout.renderExpandBtn(this,this._quickCreateChildBtn)}}function QN(){this.isGeneralization||this._quickCreateChildBtn&&this._showQuickCreateChildBtn&&(this._quickCreateChildBtn.remove(),this._showQuickCreateChildBtn=!1)}function JN(){if(this.isGeneralization)return;let{isActive:i}=this.getData();i||this.removeQuickCreateChildBtn()}var Iu,X2=T(()=>{Eu();yt();Iu={initQuickCreateChildBtn:KN,showQuickCreateChildBtn:ZN,removeQuickCreateChildBtn:QN,hideQuickCreateChildBtn:JN}});function eE(i,e,t,r,n){let{imgTextMargin:s}=this.mindMap.opt;return i==="v"?r>0&&n>0?s:0:e>0&&t>0?s:0}function tE(i){let e=0,t=this._tagData.reduce((r,n)=>(e=Math.max(e,n.height),r+=n.width),0);return t+=(this._tagData.length-1)*i,{width:t,height:e}}function iE(){if(this.isUseCustomNodeContent()){let C=this.measureCustomNodeContentSize(this._customNodeContent);return{width:this.hasCustomWidth()?this.customTextWidth:C.width,height:C.height}}let{TAG_PLACEMENT:i,IMG_PLACEMENT:e}=k,{textContentMargin:t}=this.mindMap.opt,n=(this.getStyle("tagPlacement")||i.RIGHT)===i.BOTTOM,s=this.getStyle("imgPlacement")||e.TOP,a=0,o=0,l=0,h=0,d=0,c=0,f=0;if(this._imgData&&(a=this._imgData.width,o=this._imgData.height),this.mindMap.nodeInnerPrefixList.forEach(C=>{let I=this[`_${C.name}Data`];I&&(l+=I.width,h=Math.max(h,I.height),f++)}),this._prefixData&&(l+=this._prefixData.width,h=Math.max(h,this._prefixData.height),f++),this._iconData.length>0&&(l+=this._iconData.reduce((C,I)=>(h=Math.max(h,I.height),C+=I.width),0)+(this._iconData.length-1)*t,f++),this._textData&&(l+=this._textData.width,h=Math.max(h,this._textData.height),f++),this._hyperlinkData&&(l+=this._hyperlinkData.width,h=Math.max(h,this._hyperlinkData.height),f++),this._tagData.length>0){let{width:C,height:I}=this.getTagContentSize(t);n?(d=C,c=I):(l+=C,h=Math.max(h,I),f++)}this._noteData&&(l+=this._noteData.width,h=Math.max(h,this._noteData.height),f++),this._attachmentData&&(l+=this._attachmentData.width,h=Math.max(h,this._attachmentData.height),f++),this._postfixData&&(l+=this._postfixData.width,h=Math.max(h,this._postfixData.height),f++),this.mindMap.nodeInnerPostfixList.forEach(C=>{let I=this[`_${C.name}Data`];I&&(l+=I.width,h=Math.max(h,I.height),f++)}),l+=(f-1)*t,n&&l>0&&c>0&&(this._rectInfo.textContentWidthWithoutTag=l,l=Math.max(l,d),h=h+t+c),this._rectInfo.textContentWidth=l,this._rectInfo.textContentHeight=h;let m=0,g=0;[e.TOP,e.BOTTOM].includes(s)?(m=Math.max(a,l),g=o+h+this.getImgTextMarin("v",0,0,o,h)):(m=a+l+this.getImgTextMarin("h",a,l),g=Math.max(o,h));let{paddingX:x,paddingY:y}=this.getPaddingVale(),{paddingX:b,paddingY:S}=this.shapeInstance.getShapePadding(m,g,x,y);this.shapePadding.paddingX=b,this.shapePadding.paddingY=S;let A=this.getBorderWidth();return{width:m+x*2+b*2+A,height:g+y*2+S*2+A}}function rE(){if(!this.group)return;this.group.clear();let{hoverRectPadding:i,openRealtimeRenderOnNodeTextEdit:e,textContentMargin:t,addCustomContentToNode:r}=this.mindMap.opt,{width:n,height:s}=this,{paddingX:a,paddingY:o}=this.getPaddingVale(),l=this.getBorderWidth()/2;a+=this.shapePadding.paddingX+l,o+=this.shapePadding.paddingY+l,this.shapeNode=this.shapeInstance.createShape(),this.shapeNode.addClass("smm-node-shape"),this.shapeNode.translate(l,l),this.style.shape(this.shapeNode),this.group.add(this.shapeNode),this.renderExpandBtnPlaceholderRect(),this.createUserListNode&&this.createUserListNode(),this.isGeneralization&&this.generalizationBelongNode&&this.group.addClass("generalization_"+this.generalizationBelongNode.uid);let h=()=>{this.hoverNode=new Ve().size(n+i*2,s+i*2).x(-i).y(-i),this.hoverNode.addClass("smm-hover-node"),this.style.hoverNode(this.hoverNode,n,s),this.group.add(this.hoverNode)};if(this.isUseCustomNodeContent()){let le=pn({el:this._customNodeContent,width:n,height:s});this.group.add(le),h();return}let{IMG_PLACEMENT:d,TAG_PLACEMENT:c}=k,f=this.getStyle("imgPlacement")||d.TOP,g=(this.getStyle("tagPlacement")||c.RIGHT)===c.BOTTOM,{textContentWidth:x,textContentHeight:y,textContentWidthWithoutTag:b}=this._rectInfo,S=y,A=0,C=0,I=this._tagData&&this._tagData.length>0;if(I){let le=this.getTagContentSize(t);A=le.width,C=le.height,g&&(y-=C+t)}let O=0,q=0;if(this._imgData)switch(O=this._imgData.width,q=this._imgData.height,this.group.add(this._imgData.node),f){case d.TOP:this._imgData.node.cx(n/2).y(o);break;case d.BOTTOM:this._imgData.node.cx(n/2).y(s-o-q);break;case d.LEFT:this._imgData.node.x(a).cy(s/2);break;case d.RIGHT:this._imgData.node.x(n-a-O).cy(s/2);break;default:break}let P=new _e,W=0;if(I&&g&&(W=b{let ce=this[`_${le.name}Data`];ce&&(ce.node.x(W).y((y-ce.height)/2),P.add(ce.node),W+=ce.width+t)}),this._prefixData){let le=pn({el:this._prefixData.el,width:this._prefixData.width,height:this._prefixData.height});le.x(W).y((y-this._prefixData.height)/2),P.add(le),W+=this._prefixData.width+t}let ne=new _e;if(this._iconData&&this._iconData.length>0){let le=0;this._iconData.forEach(ce=>{ce.node.x(W+le).y((y-ce.height)/2),ne.add(ce.node),le+=ce.width+t}),P.add(ne),W+=le}if(this._textData){let le=this._textData.node.attr("data-offsetx")||0;this._textData.node.attr("data-offsetx",W),(this._textData.nodeContent||this._textData.node).x(-le).x(W).y((y-this._textData.height)/2),e&&this._textData.node.opacity(this.mindMap.renderer.textEdit.getCurrentEditNode()===this?0:1),P.add(this._textData.node),W+=this._textData.width+t}this._hyperlinkData&&(this._hyperlinkData.node.x(W).y((y-this._hyperlinkData.height)/2),P.add(this._hyperlinkData.node),W+=this._hyperlinkData.width+t);let re=new _e;if(I)if(g){let le=0;this._tagData.forEach(ce=>{ce.node.x(le).y((C-ce.height)/2),re.add(ce.node),le+=ce.width+t}),re.x((x-A)/2).y(S-C),P.add(re)}else{let le=0;this._tagData.forEach(ce=>{ce.node.x(W+le).y((y-ce.height)/2),re.add(ce.node),le+=ce.width+t}),P.add(re),W+=le}if(this._noteData&&(this._noteData.node.x(W).y((y-this._noteData.height)/2),P.add(this._noteData.node),W+=this._noteData.width+t),this._attachmentData&&(this._attachmentData.node.x(W).y((y-this._attachmentData.height)/2),P.add(this._attachmentData.node),W+=this._attachmentData.width+t),this._postfixData){let le=pn({el:this._postfixData.el,width:this._postfixData.width,height:this._postfixData.height});le.x(W).y((y-this._postfixData.height)/2),P.add(le),W+=this._postfixData.width+t}this.mindMap.nodeInnerPostfixList.forEach(le=>{let ce=this[`_${le.name}Data`];ce&&(ce.node.x(W).y((y-ce.height)/2),P.add(ce.node),W+=ce.width+t)}),this.group.add(P);let{width:oe,height:ke}=P.bbox(),Q=0,Y=0;switch(f){case d.TOP:Q=n/2-oe/2,Y=o+q+this.getImgTextMarin("v",0,0,q,S);break;case d.BOTTOM:Q=n/2-oe/2,Y=o;break;case d.LEFT:Q=O+a+this.getImgTextMarin("h",O,x),Y=s/2-ke/2;break;case d.RIGHT:Q=a,Y=s/2-ke/2;break}if(P.translate(Q,Y),h(),this._customContentAddToNodeAdd&&this._customContentAddToNodeAdd.el){let le=pn(this._customContentAddToNodeAdd);this.group.add(le),r&&typeof r.handle=="function"&&r.handle({content:this._customContentAddToNodeAdd,element:le,node:this})}this.mindMap.emit("node_layout_end",this)}var zu,K2=T(()=>{$e();yt();pe();zu={getImgTextMarin:eE,getTagContentSize:tE,getNodeRect:iE,layout:rE}});var Ru,da,X0=T(()=>{Y0();Tu();yt();F2();q2();H2();G2();V2();W2();Y2();X2();K2();$e();pe();Ru=class i{constructor(e={}){this.opt=e,this.nodeData=this.handleData(e.data||{}),this.nodeDataSnapshot="",this.uid=e.uid,this.mindMap=e.mindMap,this.renderer=e.renderer,this.draw=this.mindMap.draw,this.nodeDraw=this.mindMap.nodeDraw,this.lineDraw=this.mindMap.lineDraw,this.style=new qo(this),this.effectiveStyles={},this.shapeInstance=new Ho(this),this.shapePadding={paddingX:0,paddingY:0},this.isRoot=e.isRoot===void 0?!1:e.isRoot,this.isGeneralization=e.isGeneralization===void 0?!1:e.isGeneralization,this.generalizationBelongNode=null,this.layerIndex=e.layerIndex===void 0?0:e.layerIndex,this.width=e.width||0,this.height=e.height||0,this.customTextWidth=e.data.data.customTextWidth||void 0,this._left=e.left||0,this._top=e.top||0,this.customLeft=e.data.data.customLeft||void 0,this.customTop=e.data.data.customTop||void 0,this.isDrag=!1,this.parent=e.parent||null,this.children=e.children||[],this.userList=[],this.group=null,this.shapeNode=null,this.hoverNode=null,this._customNodeContent=null,this._imgData=null,this._iconData=null,this._textData=null,this._hyperlinkData=null,this._tagData=null,this._noteData=null,this.noteEl=null,this.noteContentIsShow=!1,this._attachmentData=null,this._prefixData=null,this._postfixData=null,this._expandBtn=null,this._lastExpandBtnType=null,this._showExpandBtn=!1,this._openExpandNode=null,this._closeExpandNode=null,this._fillExpandNode=null,this._userListGroup=null,this._lines=[],this._generalizationList=[],this._unVisibleRectRegionNode=null,this._isMouseenter=!1,this._customContentAddToNodeAdd=null,this._rectInfo={textContentWidth:0,textContentHeight:0,textContentWidthWithoutTag:0},this._generalizationNodeWidth=0,this._generalizationNodeHeight=0,this.expandBtnSize=this.mindMap.opt.expandBtnSize,this.isMultipleChoice=!1,this.needLayout=!1,this.isHide=!1;let t=Object.getPrototypeOf(this);t.bindEvent||(Object.keys(zu).forEach(r=>{t[r]=zu[r]}),Object.keys(Nu).forEach(r=>{t[r]=Nu[r]}),Object.keys(Su).forEach(r=>{t[r]=Su[r]}),Object.keys(Cu).forEach(r=>{t[r]=Cu[r]}),Object.keys(Au).forEach(r=>{t[r]=Au[r]}),Object.keys(ku).forEach(r=>{t[r]=ku[r]}),this.mindMap.cooperate&&Object.keys(Lu).forEach(r=>{t[r]=Lu[r]}),Object.keys(_u).forEach(r=>{t[r]=_u[r]}),this.mindMap.opt.isShowCreateChildBtnIcon&&(Object.keys(Iu).forEach(r=>{t[r]=Iu[r]}),this.initQuickCreateChildBtn()),t.bindEvent=!0),this.getSize(),this.updateGeneralization(),this.initDragHandle()}get left(){return this.customLeft||this._left}set left(e){this._left=e}get top(){return this.customTop||this._top}set top(e){this._top=e}reset(){this.children=[],this.parent=null,this.isRoot=!1,this.layerIndex=0,this.left=0,this.top=0}resetWhenDelete(){this._isMouseenter=!1}handleData(e){return e.data.expand=e.data.expand!==!1,e.data.isActive=e.data.isActive===!0,e.children=e.children||[],e}createNodeData(e){let{isUseCustomNodeContent:t,customCreateNodeContent:r,createNodePrefixContent:n,createNodePostfixContent:s,addCustomContentToNode:a}=this.mindMap.opt,o=["custom","image","icon","text","hyperlink","tag","note","attachment","prefix","postfix",...this.mindMap.nodeInnerPrefixList.map(h=>h.name),...this.mindMap.nodeInnerPostfixList.map(h=>h.name)],l={};if(Array.isArray(e)?o.forEach(h=>{e.includes(h)&&(l[h]=!0)}):o.forEach(h=>{l[h]=!0}),t&&r&&l.custom&&(this._customNodeContent=r(this)),this._customNodeContent){or(this._customNodeContent);return}l.image&&(this._imgData=this.createImgNode()),l.icon&&(this._iconData=this.createIconNode()),l.text&&(this._textData=this.createTextNode()),l.hyperlink&&(this._hyperlinkData=this.createHyperlinkNode()),l.tag&&(this._tagData=this.createTagNode()),l.note&&(this._noteData=this.createNoteNode()),l.attachment&&(this._attachmentData=this.createAttachmentNode()),this.mindMap.nodeInnerPrefixList.forEach(h=>{l[h.name]&&(this[`_${h.name}Data`]=h.createContent(this))}),l.prefix&&(this._prefixData=n?n(this):null,this._prefixData&&this._prefixData.el&&or(this._prefixData.el)),l.postfix&&(this._postfixData=s?s(this):null,this._postfixData&&this._postfixData.el&&or(this._postfixData.el)),this.mindMap.nodeInnerPostfixList.forEach(h=>{l[h.name]&&(this[`_${h.name}Data`]=h.createContent(this))}),a&&typeof a.create=="function"&&(this._customContentAddToNodeAdd=a.create(this),this._customContentAddToNodeAdd&&this._customContentAddToNodeAdd.el&&or(this._customContentAddToNodeAdd.el))}getSize(e,t={}){t.ignoreUpdateCustomTextWidth||(this.customTextWidth=this.getData("customTextWidth")||void 0),this.customLeft=this.getData("customLeft")||void 0,this.customTop=this.getData("customTop")||void 0,this.createNodeData(e);let{width:n,height:s}=this.getNodeRect(),a=this.width!==n||this.height!==s;return this.width=n,this.height=s,a}bindGroupEvent(){this.group.on("click",e=>{if(this.mindMap.emit("node_click",this,e),this.isMultipleChoice){e.stopPropagation(),this.isMultipleChoice=!1;return}this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate&&this.userList.length>0||this.active(e)}),this.group.on("mousedown",e=>{let{readonly:t,enableCtrlKeyNodeSelection:r,useLeftKeySelectionRightKeyDrag:n,mousedownEventPreventDefault:s}=this.mindMap.opt;if(s&&e.preventDefault(),t||(this.isRoot?e.which===3&&!n&&e.stopPropagation():e.which!==2&&e.stopPropagation()),!t&&(e.ctrlKey||e.metaKey)&&r){this.isMultipleChoice=!0;let a=this.getData("isActive");a||this.mindMap.emit("before_node_active",this,this.renderer.activeNodeList),this.mindMap.renderer[a?"removeNodeFromActiveList":"addNodeToActiveList"](this,!0),this.renderer.emitNodeActiveEvent(a?null:this)}this.mindMap.emit("node_mousedown",this,e)}),this.group.on("mouseup",e=>{!this.isRoot&&e.which!==2&&!this.mindMap.opt.readonly&&e.stopPropagation(),this.mindMap.emit("node_mouseup",this,e)}),this.group.on("mouseenter",e=>{this.isDrag||(this._isMouseenter=!0,this.showExpandBtn(),this.isGeneralization&&this.handleGeneralizationMouseenter(),this.mindMap.emit("node_mouseenter",this,e))}),this.group.on("mouseleave",e=>{this._isMouseenter&&(this._isMouseenter=!1,this.hideExpandBtn(),this.isGeneralization&&this.handleGeneralizationMouseleave(),this.mindMap.emit("node_mouseleave",this,e))}),this.group.on("dblclick",e=>{let{readonly:t,onlyOneEnableActiveNodeOnCooperate:r}=this.mindMap.opt;t||e.ctrlKey||e.metaKey||(e.stopPropagation(),!(r&&this.userList.length>0)&&this.mindMap.emit("node_dblclick",this,e))}),this.group.on("contextmenu",e=>{let{readonly:t,useLeftKeySelectionRightKeyDrag:r}=this.mindMap.opt;t||e.ctrlKey||(e.stopPropagation(),e.preventDefault(),!(this.mindMap.select&&!r&&this.mindMap.select.hasSelectRange())&&(this.getData("isActive")&&this.renderer.activeNodeList.length===1||(this.renderer.clearActiveNodeList(),this.active(e)),this.mindMap.emit("node_contextmenu",e,this)))})}active(e){this.mindMap.opt.readonly||(e&&e.stopPropagation(),!this.getData("isActive")&&(this.mindMap.emit("before_node_active",this,this.renderer.activeNodeList),this.renderer.clearActiveNodeList(),this.renderer.addNodeToActiveList(this,!0),this.renderer.emitNodeActiveEvent(this)))}deactivate(){this.mindMap.renderer.removeNodeFromActiveList(this),this.mindMap.renderer.emitNodeActiveEvent()}update(e){if(!this.group)return;this.updateNodeActiveClass();let{alwaysShowExpandBtn:t,notShowExpandBtn:r,isShowCreateChildBtnIcon:n,readonly:s}=this.mindMap.opt,a=this.getChildrenLength();if(!r)if(t)this._expandBtn&&a<=0?this.removeExpandBtn():this.renderExpandBtn();else{let{isActive:l,expand:h}=this.getData();a<=0?this.removeExpandBtn():h&&!l&&!this._isMouseenter?this.hideExpandBtn():this.showExpandBtn()}if(n)if(a>0)this.removeQuickCreateChildBtn();else{let{isActive:l}=this.getData();l?this.showQuickCreateChildBtn():this.hideQuickCreateChildBtn()}this.updateDragHandle(),this.renderGeneralization(e),this.updateUserListNode&&this.updateUserListNode();let o=this.group.transform();this.nodeDataSnapshot=s?"":JSON.stringify(this.getData()),(this.left!==o.translateX||this.top!==o.translateY)&&this.group.translate(this.left-o.translateX,this.top-o.translateY)}getNodePosInClient(e,t){let r=this.mindMap.draw.transform(),{scaleX:n,scaleY:s,translateX:a,translateY:o}=r,l=e*n+a,h=t*s+o;return{left:l,top:h}}checkIsInClient(e=0){let{left:t,top:r}=this.getNodePosInClient(this.left,this.top);return t+this.width>0-e&&r+this.height>0-e&&t{},t=!1,r=!1){this.renderLine();let{openPerformance:n,performanceConfig:s}=this.mindMap.opt;if(t||!n||this.checkIsInClient(s.padding)||this.isRoot?this.group?(this.nodeDraw.has(this.group)||this.nodeDraw.add(this.group),this.needLayout&&(this.needLayout=!1,this.layout()),this.updateExpandBtnPlaceholderRect(),this.update(t)):(this.group=new _e,this.group.addClass("smm-node"),this.group.css({cursor:"default"}),this.bindGroupEvent(),this.nodeDraw.add(this.group),this.layout(),this.update(t)):n&&s.removeNodeWhenOutCanvas&&this.removeSelf(),this.children&&this.children.length&&this.getData("expand")!==!1){let a=0;this.children.forEach(o=>{let l=()=>{o.render(()=>{a++,a>=this.children.length&&e()},t,r)};r?setTimeout(l,0):l()})}else e();this.nodeData.inserting&&(delete this.nodeData.inserting,this.active(),this.mindMap.emit("node_dblclick",this,null,!0))}removeSelf(){this.group&&(this.group.remove(),this.removeGeneralization())}remove(){this.group&&(this.group.remove(),this.removeGeneralization(),this.removeLine(),this.children&&this.children.length&&this.children.forEach(e=>{e.remove()}))}destroy(){this.removeLine(),this.parent&&this.parent.removeLine(),this.group&&(this.emptyUser&&this.emptyUser(),this.resetWhenDelete(),this.group.remove(),this.removeGeneralization(),this.group=null,this.style.onRemove())}hide(){if(this.group&&this.group.hide(),this.hideGeneralization(),this.parent){let e=this.parent.children.indexOf(this);this.parent._lines[e]&&this.parent._lines[e].hide(),this._lines.forEach(t=>{t.hide()})}this.children&&this.children.length&&this.children.forEach(e=>{e.hide()})}show(){if(this.group){if(this.group.show(),this.showGeneralization(),this.parent){let e=this.parent.children.indexOf(this);this.parent._lines[e]&&this.parent._lines[e].show(),this._lines.forEach(t=>{t.show()})}this.children&&this.children.length&&this.children.forEach(e=>{e.show()})}}setOpacity(e){this.group&&this.group.opacity(e),this._lines.forEach(t=>{t.opacity(e)}),this.children.forEach(t=>{t.setOpacity(e)}),this.setGeneralizationOpacity(e)}hideChildren(){this._lines.forEach(e=>{e.hide()}),this.children&&this.children.length&&this.children.forEach(e=>{e.hide()})}showChildren(){this._lines.forEach(e=>{e.show()}),this.children&&this.children.length&&this.children.forEach(e=>{e.show()})}startDrag(){this.isDrag=!0,this.group&&this.group.addClass("smm-node-dragging")}endDrag(){this.isDrag=!1,this.group&&this.group.removeClass("smm-node-dragging")}renderLine(e=!1){if(this.getData("expand")===!1)return;let t=this.getChildrenLength();this.mindMap.renderer.layout.nodeIsRemoveAllLines&&this.mindMap.renderer.layout.nodeIsRemoveAllLines(this)&&(t=0),t>this._lines.length?new Array(t-this._lines.length).fill(0).forEach(()=>{this._lines.push(this.lineDraw.path())}):t{r.remove()}),this._lines=this._lines.slice(0,t)),this.renderer.layout.renderLine(this,this._lines,(...r)=>{this.styleLine(...r)},this.style.getStyle("lineStyle",!0)),e&&this.children&&this.children.length>0&&this.children.forEach(r=>{r.renderLine(e)})}getShape(){return this.mindMap.themeConfig.nodeUseLineStyle?k.SHAPE.RECTANGLE:this.style.getStyle("shape",!1,!1)}hasCustomPosition(){return this.customLeft!==void 0&&this.customTop!==void 0}ancestorHasCustomPosition(){let e=this;for(;e;){if(e.hasCustomPosition())return!0;e=e.parent}return!1}ancestorHasGeneralization(){let e=this.parent;for(;e;){if(e.checkHasGeneralization())return!0;e=e.parent}return!1}addChildren(e){this.children.push(e)}styleLine(e,t,r){let{enableInheritAncestorLineStyle:n}=this.mindMap.opt,s=n?"getSelfInhertStyle":"getSelfStyle",a=t[s]("lineWidth")||t.getStyle("lineWidth",!0),o=t[s]("lineColor")||this.getRainbowLineColor(t)||t.getStyle("lineColor",!0),l=t[s]("lineDasharray")||t.getStyle("lineDasharray",!0);this.style.line(e,{width:a,color:o,dasharray:l},r,t)}getRainbowLineColor(e){return this.mindMap.rainbowLines?this.mindMap.rainbowLines.getNodeColor(e):""}removeLine(){this._lines.forEach(e=>{e.remove()}),this._lines=[]}isAncestor(e){if(this.uid===e.uid)return!1;let t=e.parent;for(;t;){if(this.uid===t.uid)return!0;t=t.parent}return!1}isParent(e){if(this.uid===e.uid)return!1;let t=e.parent;return!!(t&&this.uid===t.uid)}isBrother(e){return!this.parent||this.uid===e.uid?!1:this.parent.children.find(t=>t.uid===e.uid)}getIndexInBrothers(){return this.parent&&this.parent.children?this.parent.children.findIndex(e=>e.uid===this.uid):-1}getPaddingVale(){return{paddingX:this.getStyle("paddingX"),paddingY:this.getStyle("paddingY")}}getStyle(e,t){let r=this.style.merge(e,t);return r===void 0?"":r}getSelfStyle(e){return this.style.getSelfStyle(e)}getParentSelfStyle(e){return this.parent?this.parent.getSelfStyle(e)||this.parent.getParentSelfStyle(e):null}getSelfInhertStyle(e){return this.getSelfStyle(e)||this.getParentSelfStyle(e)}getBorderWidth(){return this.style.merge("borderWidth",!1)||0}getData(e){return e?this.nodeData.data[e]:this.nodeData.data}getPureData(e=!0,t=!1){return ls({},this,e,t)}getAncestorNodes(){let e=[],t=this.parent;for(;t;)e.unshift(t),t=t.parent;return e}hasCustomStyle(){return this.style.hasCustomStyle()}getRect(){return this.group?this.group.rbox():null}getRectInSvg(){let{scaleX:e,scaleY:t,translateX:r,translateY:n}=this.mindMap.draw.transform(),{left:s,top:a,width:o,height:l}=this,h=(s+o)*e+r,d=(a+l)*t+n;return s=s*e+r,a=a*t+n,{left:s,right:h,top:a,bottom:d,width:o*e,height:l*t}}highlight(){this.group&&this.group.addClass("smm-node-highlight")}closeHighlight(){this.group&&this.group.removeClass("smm-node-highlight")}fakeClone(){let e=new i({...this.opt,uid:wt()});return Object.keys(this).forEach(t=>{e[t]=this[t]}),e}createSvgTextNode(e=""){return new Ce().text(e)}getSvgObjects(){return{SVG:Ae,G:_e,Rect:Ve}}checkEnableDragModifyNodeWidth(){let{enableDragModifyNodeWidth:e,isUseCustomNodeContent:t,customCreateNodeContent:r}=this.mindMap.opt;return e&&(this.mindMap.richText||t&&r)}hasCustomWidth(){return this.checkEnableDragModifyNodeWidth()&&this.customTextWidth!==void 0}getChildrenLength(){return this.nodeData.children?this.nodeData.children.length:0}},da=Ru});var jo,Z2=T(()=>{jo=class{constructor(e){this.max=e||1e3,this.size=0,this.pool=new Map}add(e,t){return!this.has(e)&&this.size>=this.max?!1:(this.delete(e),this.pool.set(e,t),this.size++,!0)}delete(e){this.pool.has(e)&&(this.pool.delete(e),this.size--)}has(e){return this.pool.has(e)}get(e){if(this.pool.has(e))return this.pool.get(e)}clear(){this.size=0,this.pool=new Map}}});var Du,Mt,Ar=T(()=>{X0();$e();Z2();pe();Du=class{constructor(e){this.renderer=e,this.mindMap=e.mindMap,this.draw=this.mindMap.draw,this.lineDraw=this.mindMap.lineDraw,this.root=null,this.lru=new jo(this.mindMap.opt.maxNodeCacheCount),this.rootNodeCenterOffset=null}doLayout(){throw new Error("\u3010computed\u3011\u65B9\u6CD5\u4E3A\u5FC5\u8981\u65B9\u6CD5\uFF0C\u9700\u8981\u5B50\u7C7B\u8FDB\u884C\u91CD\u5199\uFF01")}renderLine(){throw new Error("\u3010renderLine\u3011\u65B9\u6CD5\u4E3A\u5FC5\u8981\u65B9\u6CD5\uFF0C\u9700\u8981\u5B50\u7C7B\u8FDB\u884C\u91CD\u5199\uFF01")}renderExpandBtn(){throw new Error("\u3010renderExpandBtn\u3011\u65B9\u6CD5\u4E3A\u5FC5\u8981\u65B9\u6CD5\uFF0C\u9700\u8981\u5B50\u7C7B\u8FDB\u884C\u91CD\u5199\uFF01")}renderGeneralization(){}cacheNode(e,t){this.renderer.nodeCache[e]=t,this.lru.add(e,t)}checkIsNeedResizeSources(){return this.renderer.checkHasRenderSource(k.CHANGE_THEME)}checkIsLayerTypeChange(e,t){if(e>=2&&t>=2)return!1;if(e>=2&&t<2||e<2&&t>=2)return!0}checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(e){this.renderer.checkHasRenderSource(k.CHANGE_LAYOUT)&&(e.needRerenderExpandBtnPlaceholderRect=!0)}checkIsNodeDataChange(e,t){if(e)e=typeof e=="string"?JSON.parse(e):e,e.isActive=t.isActive,e.expand=t.expand,e=JSON.stringify(e);else return!1;return e!==JSON.stringify(t)}checkNodeFixChange(e,t,r){let n=!1;this.mindMap.nodeInnerPrefixList.forEach(a=>{if(a.updateNodeData){let o=a.updateNodeData(e,t);o&&(n=o)}});let s=!1;return this.mindMap.nodeInnerPostfixList.forEach(a=>{if(a.updateNodeData){let o=a.updateNodeData(e,r);o&&(s=o)}}),n||s}createNode(e,t,r,n,s,a){let o={};this.mindMap.nodeInnerPrefixList.forEach(c=>{if(c.createNodeData){let[f,m]=c.createNodeData({data:e,parent:t,ancestors:a,layerIndex:n,index:s});o[f]=m}});let l={};this.mindMap.nodeInnerPostfixList.forEach(c=>{if(c.createNodeData){let[f,m]=c.createNodeData({data:e,parent:t,ancestors:a,layerIndex:n,index:s});l[f]=m}});let h=e.data.uid,d=null;if(e&&e._node&&!this.renderer.reRender){d=e._node;let c=this.checkIsLayerTypeChange(d.layerIndex,n);d.reset(),d.layerIndex=n,r?d.isRoot=!0:d.parent=t._node,this.cacheNode(e._node.uid,d),this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(d);let f=this.checkNodeFixChange(d,o,l),m=this.checkIsNeedResizeSources(),g=this.checkIsNodeDataChange(e._node.nodeDataSnapshot,e.data);(m||g||c||d.getData("resetRichText")||d.getData("needUpdate")||f)&&(d.getSize(),d.needLayout=!0),this.checkGetGeneralizationChange(d,m)}else if((this.lru.has(h)||this.renderer.lastNodeCache[h])&&!this.renderer.reRender){d=this.lru.get(h)||this.renderer.lastNodeCache[h];let c=JSON.stringify(d.getData()),f=this.checkIsLayerTypeChange(d.layerIndex,n);d.reset(),d.nodeData=d.handleData(e||{}),d.layerIndex=n,r?d.isRoot=!0:d.parent=t._node,this.cacheNode(h,d),this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(d),e._node=d;let m=this.checkIsNeedResizeSources(),g=this.checkIsNodeDataChange(c,e.data),x=this.checkNodeFixChange(d,o,l);(m||g||f||d.getData("resetRichText")||d.getData("needUpdate")||x)&&(d.getSize(),d.needLayout=!0),this.checkGetGeneralizationChange(d,m)}else{let c=h||wt();d=new da({data:e,uid:c,renderer:this.renderer,mindMap:this.mindMap,draw:this.draw,layerIndex:n,isRoot:r,parent:r?null:t._node,...o}),e.data.uid=c,this.cacheNode(c,d),e._node=d}return e.data.isActive&&this.renderer.addNodeToActiveList(d),this.mindMap.renderer.findActiveNodeIndex(d)!==-1&&d.setData({isActive:!0}),r?this.root=d:t._node.addChildren(d),d}checkGetGeneralizationChange(e,t){let r=e.getData("generalization");r&&e._generalizationList&&e._generalizationList.length>0&&e._generalizationList.forEach((n,s)=>{let a=n.generalizationNode,o=a.getData(),l=r[s];(t||l&&JSON.stringify(o)!==JSON.stringify(l))&&(l&&(a.nodeData.data=l),a.getSize(),a.needLayout=!0)})}formatPosition(e,t,r){return typeof e=="number"?e:Uc[e]!==void 0?t*Uc[e]:/^\d\d*%$/.test(e)?Number.parseFloat(e)/100*t:(t-r)/2}formatInitRootNodePosition(e){let{CENTER:t}=k.INIT_ROOT_NODE_POSITION;return(!e||!Array.isArray(e)||e.length<2)&&(e=[t,t]),e}setNodeCenter(e,t){let{initRootNodePosition:r}=this.mindMap.opt;r=this.formatInitRootNodePosition(t||r),e.left=this.formatPosition(r[0],this.mindMap.width,e.width),e.top=this.formatPosition(r[1],this.mindMap.height,e.height)}getRootCenterOffset(e,t){if(this.rootNodeCenterOffset)return this.rootNodeCenterOffset;let{initRootNodePosition:r}=this.mindMap.opt,{CENTER:n}=k.INIT_ROOT_NODE_POSITION;if(r=this.formatInitRootNodePosition(r),r[0]===n&&r[1]===n)this.rootNodeCenterOffset={x:0,y:0};else{let s={width:e,height:t},a={width:e,height:t};this.setNodeCenter(s,[n,n]),this.setNodeCenter(a),this.rootNodeCenterOffset={x:a.left-s.left,y:a.top-s.top}}return this.rootNodeCenterOffset}updateChildren(e,t,r){e.forEach(n=>{n[t]+=r,n.children&&n.children.length&&!n.hasCustomPosition()&&this.updateChildren(n.children,t,r)})}updateChildrenPro(e,t){e.forEach(r=>{Object.keys(t).forEach(n=>{r[n]+=t[n]}),r.children&&r.children.length&&!r.hasCustomPosition()&&this.updateChildrenPro(r.children,t)})}getNodeAreaWidth(e,t=!1){let r=[],n=0,s=(a,o)=>{t&&a.checkHasGeneralization()&&(n+=a._generalizationNodeWidth),a.children.length?(o+=a.width/2,a.children.forEach(l=>{s(l,o)})):(o+=a.width,r.push(o))};return s(e,0),Math.max(...r)+n}quadraticCurvePath(e,t,r,n,s=!1){let a,o;return s?(a=e+(r-e)*.8,o=t+(n-t)*.2):(a=e+(r-e)*.2,o=t+(n-t)*.8),`M ${e},${t} Q ${a},${o} ${r},${n}`}cubicBezierPath(e,t,r,n,s=!1){let a,o,l,h;return s?(a=e,o=t+(n-t)/2,l=r,h=o):(a=e+(r-e)/2,o=t,l=a,h=n),`M ${e},${t} C ${a},${o} ${l},${h} ${r},${n}`}computeNewPoint(e,t,r=0){if(e[0]===t[0])return t[1]>e[1]?[t[0],t[1]-r]:[t[0],t[1]+r];if(e[1]===t[1])return t[0]>e[0]?[t[0]-r,t[1]]:[t[0]+r,t[1]]}createFoldLine(e){let{lineRadius:t}=this.mindMap.themeConfig,r=e.length,n="",s="";if(r>=3&&t>0){let a=e[r-3],o=e[r-2],l=e[r-1];if(!(a[0].toFixed(0)===o[0].toFixed(0)&&o[0].toFixed(0)===l[0].toFixed(0)||a[1].toFixed(0)===o[1].toFixed(0)&&o[1].toFixed(0)===l[1].toFixed(0))){let d=this.computeNewPoint(a,o,t),c=this.computeNewPoint(l,o,t);s=`Q ${o[0]},${o[1]} ${c[0]},${c[1]}`,e.splice(r-2,1,d,s)}}return e.forEach((a,o)=>{if(typeof a=="string")n+=a;else{let[l,h]=a;o===0?n+=`M ${l},${h}`:n+=`L ${l},${h}`}}),n}getMarginX(e){let{themeConfig:t,opt:r}=this.mindMap,{second:n,node:s}=t,a=r.hoverRectPadding*2;return e===1?n.marginX+a:s.marginX+a}getMarginY(e){let{themeConfig:t,opt:r}=this.mindMap,{second:n,node:s}=t,a=r.hoverRectPadding*2;return e===1?n.marginY+a:s.marginY+a}getNodeWidthWithGeneralization(e){return Math.max(e.width,e.checkHasGeneralization()?e._generalizationNodeWidth:0)}getNodeHeightWithGeneralization(e){return Math.max(e.height,e.checkHasGeneralization()?e._generalizationNodeHeight:0)}getNodeBoundaries(e,t){let{generalizationLineMargin:r,generalizationNodeMargin:n}=this.mindMap.themeConfig,s=d=>{let c=1/0,f=-1/0,m=1/0,g=-1/0;d.children&&d.children.length>0&&d.children.forEach(y=>{let{left:b,right:S,top:A,bottom:C}=s(y),I=y.checkHasGeneralization()&&y.getData("expand")?y._generalizationNodeWidth+n:0,O=y.checkHasGeneralization()&&y.getData("expand")?y._generalizationNodeHeight+n:0;b-(t==="h"?I:0)f&&(f=S+(t==="h"?I:0)),Ag&&(g=C+(t==="v"?O:0))});let x={left:d.left,right:d.left+d.width,top:d.top,bottom:d.top+d.height};return{left:x.leftf?x.right:f,top:x.topg?x.bottom:g}},{left:a,right:o,top:l,bottom:h}=s(e);return{left:a,right:o,top:l,bottom:h,generalizationLineMargin:r,generalizationNodeMargin:n}}getChildrenBoundaries(e,t,r=0,n){let{generalizationLineMargin:s,generalizationNodeMargin:a}=this.mindMap.themeConfig,o=e.children.slice(r,n+1),l=1/0,h=-1/0,d=1/0,c=-1/0;return o.forEach(f=>{let m=this.getNodeBoundaries(f,t);l=m.lefth?m.right:h,d=m.topc?m.bottom:c}),{left:l,right:h,top:d,bottom:c,generalizationLineMargin:s,generalizationNodeMargin:a}}getNodeGeneralizationRenderBoundaries(e,t){let r=null;return e.range?r=this.getChildrenBoundaries(e.node,t,e.range[0],e.range[1]):r=this.getNodeBoundaries(e.node,t),r}getNodeActChildrenLength(e){return e.nodeData.children&&e.nodeData.children.length}setLineStyle(e,t,r,n){t.plot(this.transformPath(r)),e&&e(t,n,!0)}transformPath(e){let{customTransformNodeLinePath:t}=this.mindMap.opt;return t?t(e):e}},Mt=Du});var Ou,K0,Bu=T(()=>{Ou=class{constructor(e){this.mindMap=e,this.autoMoveTimer=null}onMove(e,t,r=()=>{},n=()=>{}){r();let s=this.mindMap.opt.selectTranslateStep,a=this.mindMap.opt.selectTranslateLimit,o=0;e<=this.mindMap.elRect.left+a&&(n("left",s),this.mindMap.view.translateX(s),o++),e>=this.mindMap.elRect.right-a&&(n("right",s),this.mindMap.view.translateX(-s),o++),t<=this.mindMap.elRect.top+a&&(n("top",s),this.mindMap.view.translateY(s),o++),t>=this.mindMap.elRect.bottom-a&&(n("bottom",s),this.mindMap.view.translateY(-s),o++),o>0&&this.startAutoMove(e,t,r,n)}startAutoMove(e,t,r,n){this.autoMoveTimer=setTimeout(()=>{this.onMove(e,t,r,n)},20)}clearAutoMoveTimer(){clearTimeout(this.autoMoveTimer)}},K0=Ou});var Q2={};tt(Q2,{default:()=>nE});var Z0,nE,J2=T(()=>{pe();Ar();$e();Bu();Z0=class extends Mt{constructor({mindMap:e}){super(e.renderer),this.mindMap=e,this.autoMove=new K0(e),this.reset(),this.bindEvent()}reset(){this.isDragging=!1,this.mousedownNode=null,this.beingDragNodeList=[],this.nodeList=[],this.overlapNode=null,this.prevNode=null,this.nextNode=null,this.drawTransform=null,this.clone=null,this.placeholder=null,this.placeholderWidth=50,this.placeholderHeight=10,this.placeHolderLine=null,this.placeHolderExtraLines=[],this.offsetX=0,this.offsetY=0,this.isMousedown=!1,this.mouseDownX=0,this.mouseDownY=0,this.mouseMoveX=0,this.mouseMoveY=0,this.checkDragOffset=10,this.minOffset=10}bindEvent(){this.onNodeMousedown=this.onNodeMousedown.bind(this),this.onMousemove=this.onMousemove.bind(this),this.onMouseup=this.onMouseup.bind(this),this.checkOverlapNode=Si(this.checkOverlapNode,300,this),this.mindMap.on("node_mousedown",this.onNodeMousedown),this.mindMap.on("mousemove",this.onMousemove),this.mindMap.on("node_mouseup",this.onMouseup),this.mindMap.on("mouseup",this.onMouseup)}unBindEvent(){this.mindMap.off("node_mousedown",this.onNodeMousedown),this.mindMap.off("mousemove",this.onMousemove),this.mindMap.off("node_mouseup",this.onMouseup),this.mindMap.off("mouseup",this.onMouseup)}onNodeMousedown(e,t){if(this.mindMap.opt.readonly||t.which!==1||e.isGeneralization||e.isRoot)return;this.isMousedown=!0,this.mousedownNode=e;let{x:r,y:n}=this.mindMap.toPos(t.clientX,t.clientY);this.mouseDownX=r,this.mouseDownY=n}onMousemove(e){if(this.mindMap.opt.readonly||!this.isMousedown)return;e.preventDefault();let{x:t,y:r}=this.mindMap.toPos(e.clientX,e.clientY);this.mouseMoveX=t,this.mouseMoveY=r,!(!this.isDragging&&Math.abs(t-this.mouseDownX)<=this.checkDragOffset&&Math.abs(r-this.mouseDownY)<=this.checkDragOffset)&&(this.mindMap.emit("node_dragging",this.mousedownNode),this.handleStartMove(),this.onMove(t,r,e))}async onMouseup(e){if(!this.isMousedown)return;let{autoMoveWhenMouseInEdgeOnDrag:t,enableFreeDrag:r,beforeDragEnd:n}=this.mindMap.opt;t&&this.mindMap.select&&this.autoMove.clearAutoMoveTimer(),this.isMousedown=!1,this.beingDragNodeList.forEach(l=>{l.setOpacity(1),l.showChildren(),l.endDrag()}),this.removeCloneNode();let s=this.overlapNode?this.overlapNode.getData("uid"):"",a=this.prevNode?this.prevNode.getData("uid"):"",o=this.nextNode?this.nextNode.getData("uid"):"";if(this.isDragging&&typeof n=="function"&&await n({overlapNodeUid:s,prevNodeUid:a,nextNodeUid:o,beingDragNodeList:[...this.beingDragNodeList]})){this.reset();return}if(this.overlapNode)this.removeNodeActive(this.overlapNode),this.mindMap.execCommand("MOVE_NODE_TO",this.beingDragNodeList,this.overlapNode);else if(this.prevNode)this.removeNodeActive(this.prevNode),this.mindMap.execCommand("INSERT_AFTER",this.beingDragNodeList,this.prevNode);else if(this.nextNode)this.removeNodeActive(this.nextNode),this.mindMap.execCommand("INSERT_BEFORE",this.beingDragNodeList,this.nextNode);else if(this.clone&&r&&this.beingDragNodeList.length===1){let{x:l,y:h}=this.mindMap.toPos(e.clientX-this.offsetX,e.clientY-this.offsetY),{scaleX:d,scaleY:c,translateX:f,translateY:m}=this.drawTransform;l=(l-f)/d,h=(h-m)/c,this.mousedownNode.left=l,this.mousedownNode.top=h,this.mousedownNode.customLeft=l,this.mousedownNode.customTop=h,this.mindMap.execCommand("SET_NODE_CUSTOM_POSITION",this.mousedownNode,l,h),this.mindMap.render()}this.isDragging&&this.mindMap.emit("node_dragend",{overlapNodeUid:s,prevNodeUid:a,nextNodeUid:o}),this.reset()}removeNodeActive(e){e.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",e,!1)}onMove(e,t,r){if(!this.isMousedown||!this.isDragging)return;let{scaleX:n,scaleY:s,translateX:a,translateY:o}=this.drawTransform,l=e-this.offsetX,h=t-this.offsetY;e=(l-a)/n,t=(h-o)/s;let d=this.clone.transform();this.clone.translate(e-d.translateX,t-d.translateY),this.checkOverlapNode(),this.drawTransform=this.mindMap.draw.transform(),this.autoMove.clearAutoMoveTimer(),this.autoMove.onMove(r.clientX,r.clientY)}async handleStartMove(){if(!this.isDragging){let e=this.mousedownNode;this.drawTransform=this.mindMap.draw.transform();let{scaleX:t,scaleY:r,translateX:n,translateY:s}=this.drawTransform;this.offsetX=this.mouseDownX-(e.left*t+n),this.offsetY=this.mouseDownY-(e.top*r+s),e.getData("isActive")?this.beingDragNodeList=Fo(ds(this.mindMap.renderer.activeNodeList.filter(o=>!o.isRoot&&!o.isGeneralization))):this.beingDragNodeList=[e];let{beforeDragStart:a}=this.mindMap.opt;if(typeof a=="function"&&await a([...this.beingDragNodeList]))return;this.nodeTreeToList(),this.createCloneNode(),this.mindMap.execCommand("CLEAR_ACTIVE_NODE"),this.isDragging=!0}}nodeTreeToList(){let e=[];Xt(this.mindMap.renderer.root,t=>{this.checkIsInBeingDragNodeList(t)||(e[t.layerIndex]||(e[t.layerIndex]=[]),e[t.layerIndex].push(t))}),this.nodeList=e.reduceRight((t,r)=>[...t,...r],[])}createCloneNode(){if(!this.clone){let{dragMultiNodeRectConfig:e,dragPlaceholderRectFill:t,dragPlaceholderLineConfig:r,dragOpacityConfig:n,handleDragCloneNode:s}=this.mindMap.opt,{width:a,height:o,fill:l}=e,h=this.beingDragNodeList[0],d=h.style.merge("lineColor",!0);if(this.beingDragNodeList.length>1)this.clone=this.mindMap.otherDraw.rect().size(a,o).radius(o/2).fill({color:l||d}),this.offsetX=a/2,this.offsetY=o/2;else{this.clone=h.group.clone();let c=this.clone.findOne(".smm-expand-btn");c&&c.remove(),this.mindMap.otherDraw.add(this.clone),typeof s=="function"&&s(this.clone)}this.clone.opacity(n.cloneNodeOpacity),this.clone.css("z-index",99999),this.placeholder=this.mindMap.otherDraw.rect().fill({color:t||d}).radius(5),this.placeHolderLine=this.mindMap.otherDraw.path().stroke({color:r.color||d,width:r.width}).fill({color:"none"}),this.beingDragNodeList.forEach(c=>{c.setOpacity(n.beingDragNodeOpacity),c.hideChildren(),c.startDrag()})}}removeCloneNode(){this.clone&&(this.clone.remove(),this.placeholder.remove(),this.placeHolderLine.remove(),this.removeExtraLines())}removeExtraLines(){this.placeHolderExtraLines.forEach(e=>{e.remove()}),this.placeHolderExtraLines=[]}checkOverlapNode(){if(!this.drawTransform||!this.placeholder)return;let{LOGICAL_STRUCTURE:e,LOGICAL_STRUCTURE_LEFT:t,MIND_MAP:r,ORGANIZATION_STRUCTURE:n,CATALOG_ORGANIZATION:s,TIMELINE:a,TIMELINE2:o,VERTICAL_TIMELINE:l,VERTICAL_TIMELINE2:h,VERTICAL_TIMELINE3:d,FISHBONE:c,FISHBONE2:f,RIGHT_FISHBONE:m,RIGHT_FISHBONE2:g}=k.LAYOUT;this.overlapNode=null,this.prevNode=null,this.nextNode=null,this.placeholder.size(0,0),this.placeHolderLine.hide(),this.removeExtraLines(),this.nodeList.forEach(x=>{if(x.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",x,!1),!(this.overlapNode||this.prevNode&&this.nextNode))switch(this.mindMap.opt.layout){case e:case t:this.handleLogicalStructure(x);break;case r:this.handleMindMap(x);break;case n:this.handleOrganizationStructure(x);break;case s:this.handleCatalogOrganization(x);break;case a:this.handleTimeLine(x);break;case o:this.handleTimeLine2(x);break;case l:case h:case d:this.handleLogicalStructure(x);break;case c:case f:case m:case g:this.handleFishbone(x);break;default:this.handleLogicalStructure(x)}}),this.overlapNode&&this.handleOverlapNode()}handleOverlapNode(){let{LOGICAL_STRUCTURE:e,LOGICAL_STRUCTURE_LEFT:t,MIND_MAP:r,ORGANIZATION_STRUCTURE:n,CATALOG_ORGANIZATION:s,TIMELINE:a,TIMELINE2:o,VERTICAL_TIMELINE:l,VERTICAL_TIMELINE2:h,VERTICAL_TIMELINE3:d,FISHBONE:c,FISHBONE2:f,RIGHT_FISHBONE:m,RIGHT_FISHBONE2:g}=k.LAYOUT,{LEFT:x,TOP:y,RIGHT:b,BOTTOM:S}=k.LAYOUT_GROW_DIR,A=this.overlapNode.layerIndex,C=this.overlapNode.children,I=this.mindMap.renderer.layout.getMarginX(A+1),O=this.mindMap.renderer.layout.getMarginY(A+1),q=this.placeholderWidth/2,P=this.placeholderHeight/2,W="",ne="",re="",oe=!1,ke=!1;if(C.length>0){let Q=C[C.length-1],Y=this.getNodeRect(Q);switch(W=this.getNewChildNodeDir(Q),this.mindMap.opt.layout){case e:case r:ne=W===x?Y.originRight-this.placeholderWidth:Y.originLeft,re=Y.originBottom+this.minOffset-P;break;case t:ne=Y.originRight-this.placeholderWidth,re=Y.originBottom+this.minOffset-P;break;case n:oe=!0,ne=Y.originRight+this.minOffset-P,re=Y.originTop;break;case s:A===0?(oe=!0,ne=Y.originRight+this.minOffset-P,re=Y.originTop):(ne=Y.originLeft,re=Y.originBottom+this.minOffset-P);break;case a:A===0?(oe=!0,ne=Y.originRight+this.minOffset-P,re=Y.originTop+Y.originHeight/2-q):(ne=Y.originLeft,re=Y.originBottom+this.minOffset-P);break;case o:A===0?(oe=!0,ne=Y.originRight+this.minOffset-P,re=Y.originTop+Y.originHeight/2-q):(ne=Y.originLeft,A===1?re=W===y?Y.originTop-this.placeholderHeight-this.minOffset+P:Y.originBottom+this.minOffset-P:re=Y.originBottom+this.minOffset-P);break;case l:case h:case d:A===0?(ne=Y.originLeft+Y.originWidth/2-q,re=Y.originBottom+this.minOffset-P):(ne=W===b?Y.originLeft:Y.originRight-this.placeholderWidth,re=Y.originBottom+this.minOffset-P);break;case c:case f:case m:case g:A<=1?(ke=!0,this.mindMap.execCommand("SET_NODE_ACTIVE",this.overlapNode,!0)):(ne=Y.originLeft,re=W===y?Y.originBottom+this.minOffset-P:Y.originTop-this.placeholderHeight-this.minOffset+P);break;default:}}else{let Q=this.getNodeRect(this.overlapNode);switch(W=this.getNewChildNodeDir(this.overlapNode),this.mindMap.opt.layout){case e:case r:ne=W===b?Q.originRight+I:Q.originLeft-this.placeholderWidth-I,re=Q.originTop+(Q.originHeight-this.placeholderHeight)/2;break;case t:ne=Q.originLeft-this.placeholderWidth-I,re=Q.originTop+(Q.originHeight-this.placeholderHeight)/2;break;case n:oe=!0,ne=Q.originLeft+(Q.originWidth-this.placeholderHeight)/2,re=Q.originBottom+I;break;case s:A===0&&(oe=!0),ne=Q.originLeft+Q.originWidth*.5,re=Q.originBottom+I;break;case a:A===0&&(oe=!0),ne=Q.originLeft+Q.originWidth*.5,re=Q.originBottom+O;break;case o:A===0&&(oe=!0),ne=Q.originLeft+Q.originWidth*.5,A===1?re=W===y?Q.originTop-this.placeholderHeight-I:Q.originBottom+I:re=Q.originBottom+I;break;case l:case h:case d:A===0&&(oe=!0),ne=W===b?Q.originRight+I:Q.originLeft-this.placeholderWidth-I,re=Q.originTop+Q.originHeight/2-P;break;case c:case f:case m:case g:A<=1?(ke=!0,this.mindMap.execCommand("SET_NODE_ACTIVE",this.overlapNode,!0)):(ne=Q.originLeft+Q.originWidth*.5,re=W===S?Q.originTop-this.placeholderHeight-this.minOffset+P:Q.originBottom+this.minOffset-P);break;default:}}ke||this.setPlaceholderRect({x:ne,y:re,dir:W,rotate:oe})}getNewChildNodeDir(e){let{LOGICAL_STRUCTURE:t,LOGICAL_STRUCTURE_LEFT:r,MIND_MAP:n,TIMELINE2:s,VERTICAL_TIMELINE:a,VERTICAL_TIMELINE2:o,VERTICAL_TIMELINE3:l,FISHBONE:h,FISHBONE2:d,RIGHT_FISHBONE:c,RIGHT_FISHBONE2:f}=k.LAYOUT;switch(this.mindMap.opt.layout){case t:return k.LAYOUT_GROW_DIR.RIGHT;case r:return k.LAYOUT_GROW_DIR.LEFT;case n:case s:case a:case o:case l:case h:case d:case c:case f:return e.dir;default:return""}}handleVerticalCheck(e,t,r=!1){let{layout:n}=this.mindMap.opt,{LAYOUT:s,LAYOUT_GROW_DIR:a}=k,{VERTICAL_TIMELINE:o,VERTICAL_TIMELINE2:l,VERTICAL_TIMELINE3:h,FISHBONE:d,FISHBONE2:c,RIGHT_FISHBONE:f,RIGHT_FISHBONE2:m}=s,{LEFT:g}=a,x=this.mouseMoveX,y=this.mouseMoveY,b=this.getNodeRect(e),S=this.getNewChildNodeDir(e),A=e.layerIndex;r&&(t=t.reverse());let C=b.originHeight/4,{prevBrotherOffset:I,nextBrotherOffset:O}=this.getNodeDistanceToSiblingNode(t,e,b,"v");if(b.left<=x&&b.right>=x){if(!this.overlapNode&&!this.prevNode&&!this.nextNode&&!e.isRoot){let q=O>0?y>b.bottom&&y<=b.bottom+O:y>=b.bottom-C&&y<=b.bottom,P=I>0?y=b.top-I:y>=b.top&&y<=b.top+C,{scaleY:W}=this.drawTransform,ne=S===g?b.originRight-this.placeholderWidth:b.originLeft,re=!1;switch(n){case o:case l:case h:A===1&&(ne=b.originLeft+b.originWidth/2-this.placeholderWidth/2);break;case f:case m:ne=b.originLeft+b.originWidth-this.placeholderWidth;break;default:}if(q){r?this.nextNode=e:this.prevNode=e;let oe=b.originBottom+O/W-this.placeholderHeight/2;switch(n){case d:case c:case f:case m:A===2&&(re=!0,oe=b.originBottom+this.minOffset-this.placeholderHeight/2);break;default:}this.setPlaceholderRect({x:ne,y:oe,dir:S,notRenderLine:re})}else if(P){r?this.prevNode=e:this.nextNode=e;let oe=b.originTop-this.placeholderHeight-I/W+this.placeholderHeight/2;switch(n){case d:case c:case f:case m:A===2&&(re=!0,oe=b.originTop-this.placeholderHeight-this.minOffset+this.placeholderHeight/2);break;default:}this.setPlaceholderRect({x:ne,y:oe,dir:S,notRenderLine:re})}}this.checkIsOverlap({node:e,dir:"v",prevBrotherOffset:I,nextBrotherOffset:O,size:C,pos:y,nodeRect:b})}}handleHorizontalCheck(e,t){let{layout:r}=this.mindMap.opt,{LAYOUT:n}=k,{FISHBONE:s,FISHBONE2:a,RIGHT_FISHBONE:o,RIGHT_FISHBONE2:l,TIMELINE:h,TIMELINE2:d}=n,c=this.mouseMoveX,f=this.mouseMoveY,m=this.getNodeRect(e),g=m.originWidth/4,{prevBrotherOffset:x,nextBrotherOffset:y}=this.getNodeDistanceToSiblingNode(t,e,m,"h");if(m.top<=f&&m.bottom>=f){if(!this.overlapNode&&!this.prevNode&&!this.nextNode&&!e.isRoot){let b=y>0?c=m.right:c<=m.right&&c>=m.right-g,S=x>0?c>m.left-x&&c<=m.left:c<=m.left+g&&c>=m.left,{scaleX:A}=this.drawTransform,C=e.layerIndex,I=m.originTop,O=!1;switch(r){case h:case d:I=m.originTop+m.originHeight/2-this.placeholderWidth/2;break;case s:case a:case o:case l:C===1&&(O=!0,I=m.originTop+m.originHeight/2-this.placeholderWidth/2);break;default:}b?([o,l].includes(r)?this.nextNode=e:this.prevNode=e,this.setPlaceholderRect({x:m.originRight+y/A-this.placeholderHeight/2,y:I,rotate:!0,notRenderLine:O})):S&&([o,l].includes(r)?this.prevNode=e:this.nextNode=e,this.setPlaceholderRect({x:m.originLeft-this.placeholderHeight-x/A+this.placeholderHeight/2,y:I,rotate:!0,notRenderLine:O}))}this.checkIsOverlap({node:e,dir:"h",prevBrotherOffset:x,nextBrotherOffset:y,size:g,pos:c,nodeRect:m})}}getNodeDistanceToSiblingNode(e,t,r,n){let{TOP:s,LEFT:a,BOTTOM:o,RIGHT:l}=k.LAYOUT_GROW_DIR,{scaleX:h,scaleY:d}=this.drawTransform,c=n==="v"?s:a,f=n==="v"?o:l,m=n==="v"?d:h,g=this.minOffset*m,x=Ie(t,e),y=null,b=null;x!==-1&&(x-1>=0&&(y=e[x-1]),x+1<=e.length-1&&(b=e[x+1]));let S=0;if(y){let C=this.getNodeRect(y);S=r[c]-C[f],S=S>=g?S/2:0}else S=g;let A=0;return b?(A=this.getNodeRect(b)[c]-r[f],A=A>=g?A/2:0):A=g,{prevBrother:y,prevBrotherOffset:S,nextBrother:b,nextBrotherOffset:A}}setPlaceholderRect({x:e,y:t,dir:r,rotate:n,notRenderLine:s}){let a=this.placeholderWidth,o=this.placeholderHeight;if(n){let f=a;a=o,o=f}if(this.placeholder.size(a,o).move(e,t),s)return;let{dragPlaceholderLineConfig:l}=this.mindMap.opt,h=null,d=null;this.overlapNode?(h=this.overlapNode,d=this.overlapNode):(h=this.prevNode||this.nextNode,d=h.parent),d=d.fakeClone(),h=h.fakeClone();let c=this.beingDragNodeList[0].fakeClone();c.dir=r,c.left=e,c.top=t,c.width=a,c.height=o,d.children=[c],d._lines=[],this.placeHolderLine.show(),this.mindMap.renderer.layout.renderLine(d,[this.placeHolderLine],(...f)=>{},h.style.getStyle("lineStyle",!0)),this.placeHolderExtraLines=[...d._lines],this.placeHolderExtraLines.forEach(f=>{this.mindMap.otherDraw.add(f),f.stroke({color:l.color,width:l.width}).fill({color:"none"})})}checkIsOverlap({node:e,dir:t,prevBrotherOffset:r,nextBrotherOffset:n,size:s,pos:a,nodeRect:o}){let{TOP:l,LEFT:h,BOTTOM:d,RIGHT:c}=k.LAYOUT_GROW_DIR,f=t==="v"?l:h,m=t==="v"?d:c;!this.overlapNode&&!this.prevNode&&!this.nextNode&&o[f]+(r>0?0:s)<=a&&o[m]-(n>0?0:s)>=a&&(this.overlapNode=e)}handleLogicalStructure(e){let t=this.commonGetNodeCheckList(e);this.handleVerticalCheck(e,t)}handleMindMap(e){let t=e.parent?e.parent.children.filter(r=>{let n=!0;return e.layerIndex===1&&(n=r.dir===e.dir),n&&!this.checkIsInBeingDragNodeList(r)}):[];this.handleVerticalCheck(e,t)}handleOrganizationStructure(e){let t=this.commonGetNodeCheckList(e);this.handleHorizontalCheck(e,t)}handleCatalogOrganization(e){let t=this.commonGetNodeCheckList(e);e.layerIndex===1?this.handleHorizontalCheck(e,t):this.handleVerticalCheck(e,t)}handleTimeLine(e){let t=this.commonGetNodeCheckList(e);e.layerIndex===1?this.handleHorizontalCheck(e,t):this.handleVerticalCheck(e,t)}handleTimeLine2(e){let t=this.commonGetNodeCheckList(e);e.layerIndex===1?this.handleHorizontalCheck(e,t):e.dir===k.LAYOUT_GROW_DIR.TOP&&e.layerIndex===2?this.handleVerticalCheck(e,t,!0):this.handleVerticalCheck(e,t)}handleFishbone(e){let t=e.parent?e.parent.children.filter(r=>r.layerIndex>1&&!this.checkIsInBeingDragNodeList(r)):[];if(e.layerIndex===1)this.handleHorizontalCheck(e,t);else{let r=e.dir===k.LAYOUT_GROW_DIR.TOP&&e.layerIndex===2,n=e.dir===k.LAYOUT_GROW_DIR.BOTTOM&&e.layerIndex>=3;r||n?this.handleVerticalCheck(e,t,!0):this.handleVerticalCheck(e,t)}}commonGetNodeCheckList(e){return e.parent?[...e.parent.children].filter(t=>!this.checkIsInBeingDragNodeList(t)):[]}getNodeRect(e){let{scaleX:t,scaleY:r,translateX:n,translateY:s}=this.drawTransform,{left:a,top:o,width:l,height:h}=e,d=l,c=h,f=a,m=o,g=o+h,x=a+l,y=(a+l)*t+n,b=(o+h)*r+s;return a=a*t+n,o=o*r+s,{left:a,top:o,right:y,bottom:b,originWidth:d,originHeight:c,originLeft:f,originTop:m,originBottom:g,originRight:x}}checkIsInBeingDragNodeList(e){return!!this.beingDragNodeList.find(t=>t.uid===e.uid||t.isAncestor(e))}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};Z0.instanceName="drag";nE=Z0});var e6={};tt(e6,{default:()=>sE});var Q0,sE,t6=T(()=>{pe();$e();Q0=class{constructor(e){this.opt=e,this.mindMap=e.mindMap,this.addShortcut()}addShortcut(){this.onLeftKeyUp=this.onLeftKeyUp.bind(this),this.onUpKeyUp=this.onUpKeyUp.bind(this),this.onRightKeyUp=this.onRightKeyUp.bind(this),this.onDownKeyUp=this.onDownKeyUp.bind(this),this.mindMap.keyCommand.addShortcut(k.KEY_DIR.LEFT,this.onLeftKeyUp),this.mindMap.keyCommand.addShortcut(k.KEY_DIR.UP,this.onUpKeyUp),this.mindMap.keyCommand.addShortcut(k.KEY_DIR.RIGHT,this.onRightKeyUp),this.mindMap.keyCommand.addShortcut(k.KEY_DIR.DOWN,this.onDownKeyUp)}removeShortcut(){this.mindMap.keyCommand.removeShortcut(k.KEY_DIR.LEFT,this.onLeftKeyUp),this.mindMap.keyCommand.removeShortcut(k.KEY_DIR.UP,this.onUpKeyUp),this.mindMap.keyCommand.removeShortcut(k.KEY_DIR.RIGHT,this.onRightKeyUp),this.mindMap.keyCommand.removeShortcut(k.KEY_DIR.DOWN,this.onDownKeyUp)}onLeftKeyUp(){this.onKeyup(k.KEY_DIR.LEFT)}onUpKeyUp(){this.onKeyup(k.KEY_DIR.UP)}onRightKeyUp(){this.onKeyup(k.KEY_DIR.RIGHT)}onDownKeyUp(){this.onKeyup(k.KEY_DIR.DOWN)}onKeyup(e){if(this.mindMap.renderer.activeNodeList.length>0)this.focus(e);else{let t=this.mindMap.renderer.root;this.mindMap.execCommand("GO_TARGET_NODE",t)}}focus(e){let t=this.mindMap.renderer.activeNodeList[0],r=this.getNodeRect(t),n=null,s=1/0,a=(o,l)=>{let h=this.getDistance(r,o);h{if(s.uid===e.uid)return;let a=this.getNodeRect(s),{left:o,top:l,right:h,bottom:d}=a,c=!1;r===k.KEY_DIR.LEFT?c=h<=t.left:r===k.KEY_DIR.RIGHT?c=o>=t.right:r===k.KEY_DIR.UP?c=d<=t.top:r===k.KEY_DIR.DOWN&&(c=l>=t.bottom),c&&n(a,s)})}getFocusNodeByShadowAlgorithm({currentActiveNode:e,currentActiveNodeRect:t,dir:r,checkNodeDis:n}){Xt(this.mindMap.renderer.root,s=>{if(s.uid===e.uid)return;let a=this.getNodeRect(s),{left:o,top:l,right:h,bottom:d}=a,c=!1;r===k.KEY_DIR.LEFT?c=ot.top:r===k.KEY_DIR.RIGHT?c=h>t.right&&lt.top:r===k.KEY_DIR.UP?c=lt.left:r===k.KEY_DIR.DOWN&&(c=d>t.bottom&&ot.left),c&&n(a,s)})}getFocusNodeByAreaAlgorithm({currentActiveNode:e,currentActiveNodeRect:t,dir:r,checkNodeDis:n}){let s=(t.right+t.left)/2,a=(t.bottom+t.top)/2;Xt(this.mindMap.renderer.root,o=>{if(o.uid===e.uid)return;let l=this.getNodeRect(o),{left:h,top:d,right:c,bottom:f}=l,m=(c+h)/2,g=(f+d)/2,x=m-s,y=g-a;if(x===0&&y===0)return;let b=!1;r===k.KEY_DIR.LEFT?b=x<=0&&x<=y&&x<=-y:r===k.KEY_DIR.RIGHT?b=x>0&&x>=-y&&x>=y:r===k.KEY_DIR.UP?b=y<=0&&y0&&-yx),b&&n(l,o)})}getNodeRect(e){let{scaleX:t,scaleY:r,translateX:n,translateY:s}=this.mindMap.draw.transform(),{left:a,top:o,width:l,height:h}=e;return{right:(a+l)*t+n,bottom:(o+h)*r+s,left:a*t+n,top:o*r+s}}getDistance(e,t){let r=this.getCenter(e),n=this.getCenter(t);return Math.sqrt(Math.pow(r.x-n.x,2)+Math.pow(r.y-n.y,2))}getCenter({left:e,right:t,top:r,bottom:n}){return{x:(e+t)/2,y:(r+n)/2}}beforePluginRemove(){this.removeShortcut()}beforePluginDestroy(){this.removeShortcut()}};Q0.instanceName="keyboardNavigation";sE=Q0});var Fu,J0,Pu,i6,aE,oE,lE,eh,hE,r6,n6=T(()=>{Fu=i=>String(i).split(/\s+/).map(t=>{if(/^[\d.]+/.test(t)){let r=/^([\d.]+)(.*)$/.exec(t);return[Number(r[1]),r[2]]}else return t}),J0=(i,e)=>i*e,Pu=(i,e)=>e/i,i6={left:0,top:0,center:50,bottom:100,right:100},aE=({backgroundSize:i,drawOpt:e,imageRatio:t,canvasWidth:r,canvasHeight:n,canvasRatio:s})=>{if(i){let a=Fu(i);if(a[0]==="auto"&&a[1]==="auto")return;if(a[0]==="cover"){t>s?(e.height=n,e.width=J0(t,n)):(e.width=r,e.height=Pu(t,r));return}if(a[0]==="contain"){t>s?(e.width=r,e.height=Pu(t,r)):(e.height=n,e.width=J0(t,n));return}let o=-1;a[0]&&(Array.isArray(a[0])?a[0][1]==="%"?(e.width=a[0][0]/100*r,o=e.width):(e.width=a[0][0],o=a[0][0]):a[0]==="auto"&&a[1]&&(a[1][1]==="%"?e.width=J0(t,a[1][0]/100*n):e.width=J0(t,a[1][0]))),a[1]&&Array.isArray(a[1])?a[1][1]==="%"?e.height=a[1][0]/100*n:e.height=a[1][0]:o!==-1&&(e.height=Pu(t,o))}},oE=({backgroundPosition:i,drawOpt:e,imgWidth:t,imgHeight:r,canvasWidth:n,canvasHeight:s})=>{if(i){let a=Fu(i);if(a=a.map(o=>typeof o=="string"&&i6[o]!==void 0?[i6[o],"%"]:o),Array.isArray(a[0])){if(a.length===1&&a.push([50,"%"]),a[0][1]==="%"){let o=a[0][0]/100*n,l=a[0][0]/100*t;e.x=o-l}else e.x=a[0][0];if(a[1][1]==="%"){let o=a[1][0]/100*s,l=a[1][0]/100*r;e.y=o-l}else e.y=a[1][0]}}},lE=({ctx:i,image:e,backgroundRepeat:t,drawOpt:r,imgWidth:n,imgHeight:s,canvasWidth:a,canvasHeight:o})=>{if(t){let l=r.x,h=r.y,d=Math.ceil(l/n),c=Math.ceil(h/s),f=l-d*n,m=h-c*s,g=Fu(t);if(g[0]==="no-repeat"||n>=a&&s>=o)return;if(g[0]==="repeat-x"&&a>n){let x=f;for(;xs){let x=m;for(;xs){let y=m;for(;y{i.drawImage(e,t.sx,t.sy,t.swidth,t.sheight,t.x,t.y,t.width,t.height)},hE=(i,e,t,r,{backgroundSize:n,backgroundPosition:s,backgroundRepeat:a},o=()=>{})=>{let l=e/t,h=new Image;h.src=r,h.onload=()=>{let d=h.width,c=h.height,f=d/c,m={sx:0,sy:0,swidth:d,sheight:c,x:0,y:0,width:d,height:c};aE({backgroundSize:n,drawOpt:m,imageRatio:f,canvasWidth:e,canvasHeight:t,canvasRatio:l}),oE({backgroundPosition:s,drawOpt:m,imgWidth:m.width,imgHeight:m.height,imageRatio:f,canvasWidth:e,canvasHeight:t,canvasRatio:l}),lE({ctx:i,image:h,backgroundRepeat:a,drawOpt:m,imgWidth:m.width,imgHeight:m.height,imageRatio:f,canvasWidth:e,canvasHeight:t,canvasRatio:l})||eh(i,h,m),o()},h.onerror=d=>{o(d)}},r6=hE});var qu,dE,cE,s6,a6=T(()=>{pe();qu=i=>i.richText?j0(i.text):i.text,dE=i=>new Array(i).fill("#").join(""),cE=i=>new Array(i-6).fill(" ").join("")+"*",s6=i=>{let e="";return de(i,null,(t,r,n,s)=>{let a=s+1;a<=6?e+=dE(a):e+=cE(a),e+=" "+qu(t.data);let o=t.data.generalization;if(Array.isArray(o))e+=o.map(l=>` [${qu(l)}]`);else if(o&&o.text){let l=qu(o);e+=` [${l}]`}e+=` + `,this.mindMap.el.appendChild(this.mindMap.commonCaches.measureCustomNodeContentSizeEl)),this.mindMap.commonCaches.measureCustomNodeContentSizeEl.innerHTML="",this.mindMap.commonCaches.measureCustomNodeContentSizeEl.appendChild(i);let e=this.mindMap.commonCaches.measureCustomNodeContentSizeEl.getBoundingClientRect();return{width:e.width,height:e.height}}function nE(){return!!this._customNodeContent}var GN,n6,Fu,s6=T(()=>{pe();wt();r6();Ge();GN=(i,e)=>{let t=new Le,r=new _e().text(i);return e.text(r),t.add(r),t.bbox()},n6={radius:3,fontSize:12,fill:"",height:20,paddingX:8};Fu={createImgNode:WN,getImgShowSize:VN,createIconNode:YN,createRichTextNode:XN,createTextNode:KN,createHyperlinkNode:ZN,createTagNode:QN,createNoteNode:JN,createAttachmentNode:eE,getNoteContentPosition:iE,getNodeIconSize:tE,measureCustomNodeContentSize:rE,isUseCustomNodeContent:nE}});function sE(){if(this.getChildrenLength()<=0||this.isRoot)return;let{alwaysShowExpandBtn:i,notShowExpandBtn:e,expandBtnSize:t}=this.mindMap.opt;if(!i&&!e){let{width:r,height:n}=this;this._unVisibleRectRegionNode||(this._unVisibleRectRegionNode=new Ve,this._unVisibleRectRegionNode.fill({color:"transparent"})),this.group.add(this._unVisibleRectRegionNode),this.renderer.layout.renderExpandBtnRect(this._unVisibleRectRegionNode,t,r,n,this)}}function aE(){this._unVisibleRectRegionNode&&(this._unVisibleRectRegionNode.remove(),this._unVisibleRectRegionNode=null)}function oE(){this.needRerenderExpandBtnPlaceholderRect&&(this.needRerenderExpandBtnPlaceholderRect=!1,this.renderExpandBtnPlaceholderRect()),this.getChildrenLength()>0?this._unVisibleRectRegionNode||this.renderExpandBtnPlaceholderRect():this._unVisibleRectRegionNode&&this.clearExpandBtnPlaceholderRect()}var qu,a6=T(()=>{wt();qu={renderExpandBtnPlaceholderRect:sE,clearExpandBtnPlaceholderRect:aE,updateExpandBtnPlaceholderRect:oE}});function lE(){this.checkEnableDragModifyNodeWidth()&&(this._dragHandleNodes=null,this.dragHandleWidth=4,this.dragHandleMousedownX=0,this.isDragHandleMousedown=!1,this.dragHandleIndex=0,this.dragHandleMousedownCustomTextWidth=0,this.dragHandleMousedownBodyCursor="",this.dragHandleMousedownLeft=0,this.onDragMousemoveHandle=this.onDragMousemoveHandle.bind(this),window.addEventListener("mousemove",this.onDragMousemoveHandle),this.onDragMouseupHandle=this.onDragMouseupHandle.bind(this),window.addEventListener("mouseup",this.onDragMouseupHandle),this.mindMap.on("node_mouseup",this.onDragMouseupHandle))}function hE(i){if(!this.isDragHandleMousedown)return;i.stopPropagation(),i.preventDefault();let{minNodeTextModifyWidth:e,maxNodeTextModifyWidth:t,isUseCustomNodeContent:r,customCreateNodeContent:n}=this.mindMap.opt,s=r&&n&&this._customNodeContent;document.body.style.cursor="ew-resize",this.group.css({cursor:"ew-resize"});let{scaleX:a}=this.mindMap.draw.transform(),o=i.clientX-this.dragHandleMousedownX,l=this.dragHandleMousedownCustomTextWidth+(this.dragHandleIndex===0?-o:o)/a;if(l=Math.max(l,e),t!==-1&&(l=Math.min(l,t)),!s&&this.getData("image")){let h=this.getImgShowSize();this._rectInfo.textContentWidth-this.customTextWidth+l<=h[0]&&(l=h[0]+this.customTextWidth-this._rectInfo.textContentWidth)}this.customTextWidth=l,this.dragHandleIndex===0&&(this.left=this.dragHandleMousedownLeft+o/a),this.reRender(s?[]:["text"],{ignoreUpdateCustomTextWidth:!0})}function dE(){this.isDragHandleMousedown&&(document.body.style.cursor=this.dragHandleMousedownBodyCursor,this.group.css({cursor:"default"}),this.isDragHandleMousedown=!1,this.dragHandleMousedownX=0,this.dragHandleIndex=0,this.dragHandleMousedownCustomTextWidth=0,this.setData({customTextWidth:this.customTextWidth}),this.mindMap.render(),this.mindMap.emit("dragModifyNodeWidthEnd",this))}function cE(){let i=[new Ve,new Ve];return i.forEach((e,t)=>{e.size(this.dragHandleWidth,this.height).fill({color:"transparent"}).css({cursor:"ew-resize"}),e.on("mousedown",r=>{r.stopPropagation(),r.preventDefault(),this.dragHandleMousedownX=r.clientX,this.dragHandleIndex=t,this.dragHandleMousedownCustomTextWidth=this.customTextWidth===void 0?this._textData?this._textData.width:this.width:this.customTextWidth,this.dragHandleMousedownBodyCursor=document.body.style.cursor,this.dragHandleMousedownLeft=this.left,this.isDragHandleMousedown=!0})}),i}function uE(){this.checkEnableDragModifyNodeWidth()&&(this._dragHandleNodes||(this._dragHandleNodes=this.createDragHandleNode()),this.getData("isActive")?(this._dragHandleNodes.forEach(i=>{i.height(this.height),this.group.add(i)}),this._dragHandleNodes[1].x(this.width-this.dragHandleWidth)):this._dragHandleNodes.forEach(i=>{i.remove()}))}var Hu,o6=T(()=>{wt();Hu={initDragHandle:lE,onDragMousemoveHandle:hE,onDragMouseupHandle:dE,createDragHandleNode:cE,updateDragHandle:uE}});function fE(){this.mindMap.cooperate&&(this._userListGroup=new Le,this.group.add(this._userListGroup))}function mE(i){let{avatarSize:e,fontSize:t}=this.mindMap.opt.cooperateStyle,r=new Le,n=i.isMore?i.name:String(i.name)[0],s=new Wi().size(e,e);s.fill({color:i.color||J0(n)});let a=new _e().text(n).fill({color:"#fff"}).css({"font-size":t+"px"}).dx(-t/2).dy((e-t)/2);return r.add(s).add(a),r}function pE(i){let{avatarSize:e}=this.mindMap.opt.cooperateStyle;return new $i().load(i.avatar).size(e,e)}function gE(){if(!this._userListGroup)return;let{avatarSize:i}=this.mindMap.opt.cooperateStyle;this._userListGroup.clear();let e=this.userList.length,t=Math.floor(this.width/i),r=[];e>t?r.push(...this.userList.slice(0,t-1),{isMore:!0,name:"+"+(e-t+1)}):r.push(...this.userList),r.forEach((n,s)=>{let a=null;n.avatar?a=this.createImageAvatar(n):a=this.createTextAvatar(n),a.on("click",o=>{this.mindMap.emit("node_cooperate_avatar_click",n,this,a,o)}),a.on("mouseenter",o=>{this.mindMap.emit("node_cooperate_avatar_mouseenter",n,this,a,o)}),a.on("mouseleave",o=>{this.mindMap.emit("node_cooperate_avatar_mouseleave",n,this,a,o)}),a.x(s*i).cy(-i/2),this._userListGroup.add(a)})}function xE(i){this.userList.find(e=>e.id==i.id)||(this.userList.push(i),this.updateUserListNode())}function yE(i){let e=this.userList.findIndex(t=>t.id==i.id);e!==-1&&(this.userList.splice(e,1),this.updateUserListNode())}function vE(){this.userList=[],this.updateUserListNode()}var Uu,l6=T(()=>{wt();pe();Uu={createUserListNode:fE,updateUserListNode:gE,createTextAvatar:mE,createImageAvatar:pE,addUser:xE,removeUser:yE,emptyUser:vE}});function bE(){this.isGeneralization||(this._quickCreateChildBtn=null,this._showQuickCreateChildBtn=!1)}function wE(){if(!(this.isGeneralization||this.getChildrenLength()>0)){if(this._quickCreateChildBtn)this.group.add(this._quickCreateChildBtn);else{let{quickCreateChildBtnIcon:i,expandBtnStyle:e,expandBtnSize:t}=this.mindMap.opt,{icon:r,style:n}=i,{color:s,fill:a}=e||{color:"#808080",fill:"#fff"};s=n.color||s;let o=ke(r||Yo.quickCreateChild).size(t,t);o.css({cursor:"pointer"}),o.x(0).y(-t/2),this.style.iconNode(o,s);let l=new Wi().size(t);l.x(0).y(-t/2),l.fill({color:a}).css({cursor:"pointer"}),this._quickCreateChildBtn=new Le,this._quickCreateChildBtn.add(l).add(o),this._quickCreateChildBtn.on("click",h=>{h.stopPropagation(),this.mindMap.emit("quick_create_btn_click",this);let{customQuickCreateChildBtnClick:d}=this.mindMap.opt;if(typeof d=="function"){d(this);return}this.mindMap.execCommand("INSERT_CHILD_NODE",!0,[this])}),this._quickCreateChildBtn.on("dblclick",h=>{h.stopPropagation()}),this._quickCreateChildBtn.addClass("smm-quick-create-child-btn"),this.group.add(this._quickCreateChildBtn)}this._showQuickCreateChildBtn=!0,this.renderer.layout.renderExpandBtn(this,this._quickCreateChildBtn)}}function ME(){this.isGeneralization||this._quickCreateChildBtn&&this._showQuickCreateChildBtn&&(this._quickCreateChildBtn.remove(),this._showQuickCreateChildBtn=!1)}function TE(){if(this.isGeneralization)return;let{isActive:i}=this.getData();i||this.removeQuickCreateChildBtn()}var $u,h6=T(()=>{Ou();wt();$u={initQuickCreateChildBtn:bE,showQuickCreateChildBtn:wE,removeQuickCreateChildBtn:ME,hideQuickCreateChildBtn:TE}});function NE(i,e,t,r,n){let{imgTextMargin:s}=this.mindMap.opt;return i==="v"?r>0&&n>0?s:0:e>0&&t>0?s:0}function EE(i){let e=0,t=this._tagData.reduce((r,n)=>(e=Math.max(e,n.height),r+=n.width),0);return t+=(this._tagData.length-1)*i,{width:t,height:e}}function SE(){if(this.isUseCustomNodeContent()){let L=this.measureCustomNodeContentSize(this._customNodeContent);return{width:this.hasCustomWidth()?this.customTextWidth:L.width,height:L.height}}let{TAG_PLACEMENT:i,IMG_PLACEMENT:e}=C,{textContentMargin:t}=this.mindMap.opt,n=(this.getStyle("tagPlacement")||i.RIGHT)===i.BOTTOM,s=this.getStyle("imgPlacement")||e.TOP,a=0,o=0,l=0,h=0,d=0,c=0,f=0;if(this._imgData&&(a=this._imgData.width,o=this._imgData.height),this.mindMap.nodeInnerPrefixList.forEach(L=>{let I=this[`_${L.name}Data`];I&&(l+=I.width,h=Math.max(h,I.height),f++)}),this._prefixData&&(l+=this._prefixData.width,h=Math.max(h,this._prefixData.height),f++),this._iconData.length>0&&(l+=this._iconData.reduce((L,I)=>(h=Math.max(h,I.height),L+=I.width),0)+(this._iconData.length-1)*t,f++),this._textData&&(l+=this._textData.width,h=Math.max(h,this._textData.height),f++),this._hyperlinkData&&(l+=this._hyperlinkData.width,h=Math.max(h,this._hyperlinkData.height),f++),this._tagData.length>0){let{width:L,height:I}=this.getTagContentSize(t);n?(d=L,c=I):(l+=L,h=Math.max(h,I),f++)}this._noteData&&(l+=this._noteData.width,h=Math.max(h,this._noteData.height),f++),this._attachmentData&&(l+=this._attachmentData.width,h=Math.max(h,this._attachmentData.height),f++),this._postfixData&&(l+=this._postfixData.width,h=Math.max(h,this._postfixData.height),f++),this.mindMap.nodeInnerPostfixList.forEach(L=>{let I=this[`_${L.name}Data`];I&&(l+=I.width,h=Math.max(h,I.height),f++)}),l+=(f-1)*t,n&&l>0&&c>0&&(this._rectInfo.textContentWidthWithoutTag=l,l=Math.max(l,d),h=h+t+c),this._rectInfo.textContentWidth=l,this._rectInfo.textContentHeight=h;let m=0,g=0;[e.TOP,e.BOTTOM].includes(s)?(m=Math.max(a,l),g=o+h+this.getImgTextMarin("v",0,0,o,h)):(m=a+l+this.getImgTextMarin("h",a,l),g=Math.max(o,h));let{paddingX:x,paddingY:y}=this.getPaddingVale(),{paddingX:b,paddingY:E}=this.shapeInstance.getShapePadding(m,g,x,y);this.shapePadding.paddingX=b,this.shapePadding.paddingY=E;let k=this.getBorderWidth();return{width:m+x*2+b*2+k,height:g+y*2+E*2+k}}function AE(){if(!this.group)return;this.group.clear();let{hoverRectPadding:i,openRealtimeRenderOnNodeTextEdit:e,textContentMargin:t,addCustomContentToNode:r}=this.mindMap.opt,{width:n,height:s}=this,{paddingX:a,paddingY:o}=this.getPaddingVale(),l=this.getBorderWidth()/2;a+=this.shapePadding.paddingX+l,o+=this.shapePadding.paddingY+l,this.shapeNode=this.shapeInstance.createShape(),this.shapeNode.addClass("smm-node-shape"),this.shapeNode.translate(l,l),this.style.shape(this.shapeNode),this.group.add(this.shapeNode),this.renderExpandBtnPlaceholderRect(),this.createUserListNode&&this.createUserListNode(),this.isGeneralization&&this.generalizationBelongNode&&this.group.addClass("generalization_"+this.generalizationBelongNode.uid);let h=()=>{this.hoverNode=new Ve().size(n+i*2,s+i*2).x(-i).y(-i),this.hoverNode.addClass("smm-hover-node"),this.style.hoverNode(this.hoverNode,n,s),this.group.add(this.hoverNode)};if(this.isUseCustomNodeContent()){let oe=mn({el:this._customNodeContent,width:n,height:s});this.group.add(oe),h();return}let{IMG_PLACEMENT:d,TAG_PLACEMENT:c}=C,f=this.getStyle("imgPlacement")||d.TOP,g=(this.getStyle("tagPlacement")||c.RIGHT)===c.BOTTOM,{textContentWidth:x,textContentHeight:y,textContentWidthWithoutTag:b}=this._rectInfo,E=y,k=0,L=0,I=this._tagData&&this._tagData.length>0;if(I){let oe=this.getTagContentSize(t);k=oe.width,L=oe.height,g&&(y-=L+t)}let O=0,q=0;if(this._imgData)switch(O=this._imgData.width,q=this._imgData.height,this.group.add(this._imgData.node),f){case d.TOP:this._imgData.node.cx(n/2).y(o);break;case d.BOTTOM:this._imgData.node.cx(n/2).y(s-o-q);break;case d.LEFT:this._imgData.node.x(a).cy(s/2);break;case d.RIGHT:this._imgData.node.x(n-a-O).cy(s/2);break;default:break}let F=new Le,W=0;if(I&&g&&(W=b{let he=this[`_${oe.name}Data`];he&&(he.node.x(W).y((y-he.height)/2),F.add(he.node),W+=he.width+t)}),this._prefixData){let oe=mn({el:this._prefixData.el,width:this._prefixData.width,height:this._prefixData.height});oe.x(W).y((y-this._prefixData.height)/2),F.add(oe),W+=this._prefixData.width+t}let ne=new Le;if(this._iconData&&this._iconData.length>0){let oe=0;this._iconData.forEach(he=>{he.node.x(W+oe).y((y-he.height)/2),ne.add(he.node),oe+=he.width+t}),F.add(ne),W+=oe}if(this._textData){let oe=this._textData.node.attr("data-offsetx")||0;this._textData.node.attr("data-offsetx",W),(this._textData.nodeContent||this._textData.node).x(-oe).x(W).y((y-this._textData.height)/2),e&&this._textData.node.opacity(this.mindMap.renderer.textEdit.getCurrentEditNode()===this?0:1),F.add(this._textData.node),W+=this._textData.width+t}this._hyperlinkData&&(this._hyperlinkData.node.x(W).y((y-this._hyperlinkData.height)/2),F.add(this._hyperlinkData.node),W+=this._hyperlinkData.width+t);let re=new Le;if(I)if(g){let oe=0;this._tagData.forEach(he=>{he.node.x(oe).y((L-he.height)/2),re.add(he.node),oe+=he.width+t}),re.x((x-k)/2).y(E-L),F.add(re)}else{let oe=0;this._tagData.forEach(he=>{he.node.x(W+oe).y((y-he.height)/2),re.add(he.node),oe+=he.width+t}),F.add(re),W+=oe}if(this._noteData&&(this._noteData.node.x(W).y((y-this._noteData.height)/2),F.add(this._noteData.node),W+=this._noteData.width+t),this._attachmentData&&(this._attachmentData.node.x(W).y((y-this._attachmentData.height)/2),F.add(this._attachmentData.node),W+=this._attachmentData.width+t),this._postfixData){let oe=mn({el:this._postfixData.el,width:this._postfixData.width,height:this._postfixData.height});oe.x(W).y((y-this._postfixData.height)/2),F.add(oe),W+=this._postfixData.width+t}this.mindMap.nodeInnerPostfixList.forEach(oe=>{let he=this[`_${oe.name}Data`];he&&(he.node.x(W).y((y-he.height)/2),F.add(he.node),W+=he.width+t)}),this.group.add(F);let{width:le,height:Ce}=F.bbox(),te=0,Q=0;switch(f){case d.TOP:te=n/2-le/2,Q=o+q+this.getImgTextMarin("v",0,0,q,E);break;case d.BOTTOM:te=n/2-le/2,Q=o;break;case d.LEFT:te=O+a+this.getImgTextMarin("h",O,x),Q=s/2-Ce/2;break;case d.RIGHT:te=a,Q=s/2-Ce/2;break}if(F.translate(te,Q),h(),this._customContentAddToNodeAdd&&this._customContentAddToNodeAdd.el){let oe=mn(this._customContentAddToNodeAdd);this.group.add(oe),r&&typeof r.handle=="function"&&r.handle({content:this._customContentAddToNodeAdd,element:oe,node:this})}this.mindMap.emit("node_layout_end",this)}var ju,d6=T(()=>{Ge();wt();pe();ju={getImgTextMarin:NE,getTagContentSize:EE,getNodeRect:SE,layout:AE}});var Gu,ca,rh=T(()=>{ih();Ru();wt();J2();e6();t6();s6();a6();o6();l6();h6();d6();Ge();pe();Gu=class i{constructor(e={}){this.opt=e,this.nodeData=this.handleData(e.data||{}),this.nodeDataSnapshot="",this.uid=e.uid,this.mindMap=e.mindMap,this.renderer=e.renderer,this.draw=this.mindMap.draw,this.nodeDraw=this.mindMap.nodeDraw,this.lineDraw=this.mindMap.lineDraw,this.style=new Wo(this),this.effectiveStyles={},this.shapeInstance=new Vo(this),this.shapePadding={paddingX:0,paddingY:0},this.isRoot=e.isRoot===void 0?!1:e.isRoot,this.isGeneralization=e.isGeneralization===void 0?!1:e.isGeneralization,this.generalizationBelongNode=null,this.layerIndex=e.layerIndex===void 0?0:e.layerIndex,this.width=e.width||0,this.height=e.height||0,this.customTextWidth=e.data.data.customTextWidth||void 0,this._left=e.left||0,this._top=e.top||0,this.customLeft=e.data.data.customLeft||void 0,this.customTop=e.data.data.customTop||void 0,this.isDrag=!1,this.parent=e.parent||null,this.children=e.children||[],this.userList=[],this.group=null,this.shapeNode=null,this.hoverNode=null,this._customNodeContent=null,this._imgData=null,this._iconData=null,this._textData=null,this._hyperlinkData=null,this._tagData=null,this._noteData=null,this.noteEl=null,this.noteContentIsShow=!1,this._attachmentData=null,this._prefixData=null,this._postfixData=null,this._expandBtn=null,this._lastExpandBtnType=null,this._showExpandBtn=!1,this._openExpandNode=null,this._closeExpandNode=null,this._fillExpandNode=null,this._userListGroup=null,this._lines=[],this._generalizationList=[],this._unVisibleRectRegionNode=null,this._isMouseenter=!1,this._customContentAddToNodeAdd=null,this._rectInfo={textContentWidth:0,textContentHeight:0,textContentWidthWithoutTag:0},this._generalizationNodeWidth=0,this._generalizationNodeHeight=0,this.expandBtnSize=this.mindMap.opt.expandBtnSize,this.isMultipleChoice=!1,this.needLayout=!1,this.isHide=!1;let t=Object.getPrototypeOf(this);t.bindEvent||(Object.keys(ju).forEach(r=>{t[r]=ju[r]}),Object.keys(Du).forEach(r=>{t[r]=Du[r]}),Object.keys(Bu).forEach(r=>{t[r]=Bu[r]}),Object.keys(qu).forEach(r=>{t[r]=qu[r]}),Object.keys(Pu).forEach(r=>{t[r]=Pu[r]}),Object.keys(Fu).forEach(r=>{t[r]=Fu[r]}),this.mindMap.cooperate&&Object.keys(Uu).forEach(r=>{t[r]=Uu[r]}),Object.keys(Hu).forEach(r=>{t[r]=Hu[r]}),this.mindMap.opt.isShowCreateChildBtnIcon&&(Object.keys($u).forEach(r=>{t[r]=$u[r]}),this.initQuickCreateChildBtn()),t.bindEvent=!0),this.getSize(),this.updateGeneralization(),this.initDragHandle()}get left(){return this.customLeft||this._left}set left(e){this._left=e}get top(){return this.customTop||this._top}set top(e){this._top=e}reset(){this.children=[],this.parent=null,this.isRoot=!1,this.layerIndex=0,this.left=0,this.top=0}resetWhenDelete(){this._isMouseenter=!1}handleData(e){return e.data.expand=e.data.expand!==!1,e.data.isActive=e.data.isActive===!0,e.children=e.children||[],e}createNodeData(e){let{isUseCustomNodeContent:t,customCreateNodeContent:r,createNodePrefixContent:n,createNodePostfixContent:s,addCustomContentToNode:a}=this.mindMap.opt,o=["custom","image","icon","text","hyperlink","tag","note","attachment","prefix","postfix",...this.mindMap.nodeInnerPrefixList.map(h=>h.name),...this.mindMap.nodeInnerPostfixList.map(h=>h.name)],l={};if(Array.isArray(e)?o.forEach(h=>{e.includes(h)&&(l[h]=!0)}):o.forEach(h=>{l[h]=!0}),t&&r&&l.custom&&(this._customNodeContent=r(this)),this._customNodeContent){sr(this._customNodeContent);return}l.image&&(this._imgData=this.createImgNode()),l.icon&&(this._iconData=this.createIconNode()),l.text&&(this._textData=this.createTextNode()),l.hyperlink&&(this._hyperlinkData=this.createHyperlinkNode()),l.tag&&(this._tagData=this.createTagNode()),l.note&&(this._noteData=this.createNoteNode()),l.attachment&&(this._attachmentData=this.createAttachmentNode()),this.mindMap.nodeInnerPrefixList.forEach(h=>{l[h.name]&&(this[`_${h.name}Data`]=h.createContent(this))}),l.prefix&&(this._prefixData=n?n(this):null,this._prefixData&&this._prefixData.el&&sr(this._prefixData.el)),l.postfix&&(this._postfixData=s?s(this):null,this._postfixData&&this._postfixData.el&&sr(this._postfixData.el)),this.mindMap.nodeInnerPostfixList.forEach(h=>{l[h.name]&&(this[`_${h.name}Data`]=h.createContent(this))}),a&&typeof a.create=="function"&&(this._customContentAddToNodeAdd=a.create(this),this._customContentAddToNodeAdd&&this._customContentAddToNodeAdd.el&&sr(this._customContentAddToNodeAdd.el))}getSize(e,t={}){t.ignoreUpdateCustomTextWidth||(this.customTextWidth=this.getData("customTextWidth")||void 0),this.customLeft=this.getData("customLeft")||void 0,this.customTop=this.getData("customTop")||void 0,this.createNodeData(e);let{width:n,height:s}=this.getNodeRect(),a=this.width!==n||this.height!==s;return this.width=n,this.height=s,a}bindGroupEvent(){this.group.on("click",e=>{if(this.mindMap.emit("node_click",this,e),this.isMultipleChoice){e.stopPropagation(),this.isMultipleChoice=!1;return}this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate&&this.userList.length>0||this.active(e)}),this.group.on("mousedown",e=>{let{readonly:t,enableCtrlKeyNodeSelection:r,useLeftKeySelectionRightKeyDrag:n,mousedownEventPreventDefault:s}=this.mindMap.opt;if(s&&e.preventDefault(),t||(this.isRoot?e.which===3&&!n&&e.stopPropagation():e.which!==2&&e.stopPropagation()),!t&&(e.ctrlKey||e.metaKey)&&r){this.isMultipleChoice=!0;let a=this.getData("isActive");a||this.mindMap.emit("before_node_active",this,this.renderer.activeNodeList),this.mindMap.renderer[a?"removeNodeFromActiveList":"addNodeToActiveList"](this,!0),this.renderer.emitNodeActiveEvent(a?null:this)}this.mindMap.emit("node_mousedown",this,e)}),this.group.on("mouseup",e=>{!this.isRoot&&e.which!==2&&!this.mindMap.opt.readonly&&e.stopPropagation(),this.mindMap.emit("node_mouseup",this,e)}),this.group.on("mouseenter",e=>{this.isDrag||(this._isMouseenter=!0,this.showExpandBtn(),this.isGeneralization&&this.handleGeneralizationMouseenter(),this.mindMap.emit("node_mouseenter",this,e))}),this.group.on("mouseleave",e=>{this._isMouseenter&&(this._isMouseenter=!1,this.hideExpandBtn(),this.isGeneralization&&this.handleGeneralizationMouseleave(),this.mindMap.emit("node_mouseleave",this,e))}),this.group.on("dblclick",e=>{let{readonly:t,onlyOneEnableActiveNodeOnCooperate:r}=this.mindMap.opt;t||e.ctrlKey||e.metaKey||(e.stopPropagation(),!(r&&this.userList.length>0)&&this.mindMap.emit("node_dblclick",this,e))}),this.group.on("contextmenu",e=>{let{readonly:t,useLeftKeySelectionRightKeyDrag:r}=this.mindMap.opt;t||e.ctrlKey||(e.stopPropagation(),e.preventDefault(),!(this.mindMap.select&&!r&&this.mindMap.select.hasSelectRange())&&(this.getData("isActive")&&this.renderer.activeNodeList.length===1||(this.renderer.clearActiveNodeList(),this.active(e)),this.mindMap.emit("node_contextmenu",e,this)))})}active(e){this.mindMap.opt.readonly||(e&&e.stopPropagation(),!this.getData("isActive")&&(this.mindMap.emit("before_node_active",this,this.renderer.activeNodeList),this.renderer.clearActiveNodeList(),this.renderer.addNodeToActiveList(this,!0),this.renderer.emitNodeActiveEvent(this)))}deactivate(){this.mindMap.renderer.removeNodeFromActiveList(this),this.mindMap.renderer.emitNodeActiveEvent()}update(e){if(!this.group)return;this.updateNodeActiveClass();let{alwaysShowExpandBtn:t,notShowExpandBtn:r,isShowCreateChildBtnIcon:n,readonly:s}=this.mindMap.opt,a=this.getChildrenLength();if(!r)if(t)this._expandBtn&&a<=0?this.removeExpandBtn():this.renderExpandBtn();else{let{isActive:l,expand:h}=this.getData();a<=0?this.removeExpandBtn():h&&!l&&!this._isMouseenter?this.hideExpandBtn():this.showExpandBtn()}if(n)if(a>0)this.removeQuickCreateChildBtn();else{let{isActive:l}=this.getData();l?this.showQuickCreateChildBtn():this.hideQuickCreateChildBtn()}this.updateDragHandle(),this.renderGeneralization(e),this.updateUserListNode&&this.updateUserListNode();let o=this.group.transform();this.nodeDataSnapshot=s?"":JSON.stringify(this.getData()),(this.left!==o.translateX||this.top!==o.translateY)&&this.group.translate(this.left-o.translateX,this.top-o.translateY)}getNodePosInClient(e,t){let r=this.mindMap.draw.transform(),{scaleX:n,scaleY:s,translateX:a,translateY:o}=r,l=e*n+a,h=t*s+o;return{left:l,top:h}}checkIsInClient(e=0){let{left:t,top:r}=this.getNodePosInClient(this.left,this.top);return t+this.width>0-e&&r+this.height>0-e&&t{},t=!1,r=!1){this.renderLine();let{openPerformance:n,performanceConfig:s}=this.mindMap.opt;if(t||!n||this.checkIsInClient(s.padding)||this.isRoot?this.group?(this.nodeDraw.has(this.group)||this.nodeDraw.add(this.group),this.needLayout&&(this.needLayout=!1,this.layout()),this.updateExpandBtnPlaceholderRect(),this.update(t)):(this.group=new Le,this.group.addClass("smm-node"),this.group.css({cursor:"default"}),this.bindGroupEvent(),this.nodeDraw.add(this.group),this.layout(),this.update(t)):n&&s.removeNodeWhenOutCanvas&&this.removeSelf(),this.children&&this.children.length&&this.getData("expand")!==!1){let a=0;this.children.forEach(o=>{let l=()=>{o.render(()=>{a++,a>=this.children.length&&e()},t,r)};r?setTimeout(l,0):l()})}else e();this.nodeData.inserting&&(delete this.nodeData.inserting,this.active(),this.mindMap.emit("node_dblclick",this,null,!0))}removeSelf(){this.group&&(this.group.remove(),this.removeGeneralization())}remove(){this.group&&(this.group.remove(),this.removeGeneralization(),this.removeLine(),this.children&&this.children.length&&this.children.forEach(e=>{e.remove()}))}destroy(){this.removeLine(),this.parent&&this.parent.removeLine(),this.group&&(this.emptyUser&&this.emptyUser(),this.resetWhenDelete(),this.group.remove(),this.removeGeneralization(),this.group=null,this.style.onRemove())}hide(){if(this.group&&this.group.hide(),this.hideGeneralization(),this.parent){let e=this.parent.children.indexOf(this);this.parent._lines[e]&&this.parent._lines[e].hide(),this._lines.forEach(t=>{t.hide()})}this.children&&this.children.length&&this.children.forEach(e=>{e.hide()})}show(){if(this.group){if(this.group.show(),this.showGeneralization(),this.parent){let e=this.parent.children.indexOf(this);this.parent._lines[e]&&this.parent._lines[e].show(),this._lines.forEach(t=>{t.show()})}this.children&&this.children.length&&this.children.forEach(e=>{e.show()})}}setOpacity(e){this.group&&this.group.opacity(e),this._lines.forEach(t=>{t.opacity(e)}),this.children.forEach(t=>{t.setOpacity(e)}),this.setGeneralizationOpacity(e)}hideChildren(){this._lines.forEach(e=>{e.hide()}),this.children&&this.children.length&&this.children.forEach(e=>{e.hide()})}showChildren(){this._lines.forEach(e=>{e.show()}),this.children&&this.children.length&&this.children.forEach(e=>{e.show()})}startDrag(){this.isDrag=!0,this.group&&this.group.addClass("smm-node-dragging")}endDrag(){this.isDrag=!1,this.group&&this.group.removeClass("smm-node-dragging")}renderLine(e=!1){if(this.getData("expand")===!1)return;let t=this.getChildrenLength();this.mindMap.renderer.layout.nodeIsRemoveAllLines&&this.mindMap.renderer.layout.nodeIsRemoveAllLines(this)&&(t=0),t>this._lines.length?new Array(t-this._lines.length).fill(0).forEach(()=>{this._lines.push(this.lineDraw.path())}):t{r.remove()}),this._lines=this._lines.slice(0,t)),this.renderer.layout.renderLine(this,this._lines,(...r)=>{this.styleLine(...r)},this.style.getStyle("lineStyle",!0)),e&&this.children&&this.children.length>0&&this.children.forEach(r=>{r.renderLine(e)})}getShape(){return this.mindMap.themeConfig.nodeUseLineStyle?C.SHAPE.RECTANGLE:this.style.getStyle("shape",!1,!1)}hasCustomPosition(){return this.customLeft!==void 0&&this.customTop!==void 0}ancestorHasCustomPosition(){let e=this;for(;e;){if(e.hasCustomPosition())return!0;e=e.parent}return!1}ancestorHasGeneralization(){let e=this.parent;for(;e;){if(e.checkHasGeneralization())return!0;e=e.parent}return!1}addChildren(e){this.children.push(e)}styleLine(e,t,r){let{enableInheritAncestorLineStyle:n}=this.mindMap.opt,s=n?"getSelfInhertStyle":"getSelfStyle",a=t[s]("lineWidth")||t.getStyle("lineWidth",!0),o=t[s]("lineColor")||this.getRainbowLineColor(t)||t.getStyle("lineColor",!0),l=t[s]("lineDasharray")||t.getStyle("lineDasharray",!0);this.style.line(e,{width:a,color:o,dasharray:l},r,t)}getRainbowLineColor(e){return this.mindMap.rainbowLines?this.mindMap.rainbowLines.getNodeColor(e):""}removeLine(){this._lines.forEach(e=>{e.remove()}),this._lines=[]}isAncestor(e){if(this.uid===e.uid)return!1;let t=e.parent;for(;t;){if(this.uid===t.uid)return!0;t=t.parent}return!1}isParent(e){if(this.uid===e.uid)return!1;let t=e.parent;return!!(t&&this.uid===t.uid)}isBrother(e){return!this.parent||this.uid===e.uid?!1:this.parent.children.find(t=>t.uid===e.uid)}getIndexInBrothers(){return this.parent&&this.parent.children?this.parent.children.findIndex(e=>e.uid===this.uid):-1}getPaddingVale(){return{paddingX:this.getStyle("paddingX"),paddingY:this.getStyle("paddingY")}}getStyle(e,t){let r=this.style.merge(e,t);return r===void 0?"":r}getSelfStyle(e){return this.style.getSelfStyle(e)}getParentSelfStyle(e){return this.parent?this.parent.getSelfStyle(e)||this.parent.getParentSelfStyle(e):null}getSelfInhertStyle(e){return this.getSelfStyle(e)||this.getParentSelfStyle(e)}getBorderWidth(){return this.style.merge("borderWidth",!1)||0}getData(e){return e?this.nodeData.data[e]:this.nodeData.data}getPureData(e=!0,t=!1){return ls({},this,e,t)}getAncestorNodes(){let e=[],t=this.parent;for(;t;)e.unshift(t),t=t.parent;return e}hasCustomStyle(){return this.style.hasCustomStyle()}getRect(){return this.group?this.group.rbox():null}getRectInSvg(){let{scaleX:e,scaleY:t,translateX:r,translateY:n}=this.mindMap.draw.transform(),{left:s,top:a,width:o,height:l}=this,h=(s+o)*e+r,d=(a+l)*t+n;return s=s*e+r,a=a*t+n,{left:s,right:h,top:a,bottom:d,width:o*e,height:l*t}}highlight(){this.group&&this.group.addClass("smm-node-highlight")}closeHighlight(){this.group&&this.group.removeClass("smm-node-highlight")}fakeClone(){let e=new i({...this.opt,uid:Tt()});return Object.keys(this).forEach(t=>{e[t]=this[t]}),e}createSvgTextNode(e=""){return new _e().text(e)}getSvgObjects(){return{SVG:ke,G:Le,Rect:Ve}}checkEnableDragModifyNodeWidth(){let{enableDragModifyNodeWidth:e,isUseCustomNodeContent:t,customCreateNodeContent:r}=this.mindMap.opt;return e&&(this.mindMap.richText||t&&r)}hasCustomWidth(){return this.checkEnableDragModifyNodeWidth()&&this.customTextWidth!==void 0}getChildrenLength(){return this.nodeData.children?this.nodeData.children.length:0}},ca=Gu});var Ko,c6=T(()=>{Ko=class{constructor(e){this.max=e||1e3,this.size=0,this.pool=new Map}add(e,t){return!this.has(e)&&this.size>=this.max?!1:(this.delete(e),this.pool.set(e,t),this.size++,!0)}delete(e){this.pool.has(e)&&(this.pool.delete(e),this.size--)}has(e){return this.pool.has(e)}get(e){if(this.pool.has(e))return this.pool.get(e)}clear(){this.size=0,this.pool=new Map}}});var Wu,Nt,Ar=T(()=>{rh();Ge();c6();pe();Wu=class{constructor(e){this.renderer=e,this.mindMap=e.mindMap,this.draw=this.mindMap.draw,this.lineDraw=this.mindMap.lineDraw,this.root=null,this.lru=new Ko(this.mindMap.opt.maxNodeCacheCount),this.rootNodeCenterOffset=null}doLayout(){throw new Error("\u3010computed\u3011\u65B9\u6CD5\u4E3A\u5FC5\u8981\u65B9\u6CD5\uFF0C\u9700\u8981\u5B50\u7C7B\u8FDB\u884C\u91CD\u5199\uFF01")}renderLine(){throw new Error("\u3010renderLine\u3011\u65B9\u6CD5\u4E3A\u5FC5\u8981\u65B9\u6CD5\uFF0C\u9700\u8981\u5B50\u7C7B\u8FDB\u884C\u91CD\u5199\uFF01")}renderExpandBtn(){throw new Error("\u3010renderExpandBtn\u3011\u65B9\u6CD5\u4E3A\u5FC5\u8981\u65B9\u6CD5\uFF0C\u9700\u8981\u5B50\u7C7B\u8FDB\u884C\u91CD\u5199\uFF01")}renderGeneralization(){}cacheNode(e,t){this.renderer.nodeCache[e]=t,this.lru.add(e,t)}checkIsNeedResizeSources(){return this.renderer.checkHasRenderSource(C.CHANGE_THEME)}checkIsLayerTypeChange(e,t){if(e>=2&&t>=2)return!1;if(e>=2&&t<2||e<2&&t>=2)return!0}checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(e){this.renderer.checkHasRenderSource(C.CHANGE_LAYOUT)&&(e.needRerenderExpandBtnPlaceholderRect=!0)}checkIsNodeDataChange(e,t){if(e)e=typeof e=="string"?JSON.parse(e):e,e.isActive=t.isActive,e.expand=t.expand,e=JSON.stringify(e);else return!1;return e!==JSON.stringify(t)}checkNodeFixChange(e,t,r){let n=!1;this.mindMap.nodeInnerPrefixList.forEach(a=>{if(a.updateNodeData){let o=a.updateNodeData(e,t);o&&(n=o)}});let s=!1;return this.mindMap.nodeInnerPostfixList.forEach(a=>{if(a.updateNodeData){let o=a.updateNodeData(e,r);o&&(s=o)}}),n||s}createNode(e,t,r,n,s,a){let o={};this.mindMap.nodeInnerPrefixList.forEach(c=>{if(c.createNodeData){let[f,m]=c.createNodeData({data:e,parent:t,ancestors:a,layerIndex:n,index:s});o[f]=m}});let l={};this.mindMap.nodeInnerPostfixList.forEach(c=>{if(c.createNodeData){let[f,m]=c.createNodeData({data:e,parent:t,ancestors:a,layerIndex:n,index:s});l[f]=m}});let h=e.data.uid,d=null;if(e&&e._node&&!this.renderer.reRender){d=e._node;let c=this.checkIsLayerTypeChange(d.layerIndex,n);d.reset(),d.layerIndex=n,r?d.isRoot=!0:d.parent=t._node,this.cacheNode(e._node.uid,d),this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(d);let f=this.checkNodeFixChange(d,o,l),m=this.checkIsNeedResizeSources(),g=this.checkIsNodeDataChange(e._node.nodeDataSnapshot,e.data);(m||g||c||d.getData("resetRichText")||d.getData("needUpdate")||f)&&(d.getSize(),d.needLayout=!0),this.checkGetGeneralizationChange(d,m)}else if((this.lru.has(h)||this.renderer.lastNodeCache[h])&&!this.renderer.reRender){d=this.lru.get(h)||this.renderer.lastNodeCache[h];let c=JSON.stringify(d.getData()),f=this.checkIsLayerTypeChange(d.layerIndex,n);d.reset(),d.nodeData=d.handleData(e||{}),d.layerIndex=n,r?d.isRoot=!0:d.parent=t._node,this.cacheNode(h,d),this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(d),e._node=d;let m=this.checkIsNeedResizeSources(),g=this.checkIsNodeDataChange(c,e.data),x=this.checkNodeFixChange(d,o,l);(m||g||f||d.getData("resetRichText")||d.getData("needUpdate")||x)&&(d.getSize(),d.needLayout=!0),this.checkGetGeneralizationChange(d,m)}else{let c=h||Tt();d=new ca({data:e,uid:c,renderer:this.renderer,mindMap:this.mindMap,draw:this.draw,layerIndex:n,isRoot:r,parent:r?null:t._node,...o}),e.data.uid=c,this.cacheNode(c,d),e._node=d}return e.data.isActive&&this.renderer.addNodeToActiveList(d),this.mindMap.renderer.findActiveNodeIndex(d)!==-1&&d.setData({isActive:!0}),r?this.root=d:t._node.addChildren(d),d}checkGetGeneralizationChange(e,t){let r=e.getData("generalization");r&&e._generalizationList&&e._generalizationList.length>0&&e._generalizationList.forEach((n,s)=>{let a=n.generalizationNode,o=a.getData(),l=r[s];(t||l&&JSON.stringify(o)!==JSON.stringify(l))&&(l&&(a.nodeData.data=l),a.getSize(),a.needLayout=!0)})}formatPosition(e,t,r){return typeof e=="number"?e:Jc[e]!==void 0?t*Jc[e]:/^\d\d*%$/.test(e)?Number.parseFloat(e)/100*t:(t-r)/2}formatInitRootNodePosition(e){let{CENTER:t}=C.INIT_ROOT_NODE_POSITION;return(!e||!Array.isArray(e)||e.length<2)&&(e=[t,t]),e}setNodeCenter(e,t){let{initRootNodePosition:r}=this.mindMap.opt;r=this.formatInitRootNodePosition(t||r),e.left=this.formatPosition(r[0],this.mindMap.width,e.width),e.top=this.formatPosition(r[1],this.mindMap.height,e.height)}getRootCenterOffset(e,t){if(this.rootNodeCenterOffset)return this.rootNodeCenterOffset;let{initRootNodePosition:r}=this.mindMap.opt,{CENTER:n}=C.INIT_ROOT_NODE_POSITION;if(r=this.formatInitRootNodePosition(r),r[0]===n&&r[1]===n)this.rootNodeCenterOffset={x:0,y:0};else{let s={width:e,height:t},a={width:e,height:t};this.setNodeCenter(s,[n,n]),this.setNodeCenter(a),this.rootNodeCenterOffset={x:a.left-s.left,y:a.top-s.top}}return this.rootNodeCenterOffset}updateChildren(e,t,r){e.forEach(n=>{n[t]+=r,n.children&&n.children.length&&!n.hasCustomPosition()&&this.updateChildren(n.children,t,r)})}updateChildrenPro(e,t){e.forEach(r=>{Object.keys(t).forEach(n=>{r[n]+=t[n]}),r.children&&r.children.length&&!r.hasCustomPosition()&&this.updateChildrenPro(r.children,t)})}getNodeAreaWidth(e,t=!1){let r=[],n=0,s=(a,o)=>{t&&a.checkHasGeneralization()&&(n+=a._generalizationNodeWidth),a.children.length?(o+=a.width/2,a.children.forEach(l=>{s(l,o)})):(o+=a.width,r.push(o))};return s(e,0),Math.max(...r)+n}quadraticCurvePath(e,t,r,n,s=!1){let a,o;return s?(a=e+(r-e)*.8,o=t+(n-t)*.2):(a=e+(r-e)*.2,o=t+(n-t)*.8),`M ${e},${t} Q ${a},${o} ${r},${n}`}cubicBezierPath(e,t,r,n,s=!1){let a,o,l,h;return s?(a=e,o=t+(n-t)/2,l=r,h=o):(a=e+(r-e)/2,o=t,l=a,h=n),`M ${e},${t} C ${a},${o} ${l},${h} ${r},${n}`}computeNewPoint(e,t,r=0){if(e[0]===t[0])return t[1]>e[1]?[t[0],t[1]-r]:[t[0],t[1]+r];if(e[1]===t[1])return t[0]>e[0]?[t[0]-r,t[1]]:[t[0]+r,t[1]]}createFoldLine(e){let{lineRadius:t}=this.mindMap.themeConfig,r=e.length,n="",s="";if(r>=3&&t>0){let a=e[r-3],o=e[r-2],l=e[r-1];if(!(a[0].toFixed(0)===o[0].toFixed(0)&&o[0].toFixed(0)===l[0].toFixed(0)||a[1].toFixed(0)===o[1].toFixed(0)&&o[1].toFixed(0)===l[1].toFixed(0))){let d=this.computeNewPoint(a,o,t),c=this.computeNewPoint(l,o,t);s=`Q ${o[0]},${o[1]} ${c[0]},${c[1]}`,e.splice(r-2,1,d,s)}}return e.forEach((a,o)=>{if(typeof a=="string")n+=a;else{let[l,h]=a;o===0?n+=`M ${l},${h}`:n+=`L ${l},${h}`}}),n}getMarginX(e){let{themeConfig:t,opt:r}=this.mindMap,{second:n,node:s}=t,a=r.hoverRectPadding*2;return e===1?n.marginX+a:s.marginX+a}getMarginY(e){let{themeConfig:t,opt:r}=this.mindMap,{second:n,node:s}=t,a=r.hoverRectPadding*2;return e===1?n.marginY+a:s.marginY+a}getNodeWidthWithGeneralization(e){return Math.max(e.width,e.checkHasGeneralization()?e._generalizationNodeWidth:0)}getNodeHeightWithGeneralization(e){return Math.max(e.height,e.checkHasGeneralization()?e._generalizationNodeHeight:0)}getNodeBoundaries(e,t){let{generalizationLineMargin:r,generalizationNodeMargin:n}=this.mindMap.themeConfig,s=d=>{let c=1/0,f=-1/0,m=1/0,g=-1/0;d.children&&d.children.length>0&&d.children.forEach(y=>{let{left:b,right:E,top:k,bottom:L}=s(y),I=y.checkHasGeneralization()&&y.getData("expand")?y._generalizationNodeWidth+n:0,O=y.checkHasGeneralization()&&y.getData("expand")?y._generalizationNodeHeight+n:0;b-(t==="h"?I:0)f&&(f=E+(t==="h"?I:0)),kg&&(g=L+(t==="v"?O:0))});let x={left:d.left,right:d.left+d.width,top:d.top,bottom:d.top+d.height};return{left:x.leftf?x.right:f,top:x.topg?x.bottom:g}},{left:a,right:o,top:l,bottom:h}=s(e);return{left:a,right:o,top:l,bottom:h,generalizationLineMargin:r,generalizationNodeMargin:n}}getChildrenBoundaries(e,t,r=0,n){let{generalizationLineMargin:s,generalizationNodeMargin:a}=this.mindMap.themeConfig,o=e.children.slice(r,n+1),l=1/0,h=-1/0,d=1/0,c=-1/0;return o.forEach(f=>{let m=this.getNodeBoundaries(f,t);l=m.lefth?m.right:h,d=m.topc?m.bottom:c}),{left:l,right:h,top:d,bottom:c,generalizationLineMargin:s,generalizationNodeMargin:a}}getNodeGeneralizationRenderBoundaries(e,t){let r=null;return e.range?r=this.getChildrenBoundaries(e.node,t,e.range[0],e.range[1]):r=this.getNodeBoundaries(e.node,t),r}getNodeActChildrenLength(e){return e.nodeData.children&&e.nodeData.children.length}setLineStyle(e,t,r,n){t.plot(this.transformPath(r)),e&&e(t,n,!0)}transformPath(e){let{customTransformNodeLinePath:t}=this.mindMap.opt;return t?t(e):e}},Nt=Wu});var Vu,nh,Yu=T(()=>{Vu=class{constructor(e){this.mindMap=e,this.autoMoveTimer=null}onMove(e,t,r=()=>{},n=()=>{}){r();let s=this.mindMap.opt.selectTranslateStep,a=this.mindMap.opt.selectTranslateLimit,o=0;e<=this.mindMap.elRect.left+a&&(n("left",s),this.mindMap.view.translateX(s),o++),e>=this.mindMap.elRect.right-a&&(n("right",s),this.mindMap.view.translateX(-s),o++),t<=this.mindMap.elRect.top+a&&(n("top",s),this.mindMap.view.translateY(s),o++),t>=this.mindMap.elRect.bottom-a&&(n("bottom",s),this.mindMap.view.translateY(-s),o++),o>0&&this.startAutoMove(e,t,r,n)}startAutoMove(e,t,r,n){this.autoMoveTimer=setTimeout(()=>{this.onMove(e,t,r,n)},20)}clearAutoMoveTimer(){clearTimeout(this.autoMoveTimer)}},nh=Vu});var u6={};tt(u6,{default:()=>kE});var sh,kE,f6=T(()=>{pe();Ar();Ge();Yu();sh=class extends Nt{constructor({mindMap:e}){super(e.renderer),this.mindMap=e,this.autoMove=new nh(e),this.reset(),this.bindEvent()}reset(){this.isDragging=!1,this.mousedownNode=null,this.beingDragNodeList=[],this.nodeList=[],this.overlapNode=null,this.prevNode=null,this.nextNode=null,this.drawTransform=null,this.clone=null,this.placeholder=null,this.placeholderWidth=50,this.placeholderHeight=10,this.placeHolderLine=null,this.placeHolderExtraLines=[],this.offsetX=0,this.offsetY=0,this.isMousedown=!1,this.mouseDownX=0,this.mouseDownY=0,this.mouseMoveX=0,this.mouseMoveY=0,this.checkDragOffset=10,this.minOffset=10}bindEvent(){this.onNodeMousedown=this.onNodeMousedown.bind(this),this.onMousemove=this.onMousemove.bind(this),this.onMouseup=this.onMouseup.bind(this),this.checkOverlapNode=Ai(this.checkOverlapNode,300,this),this.mindMap.on("node_mousedown",this.onNodeMousedown),this.mindMap.on("mousemove",this.onMousemove),this.mindMap.on("node_mouseup",this.onMouseup),this.mindMap.on("mouseup",this.onMouseup)}unBindEvent(){this.mindMap.off("node_mousedown",this.onNodeMousedown),this.mindMap.off("mousemove",this.onMousemove),this.mindMap.off("node_mouseup",this.onMouseup),this.mindMap.off("mouseup",this.onMouseup)}onNodeMousedown(e,t){if(this.mindMap.opt.readonly||t.which!==1||e.isGeneralization||e.isRoot)return;this.isMousedown=!0,this.mousedownNode=e;let{x:r,y:n}=this.mindMap.toPos(t.clientX,t.clientY);this.mouseDownX=r,this.mouseDownY=n}onMousemove(e){if(this.mindMap.opt.readonly||!this.isMousedown)return;e.preventDefault();let{x:t,y:r}=this.mindMap.toPos(e.clientX,e.clientY);this.mouseMoveX=t,this.mouseMoveY=r,!(!this.isDragging&&Math.abs(t-this.mouseDownX)<=this.checkDragOffset&&Math.abs(r-this.mouseDownY)<=this.checkDragOffset)&&(this.mindMap.emit("node_dragging",this.mousedownNode),this.handleStartMove(),this.onMove(t,r,e))}async onMouseup(e){if(!this.isMousedown)return;let{autoMoveWhenMouseInEdgeOnDrag:t,enableFreeDrag:r,beforeDragEnd:n}=this.mindMap.opt;t&&this.mindMap.select&&this.autoMove.clearAutoMoveTimer(),this.isMousedown=!1,this.beingDragNodeList.forEach(l=>{l.setOpacity(1),l.showChildren(),l.endDrag()}),this.removeCloneNode();let s=this.overlapNode?this.overlapNode.getData("uid"):"",a=this.prevNode?this.prevNode.getData("uid"):"",o=this.nextNode?this.nextNode.getData("uid"):"";if(this.isDragging&&typeof n=="function"&&await n({overlapNodeUid:s,prevNodeUid:a,nextNodeUid:o,beingDragNodeList:[...this.beingDragNodeList]})){this.reset();return}if(this.overlapNode)this.removeNodeActive(this.overlapNode),this.mindMap.execCommand("MOVE_NODE_TO",this.beingDragNodeList,this.overlapNode);else if(this.prevNode)this.removeNodeActive(this.prevNode),this.mindMap.execCommand("INSERT_AFTER",this.beingDragNodeList,this.prevNode);else if(this.nextNode)this.removeNodeActive(this.nextNode),this.mindMap.execCommand("INSERT_BEFORE",this.beingDragNodeList,this.nextNode);else if(this.clone&&r&&this.beingDragNodeList.length===1){let{x:l,y:h}=this.mindMap.toPos(e.clientX-this.offsetX,e.clientY-this.offsetY),{scaleX:d,scaleY:c,translateX:f,translateY:m}=this.drawTransform;l=(l-f)/d,h=(h-m)/c,this.mousedownNode.left=l,this.mousedownNode.top=h,this.mousedownNode.customLeft=l,this.mousedownNode.customTop=h,this.mindMap.execCommand("SET_NODE_CUSTOM_POSITION",this.mousedownNode,l,h),this.mindMap.render()}this.isDragging&&this.mindMap.emit("node_dragend",{overlapNodeUid:s,prevNodeUid:a,nextNodeUid:o}),this.reset()}removeNodeActive(e){e.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",e,!1)}onMove(e,t,r){if(!this.isMousedown||!this.isDragging)return;let{scaleX:n,scaleY:s,translateX:a,translateY:o}=this.drawTransform,l=e-this.offsetX,h=t-this.offsetY;e=(l-a)/n,t=(h-o)/s;let d=this.clone.transform();this.clone.translate(e-d.translateX,t-d.translateY),this.checkOverlapNode(),this.drawTransform=this.mindMap.draw.transform(),this.autoMove.clearAutoMoveTimer(),this.autoMove.onMove(r.clientX,r.clientY)}async handleStartMove(){if(!this.isDragging){let e=this.mousedownNode;this.drawTransform=this.mindMap.draw.transform();let{scaleX:t,scaleY:r,translateX:n,translateY:s}=this.drawTransform;this.offsetX=this.mouseDownX-(e.left*t+n),this.offsetY=this.mouseDownY-(e.top*r+s),e.getData("isActive")?this.beingDragNodeList=Go(ds(this.mindMap.renderer.activeNodeList.filter(o=>!o.isRoot&&!o.isGeneralization))):this.beingDragNodeList=[e];let{beforeDragStart:a}=this.mindMap.opt;if(typeof a=="function"&&await a([...this.beingDragNodeList]))return;this.nodeTreeToList(),this.createCloneNode(),this.mindMap.execCommand("CLEAR_ACTIVE_NODE"),this.isDragging=!0}}nodeTreeToList(){let e=[];Zt(this.mindMap.renderer.root,t=>{this.checkIsInBeingDragNodeList(t)||(e[t.layerIndex]||(e[t.layerIndex]=[]),e[t.layerIndex].push(t))}),this.nodeList=e.reduceRight((t,r)=>[...t,...r],[])}createCloneNode(){if(!this.clone){let{dragMultiNodeRectConfig:e,dragPlaceholderRectFill:t,dragPlaceholderLineConfig:r,dragOpacityConfig:n,handleDragCloneNode:s}=this.mindMap.opt,{width:a,height:o,fill:l}=e,h=this.beingDragNodeList[0],d=h.style.merge("lineColor",!0);if(this.beingDragNodeList.length>1)this.clone=this.mindMap.otherDraw.rect().size(a,o).radius(o/2).fill({color:l||d}),this.offsetX=a/2,this.offsetY=o/2;else{this.clone=h.group.clone();let c=this.clone.findOne(".smm-expand-btn");c&&c.remove(),this.mindMap.otherDraw.add(this.clone),typeof s=="function"&&s(this.clone)}this.clone.opacity(n.cloneNodeOpacity),this.clone.css("z-index",99999),this.placeholder=this.mindMap.otherDraw.rect().fill({color:t||d}).radius(5),this.placeHolderLine=this.mindMap.otherDraw.path().stroke({color:r.color||d,width:r.width}).fill({color:"none"}),this.beingDragNodeList.forEach(c=>{c.setOpacity(n.beingDragNodeOpacity),c.hideChildren(),c.startDrag()})}}removeCloneNode(){this.clone&&(this.clone.remove(),this.placeholder.remove(),this.placeHolderLine.remove(),this.removeExtraLines())}removeExtraLines(){this.placeHolderExtraLines.forEach(e=>{e.remove()}),this.placeHolderExtraLines=[]}checkOverlapNode(){if(!this.drawTransform||!this.placeholder)return;let{LOGICAL_STRUCTURE:e,LOGICAL_STRUCTURE_LEFT:t,MIND_MAP:r,ORGANIZATION_STRUCTURE:n,CATALOG_ORGANIZATION:s,TIMELINE:a,TIMELINE2:o,VERTICAL_TIMELINE:l,VERTICAL_TIMELINE2:h,VERTICAL_TIMELINE3:d,FISHBONE:c,FISHBONE2:f,RIGHT_FISHBONE:m,RIGHT_FISHBONE2:g}=C.LAYOUT;this.overlapNode=null,this.prevNode=null,this.nextNode=null,this.placeholder.size(0,0),this.placeHolderLine.hide(),this.removeExtraLines(),this.nodeList.forEach(x=>{if(x.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",x,!1),!(this.overlapNode||this.prevNode&&this.nextNode))switch(this.mindMap.opt.layout){case e:case t:this.handleLogicalStructure(x);break;case r:this.handleMindMap(x);break;case n:this.handleOrganizationStructure(x);break;case s:this.handleCatalogOrganization(x);break;case a:this.handleTimeLine(x);break;case o:this.handleTimeLine2(x);break;case l:case h:case d:this.handleLogicalStructure(x);break;case c:case f:case m:case g:this.handleFishbone(x);break;default:this.handleLogicalStructure(x)}}),this.overlapNode&&this.handleOverlapNode()}handleOverlapNode(){let{LOGICAL_STRUCTURE:e,LOGICAL_STRUCTURE_LEFT:t,MIND_MAP:r,ORGANIZATION_STRUCTURE:n,CATALOG_ORGANIZATION:s,TIMELINE:a,TIMELINE2:o,VERTICAL_TIMELINE:l,VERTICAL_TIMELINE2:h,VERTICAL_TIMELINE3:d,FISHBONE:c,FISHBONE2:f,RIGHT_FISHBONE:m,RIGHT_FISHBONE2:g}=C.LAYOUT,{LEFT:x,TOP:y,RIGHT:b,BOTTOM:E}=C.LAYOUT_GROW_DIR,k=this.overlapNode.layerIndex,L=this.overlapNode.children,I=this.mindMap.renderer.layout.getMarginX(k+1),O=this.mindMap.renderer.layout.getMarginY(k+1),q=this.placeholderWidth/2,F=this.placeholderHeight/2,W="",ne="",re="",le=!1,Ce=!1;if(L.length>0){let te=L[L.length-1],Q=this.getNodeRect(te);switch(W=this.getNewChildNodeDir(te),this.mindMap.opt.layout){case e:case r:ne=W===x?Q.originRight-this.placeholderWidth:Q.originLeft,re=Q.originBottom+this.minOffset-F;break;case t:ne=Q.originRight-this.placeholderWidth,re=Q.originBottom+this.minOffset-F;break;case n:le=!0,ne=Q.originRight+this.minOffset-F,re=Q.originTop;break;case s:k===0?(le=!0,ne=Q.originRight+this.minOffset-F,re=Q.originTop):(ne=Q.originLeft,re=Q.originBottom+this.minOffset-F);break;case a:k===0?(le=!0,ne=Q.originRight+this.minOffset-F,re=Q.originTop+Q.originHeight/2-q):(ne=Q.originLeft,re=Q.originBottom+this.minOffset-F);break;case o:k===0?(le=!0,ne=Q.originRight+this.minOffset-F,re=Q.originTop+Q.originHeight/2-q):(ne=Q.originLeft,k===1?re=W===y?Q.originTop-this.placeholderHeight-this.minOffset+F:Q.originBottom+this.minOffset-F:re=Q.originBottom+this.minOffset-F);break;case l:case h:case d:k===0?(ne=Q.originLeft+Q.originWidth/2-q,re=Q.originBottom+this.minOffset-F):(ne=W===b?Q.originLeft:Q.originRight-this.placeholderWidth,re=Q.originBottom+this.minOffset-F);break;case c:case f:case m:case g:k<=1?(Ce=!0,this.mindMap.execCommand("SET_NODE_ACTIVE",this.overlapNode,!0)):(ne=Q.originLeft,re=W===y?Q.originBottom+this.minOffset-F:Q.originTop-this.placeholderHeight-this.minOffset+F);break;default:}}else{let te=this.getNodeRect(this.overlapNode);switch(W=this.getNewChildNodeDir(this.overlapNode),this.mindMap.opt.layout){case e:case r:ne=W===b?te.originRight+I:te.originLeft-this.placeholderWidth-I,re=te.originTop+(te.originHeight-this.placeholderHeight)/2;break;case t:ne=te.originLeft-this.placeholderWidth-I,re=te.originTop+(te.originHeight-this.placeholderHeight)/2;break;case n:le=!0,ne=te.originLeft+(te.originWidth-this.placeholderHeight)/2,re=te.originBottom+I;break;case s:k===0&&(le=!0),ne=te.originLeft+te.originWidth*.5,re=te.originBottom+I;break;case a:k===0&&(le=!0),ne=te.originLeft+te.originWidth*.5,re=te.originBottom+O;break;case o:k===0&&(le=!0),ne=te.originLeft+te.originWidth*.5,k===1?re=W===y?te.originTop-this.placeholderHeight-I:te.originBottom+I:re=te.originBottom+I;break;case l:case h:case d:k===0&&(le=!0),ne=W===b?te.originRight+I:te.originLeft-this.placeholderWidth-I,re=te.originTop+te.originHeight/2-F;break;case c:case f:case m:case g:k<=1?(Ce=!0,this.mindMap.execCommand("SET_NODE_ACTIVE",this.overlapNode,!0)):(ne=te.originLeft+te.originWidth*.5,re=W===E?te.originTop-this.placeholderHeight-this.minOffset+F:te.originBottom+this.minOffset-F);break;default:}}Ce||this.setPlaceholderRect({x:ne,y:re,dir:W,rotate:le})}getNewChildNodeDir(e){let{LOGICAL_STRUCTURE:t,LOGICAL_STRUCTURE_LEFT:r,MIND_MAP:n,TIMELINE2:s,VERTICAL_TIMELINE:a,VERTICAL_TIMELINE2:o,VERTICAL_TIMELINE3:l,FISHBONE:h,FISHBONE2:d,RIGHT_FISHBONE:c,RIGHT_FISHBONE2:f}=C.LAYOUT;switch(this.mindMap.opt.layout){case t:return C.LAYOUT_GROW_DIR.RIGHT;case r:return C.LAYOUT_GROW_DIR.LEFT;case n:case s:case a:case o:case l:case h:case d:case c:case f:return e.dir;default:return""}}handleVerticalCheck(e,t,r=!1){let{layout:n}=this.mindMap.opt,{LAYOUT:s,LAYOUT_GROW_DIR:a}=C,{VERTICAL_TIMELINE:o,VERTICAL_TIMELINE2:l,VERTICAL_TIMELINE3:h,FISHBONE:d,FISHBONE2:c,RIGHT_FISHBONE:f,RIGHT_FISHBONE2:m}=s,{LEFT:g}=a,x=this.mouseMoveX,y=this.mouseMoveY,b=this.getNodeRect(e),E=this.getNewChildNodeDir(e),k=e.layerIndex;r&&(t=t.reverse());let L=b.originHeight/4,{prevBrotherOffset:I,nextBrotherOffset:O}=this.getNodeDistanceToSiblingNode(t,e,b,"v");if(b.left<=x&&b.right>=x){if(!this.overlapNode&&!this.prevNode&&!this.nextNode&&!e.isRoot){let q=O>0?y>b.bottom&&y<=b.bottom+O:y>=b.bottom-L&&y<=b.bottom,F=I>0?y=b.top-I:y>=b.top&&y<=b.top+L,{scaleY:W}=this.drawTransform,ne=E===g?b.originRight-this.placeholderWidth:b.originLeft,re=!1;switch(n){case o:case l:case h:k===1&&(ne=b.originLeft+b.originWidth/2-this.placeholderWidth/2);break;case f:case m:ne=b.originLeft+b.originWidth-this.placeholderWidth;break;default:}if(q){r?this.nextNode=e:this.prevNode=e;let le=b.originBottom+O/W-this.placeholderHeight/2;switch(n){case d:case c:case f:case m:k===2&&(re=!0,le=b.originBottom+this.minOffset-this.placeholderHeight/2);break;default:}this.setPlaceholderRect({x:ne,y:le,dir:E,notRenderLine:re})}else if(F){r?this.prevNode=e:this.nextNode=e;let le=b.originTop-this.placeholderHeight-I/W+this.placeholderHeight/2;switch(n){case d:case c:case f:case m:k===2&&(re=!0,le=b.originTop-this.placeholderHeight-this.minOffset+this.placeholderHeight/2);break;default:}this.setPlaceholderRect({x:ne,y:le,dir:E,notRenderLine:re})}}this.checkIsOverlap({node:e,dir:"v",prevBrotherOffset:I,nextBrotherOffset:O,size:L,pos:y,nodeRect:b})}}handleHorizontalCheck(e,t){let{layout:r}=this.mindMap.opt,{LAYOUT:n}=C,{FISHBONE:s,FISHBONE2:a,RIGHT_FISHBONE:o,RIGHT_FISHBONE2:l,TIMELINE:h,TIMELINE2:d}=n,c=this.mouseMoveX,f=this.mouseMoveY,m=this.getNodeRect(e),g=m.originWidth/4,{prevBrotherOffset:x,nextBrotherOffset:y}=this.getNodeDistanceToSiblingNode(t,e,m,"h");if(m.top<=f&&m.bottom>=f){if(!this.overlapNode&&!this.prevNode&&!this.nextNode&&!e.isRoot){let b=y>0?c=m.right:c<=m.right&&c>=m.right-g,E=x>0?c>m.left-x&&c<=m.left:c<=m.left+g&&c>=m.left,{scaleX:k}=this.drawTransform,L=e.layerIndex,I=m.originTop,O=!1;switch(r){case h:case d:I=m.originTop+m.originHeight/2-this.placeholderWidth/2;break;case s:case a:case o:case l:L===1&&(O=!0,I=m.originTop+m.originHeight/2-this.placeholderWidth/2);break;default:}b?([o,l].includes(r)?this.nextNode=e:this.prevNode=e,this.setPlaceholderRect({x:m.originRight+y/k-this.placeholderHeight/2,y:I,rotate:!0,notRenderLine:O})):E&&([o,l].includes(r)?this.prevNode=e:this.nextNode=e,this.setPlaceholderRect({x:m.originLeft-this.placeholderHeight-x/k+this.placeholderHeight/2,y:I,rotate:!0,notRenderLine:O}))}this.checkIsOverlap({node:e,dir:"h",prevBrotherOffset:x,nextBrotherOffset:y,size:g,pos:c,nodeRect:m})}}getNodeDistanceToSiblingNode(e,t,r,n){let{TOP:s,LEFT:a,BOTTOM:o,RIGHT:l}=C.LAYOUT_GROW_DIR,{scaleX:h,scaleY:d}=this.drawTransform,c=n==="v"?s:a,f=n==="v"?o:l,m=n==="v"?d:h,g=this.minOffset*m,x=ze(t,e),y=null,b=null;x!==-1&&(x-1>=0&&(y=e[x-1]),x+1<=e.length-1&&(b=e[x+1]));let E=0;if(y){let L=this.getNodeRect(y);E=r[c]-L[f],E=E>=g?E/2:0}else E=g;let k=0;return b?(k=this.getNodeRect(b)[c]-r[f],k=k>=g?k/2:0):k=g,{prevBrother:y,prevBrotherOffset:E,nextBrother:b,nextBrotherOffset:k}}setPlaceholderRect({x:e,y:t,dir:r,rotate:n,notRenderLine:s}){let a=this.placeholderWidth,o=this.placeholderHeight;if(n){let f=a;a=o,o=f}if(this.placeholder.size(a,o).move(e,t),s)return;let{dragPlaceholderLineConfig:l}=this.mindMap.opt,h=null,d=null;this.overlapNode?(h=this.overlapNode,d=this.overlapNode):(h=this.prevNode||this.nextNode,d=h.parent),d=d.fakeClone(),h=h.fakeClone();let c=this.beingDragNodeList[0].fakeClone();c.dir=r,c.left=e,c.top=t,c.width=a,c.height=o,d.children=[c],d._lines=[],this.placeHolderLine.show(),this.mindMap.renderer.layout.renderLine(d,[this.placeHolderLine],(...f)=>{},h.style.getStyle("lineStyle",!0)),this.placeHolderExtraLines=[...d._lines],this.placeHolderExtraLines.forEach(f=>{this.mindMap.otherDraw.add(f),f.stroke({color:l.color,width:l.width}).fill({color:"none"})})}checkIsOverlap({node:e,dir:t,prevBrotherOffset:r,nextBrotherOffset:n,size:s,pos:a,nodeRect:o}){let{TOP:l,LEFT:h,BOTTOM:d,RIGHT:c}=C.LAYOUT_GROW_DIR,f=t==="v"?l:h,m=t==="v"?d:c;!this.overlapNode&&!this.prevNode&&!this.nextNode&&o[f]+(r>0?0:s)<=a&&o[m]-(n>0?0:s)>=a&&(this.overlapNode=e)}handleLogicalStructure(e){let t=this.commonGetNodeCheckList(e);this.handleVerticalCheck(e,t)}handleMindMap(e){let t=e.parent?e.parent.children.filter(r=>{let n=!0;return e.layerIndex===1&&(n=r.dir===e.dir),n&&!this.checkIsInBeingDragNodeList(r)}):[];this.handleVerticalCheck(e,t)}handleOrganizationStructure(e){let t=this.commonGetNodeCheckList(e);this.handleHorizontalCheck(e,t)}handleCatalogOrganization(e){let t=this.commonGetNodeCheckList(e);e.layerIndex===1?this.handleHorizontalCheck(e,t):this.handleVerticalCheck(e,t)}handleTimeLine(e){let t=this.commonGetNodeCheckList(e);e.layerIndex===1?this.handleHorizontalCheck(e,t):this.handleVerticalCheck(e,t)}handleTimeLine2(e){let t=this.commonGetNodeCheckList(e);e.layerIndex===1?this.handleHorizontalCheck(e,t):e.dir===C.LAYOUT_GROW_DIR.TOP&&e.layerIndex===2?this.handleVerticalCheck(e,t,!0):this.handleVerticalCheck(e,t)}handleFishbone(e){let t=e.parent?e.parent.children.filter(r=>r.layerIndex>1&&!this.checkIsInBeingDragNodeList(r)):[];if(e.layerIndex===1)this.handleHorizontalCheck(e,t);else{let r=e.dir===C.LAYOUT_GROW_DIR.TOP&&e.layerIndex===2,n=e.dir===C.LAYOUT_GROW_DIR.BOTTOM&&e.layerIndex>=3;r||n?this.handleVerticalCheck(e,t,!0):this.handleVerticalCheck(e,t)}}commonGetNodeCheckList(e){return e.parent?[...e.parent.children].filter(t=>!this.checkIsInBeingDragNodeList(t)):[]}getNodeRect(e){let{scaleX:t,scaleY:r,translateX:n,translateY:s}=this.drawTransform,{left:a,top:o,width:l,height:h}=e,d=l,c=h,f=a,m=o,g=o+h,x=a+l,y=(a+l)*t+n,b=(o+h)*r+s;return a=a*t+n,o=o*r+s,{left:a,top:o,right:y,bottom:b,originWidth:d,originHeight:c,originLeft:f,originTop:m,originBottom:g,originRight:x}}checkIsInBeingDragNodeList(e){return!!this.beingDragNodeList.find(t=>t.uid===e.uid||t.isAncestor(e))}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};sh.instanceName="drag";kE=sh});var m6={};tt(m6,{default:()=>CE});var ah,CE,p6=T(()=>{pe();Ge();ah=class{constructor(e){this.opt=e,this.mindMap=e.mindMap,this.addShortcut()}addShortcut(){this.onLeftKeyUp=this.onLeftKeyUp.bind(this),this.onUpKeyUp=this.onUpKeyUp.bind(this),this.onRightKeyUp=this.onRightKeyUp.bind(this),this.onDownKeyUp=this.onDownKeyUp.bind(this),this.mindMap.keyCommand.addShortcut(C.KEY_DIR.LEFT,this.onLeftKeyUp),this.mindMap.keyCommand.addShortcut(C.KEY_DIR.UP,this.onUpKeyUp),this.mindMap.keyCommand.addShortcut(C.KEY_DIR.RIGHT,this.onRightKeyUp),this.mindMap.keyCommand.addShortcut(C.KEY_DIR.DOWN,this.onDownKeyUp)}removeShortcut(){this.mindMap.keyCommand.removeShortcut(C.KEY_DIR.LEFT,this.onLeftKeyUp),this.mindMap.keyCommand.removeShortcut(C.KEY_DIR.UP,this.onUpKeyUp),this.mindMap.keyCommand.removeShortcut(C.KEY_DIR.RIGHT,this.onRightKeyUp),this.mindMap.keyCommand.removeShortcut(C.KEY_DIR.DOWN,this.onDownKeyUp)}onLeftKeyUp(){this.onKeyup(C.KEY_DIR.LEFT)}onUpKeyUp(){this.onKeyup(C.KEY_DIR.UP)}onRightKeyUp(){this.onKeyup(C.KEY_DIR.RIGHT)}onDownKeyUp(){this.onKeyup(C.KEY_DIR.DOWN)}onKeyup(e){if(this.mindMap.renderer.activeNodeList.length>0)this.focus(e);else{let t=this.mindMap.renderer.root;this.mindMap.execCommand("GO_TARGET_NODE",t)}}focus(e){let t=this.mindMap.renderer.activeNodeList[0],r=this.getNodeRect(t),n=null,s=1/0,a=(o,l)=>{let h=this.getDistance(r,o);h{if(s.uid===e.uid)return;let a=this.getNodeRect(s),{left:o,top:l,right:h,bottom:d}=a,c=!1;r===C.KEY_DIR.LEFT?c=h<=t.left:r===C.KEY_DIR.RIGHT?c=o>=t.right:r===C.KEY_DIR.UP?c=d<=t.top:r===C.KEY_DIR.DOWN&&(c=l>=t.bottom),c&&n(a,s)})}getFocusNodeByShadowAlgorithm({currentActiveNode:e,currentActiveNodeRect:t,dir:r,checkNodeDis:n}){Zt(this.mindMap.renderer.root,s=>{if(s.uid===e.uid)return;let a=this.getNodeRect(s),{left:o,top:l,right:h,bottom:d}=a,c=!1;r===C.KEY_DIR.LEFT?c=ot.top:r===C.KEY_DIR.RIGHT?c=h>t.right&&lt.top:r===C.KEY_DIR.UP?c=lt.left:r===C.KEY_DIR.DOWN&&(c=d>t.bottom&&ot.left),c&&n(a,s)})}getFocusNodeByAreaAlgorithm({currentActiveNode:e,currentActiveNodeRect:t,dir:r,checkNodeDis:n}){let s=(t.right+t.left)/2,a=(t.bottom+t.top)/2;Zt(this.mindMap.renderer.root,o=>{if(o.uid===e.uid)return;let l=this.getNodeRect(o),{left:h,top:d,right:c,bottom:f}=l,m=(c+h)/2,g=(f+d)/2,x=m-s,y=g-a;if(x===0&&y===0)return;let b=!1;r===C.KEY_DIR.LEFT?b=x<=0&&x<=y&&x<=-y:r===C.KEY_DIR.RIGHT?b=x>0&&x>=-y&&x>=y:r===C.KEY_DIR.UP?b=y<=0&&y0&&-yx),b&&n(l,o)})}getNodeRect(e){let{scaleX:t,scaleY:r,translateX:n,translateY:s}=this.mindMap.draw.transform(),{left:a,top:o,width:l,height:h}=e;return{right:(a+l)*t+n,bottom:(o+h)*r+s,left:a*t+n,top:o*r+s}}getDistance(e,t){let r=this.getCenter(e),n=this.getCenter(t);return Math.sqrt(Math.pow(r.x-n.x,2)+Math.pow(r.y-n.y,2))}getCenter({left:e,right:t,top:r,bottom:n}){return{x:(e+t)/2,y:(r+n)/2}}beforePluginRemove(){this.removeShortcut()}beforePluginDestroy(){this.removeShortcut()}};ah.instanceName="keyboardNavigation";CE=ah});var Ku,oh,Xu,g6,_E,LE,IE,lh,zE,x6,y6=T(()=>{Ku=i=>String(i).split(/\s+/).map(t=>{if(/^[\d.]+/.test(t)){let r=/^([\d.]+)(.*)$/.exec(t);return[Number(r[1]),r[2]]}else return t}),oh=(i,e)=>i*e,Xu=(i,e)=>e/i,g6={left:0,top:0,center:50,bottom:100,right:100},_E=({backgroundSize:i,drawOpt:e,imageRatio:t,canvasWidth:r,canvasHeight:n,canvasRatio:s})=>{if(i){let a=Ku(i);if(a[0]==="auto"&&a[1]==="auto")return;if(a[0]==="cover"){t>s?(e.height=n,e.width=oh(t,n)):(e.width=r,e.height=Xu(t,r));return}if(a[0]==="contain"){t>s?(e.width=r,e.height=Xu(t,r)):(e.height=n,e.width=oh(t,n));return}let o=-1;a[0]&&(Array.isArray(a[0])?a[0][1]==="%"?(e.width=a[0][0]/100*r,o=e.width):(e.width=a[0][0],o=a[0][0]):a[0]==="auto"&&a[1]&&(a[1][1]==="%"?e.width=oh(t,a[1][0]/100*n):e.width=oh(t,a[1][0]))),a[1]&&Array.isArray(a[1])?a[1][1]==="%"?e.height=a[1][0]/100*n:e.height=a[1][0]:o!==-1&&(e.height=Xu(t,o))}},LE=({backgroundPosition:i,drawOpt:e,imgWidth:t,imgHeight:r,canvasWidth:n,canvasHeight:s})=>{if(i){let a=Ku(i);if(a=a.map(o=>typeof o=="string"&&g6[o]!==void 0?[g6[o],"%"]:o),Array.isArray(a[0])){if(a.length===1&&a.push([50,"%"]),a[0][1]==="%"){let o=a[0][0]/100*n,l=a[0][0]/100*t;e.x=o-l}else e.x=a[0][0];if(a[1][1]==="%"){let o=a[1][0]/100*s,l=a[1][0]/100*r;e.y=o-l}else e.y=a[1][0]}}},IE=({ctx:i,image:e,backgroundRepeat:t,drawOpt:r,imgWidth:n,imgHeight:s,canvasWidth:a,canvasHeight:o})=>{if(t){let l=r.x,h=r.y,d=Math.ceil(l/n),c=Math.ceil(h/s),f=l-d*n,m=h-c*s,g=Ku(t);if(g[0]==="no-repeat"||n>=a&&s>=o)return;if(g[0]==="repeat-x"&&a>n){let x=f;for(;xs){let x=m;for(;xs){let y=m;for(;y{i.drawImage(e,t.sx,t.sy,t.swidth,t.sheight,t.x,t.y,t.width,t.height)},zE=(i,e,t,r,{backgroundSize:n,backgroundPosition:s,backgroundRepeat:a},o=()=>{})=>{let l=e/t,h=new Image;h.src=r,h.onload=()=>{let d=h.width,c=h.height,f=d/c,m={sx:0,sy:0,swidth:d,sheight:c,x:0,y:0,width:d,height:c};_E({backgroundSize:n,drawOpt:m,imageRatio:f,canvasWidth:e,canvasHeight:t,canvasRatio:l}),LE({backgroundPosition:s,drawOpt:m,imgWidth:m.width,imgHeight:m.height,imageRatio:f,canvasWidth:e,canvasHeight:t,canvasRatio:l}),IE({ctx:i,image:h,backgroundRepeat:a,drawOpt:m,imgWidth:m.width,imgHeight:m.height,imageRatio:f,canvasWidth:e,canvasHeight:t,canvasRatio:l})||lh(i,h,m),o()},h.onerror=d=>{o(d)}},x6=zE});var Zu,RE,DE,v6,b6=T(()=>{pe();Zu=i=>i.richText?Q0(i.text):i.text,RE=i=>new Array(i).fill("#").join(""),DE=i=>new Array(i-6).fill(" ").join("")+"*",v6=i=>{let e="";return ue(i,null,(t,r,n,s)=>{let a=s+1;a<=6?e+=RE(a):e+=DE(a),e+=" "+Zu(t.data);let o=t.data.generalization;if(Array.isArray(o))e+=o.map(l=>` [${Zu(l)}]`);else if(o&&o.text){let l=Zu(o);e+=` [${l}]`}e+=` `,t.data.note&&(e+=t.data.note+` -`)},()=>{},!0),e}});var Hu,uE,o6,l6=T(()=>{pe();Hu=i=>i.richText?j0(i.text):i.text,uE=i=>new Array(i).fill(" ").join(""),o6=i=>{let e="";return de(i,null,(t,r,n,s)=>{e+=uE(s),e+=" "+Hu(t.data);let a=t.data.generalization;Array.isArray(a)?e+=a.map(o=>` [${Hu(o)}]`):a&&a.text&&(e+=` [${Hu(a)}]`),e+=` +`)},()=>{},!0),e}});var Qu,OE,w6,M6=T(()=>{pe();Qu=i=>i.richText?Q0(i.text):i.text,OE=i=>new Array(i).fill(" ").join(""),w6=i=>{let e="";return ue(i,null,(t,r,n,s)=>{e+=OE(s),e+=" "+Qu(t.data);let a=t.data.generalization;Array.isArray(a)?e+=a.map(o=>` [${Qu(o)}]`):a&&a.text&&(e+=` [${Qu(a)}]`),e+=` -`},()=>{},!0),e}});var h6={};tt(h6,{default:()=>fE});var th,fE,d6=T(()=>{pe();yt();n6();a6();$e();l6();th=class{constructor(e){this.mindMap=e.mindMap}async export(e,t=!0,r="\u601D\u7EF4\u5BFC\u56FE",...n){if(this[e]){let s=await this[e](r,...n);return t&&m2(s,r+"."+e),s}else return null}createTransformImgTaskList(e,t,r,n){return e.find(t).map(async a=>{let o=n(a);if(/^data:/.test(o)||o==="none")return;let l=await fu(o);a.attr(r,l)})}async getSvgData(e){let{exportPaddingX:t,exportPaddingY:r,errorHandler:n,resetCss:s,addContentToHeader:a,addContentToFooter:o,handleBeingExportSvg:l}=this.mindMap.opt,{svg:h,svgHTML:d,clipData:c}=this.mindMap.getSvgData({paddingX:t,paddingY:r,addContentToHeader:a,addContentToFooter:o,node:e});c&&(c.paddingX=t,c.paddingY=r);let f=!1,m=this.createTransformImgTaskList(h,"image","href",y=>y.attr("href")||y.attr("xlink:href")),g=this.createTransformImgTaskList(h,"img","src",y=>y.attr("src")),x=[...m,...g];try{await Promise.all(x)}catch(y){n(Mi.EXPORT_LOAD_IMAGE_ERROR,y)}if(this.mindMap.richText){let y=h.find("foreignObject");if(y.length>0&&(y[0].add(Ae(``)),f=!0),this.mindMap.formula&&h.find(".ql-formula").length>0){let S=this.mindMap.formula.getStyleText();if(S){let A=document.createElement("style");A.innerHTML=S,or(A),y[0].add(A),f=!0}}}return typeof l=="function"&&(f=!0,h=l(h)),(x.length>0||f)&&(d=h.svg()),{node:h,str:d,clipData:c}}svgToPng(e,t,r=null,n=!1,s="image/png"){let{maxCanvasSize:a,minExportImgCanvasScale:o}=this.mindMap.opt;return new Promise((l,h)=>{let d=new Image;d.setAttribute("crossOrigin","anonymous"),d.onload=async()=>{try{let c=document.createElement("canvas"),f=Math.max(window.devicePixelRatio,o),m=d.width,g=d.height,x=0,y=0;r&&(x=r.paddingX,y=r.paddingY,m=r.width+x*2,g=r.height+y*2);let b=0,S=0,{backgroundImage:A}=this.mindMap.themeConfig;if(n&&A&&!t){let ke=await new Promise(Q=>{let Y=new Image;Y.onload=()=>{Q([Y.width,Y.height])},Y.onerror=()=>{Q(null)},Y.src=A});if(ke){let Q=m/g,Y=ke[0]/ke[1];Q>Y?(b=m,S=m/Y):(S=g,b=g*Y)}}let C=1,I=1,O=(b||m)*f,q=(S||g)*f;if(O>a||q>a){let ke=null,Q=null;O>a?ke=a:q>a&&(Q=a);let Y=U0(O,q,ke,Q);C=Y[0]/O,I=Y[1]/q,O=Y[0],q=Y[1]}c.width=O,c.height=q;let P=O/f,W=q/f;c.style.width=P+"px",c.style.height=W+"px";let ne=c.getContext("2d");ne.scale(f,f),t||await this.drawBackgroundToCanvas(ne,P,W);let re=(b>0?(b-m)/2:0)*C,oe=(S>0?(S-g)/2:0)*I;r?ne.drawImage(d,r.left,r.top,r.width,r.height,x*C+re,y*I+oe,r.width*C,r.height*I):ne.drawImage(d,re,oe,m*C,g*I),l(c.toDataURL(s))}catch(c){h(c)}},d.onerror=c=>{h(c)},d.src=e})}drawBackgroundToCanvas(e,t,r){return new Promise((n,s)=>{let{backgroundColor:a="#fff",backgroundImage:o,backgroundRepeat:l="no-repeat",backgroundPosition:h="center center",backgroundSize:d="cover"}=this.mindMap.themeConfig;e.save(),e.rect(0,0,t,r),e.fillStyle=a,e.fill(),e.restore(),o&&o!=="none"?(e.save(),r6(e,t,r,o,{backgroundRepeat:l,backgroundPosition:h,backgroundSize:d},c=>{c?s(c):n(),e.restore()})):n()})}drawBackgroundToSvg(e){return new Promise(async t=>{let{backgroundColor:r="#fff",backgroundImage:n,backgroundRepeat:s="repeat"}=this.mindMap.themeConfig;if(e.css("background-color",r),n&&n!=="none"){let a=await fu(n);e.css("background-image",`url(${a})`),e.css("background-repeat",s),t()}else t()})}async _image(e,t,r=!1,n=null,s=!1){this.mindMap.renderer.textEdit.hideEditTextBox(),this.handleNodeExport(n);let{str:a,clipData:o}=await this.getSvgData(n),l=await this.fixSvgStrAndToBlob(a);return await this.svgToPng(l,r,o,s,e)}async png(...e){return await this._image("image/png",...e)}async jpg(...e){return await this._image("image/jpg",...e)}handleNodeExport(e){if(e&&e.getData("isActive")){e.deactivate();let{alwaysShowExpandBtn:t,notShowExpandBtn:r}=this.mindMap.opt;!t&&!r&&e.getData("expand")&&e.removeExpandBtn()}}async pdf(e,t=!1,r=!1){if(!this.mindMap.doExportPDF)throw new Error("\u8BF7\u6CE8\u518CExportPDF\u63D2\u4EF6");let n=await this.png(e,t,null,r);return await this.mindMap.doExportPDF.pdf(n)}async xmind(e){if(!this.mindMap.doExportXMind)throw new Error("\u8BF7\u6CE8\u518CExportXMind\u63D2\u4EF6");let t=this.mindMap.getData(),r=await this.mindMap.doExportXMind.xmind(t,e);return await aa(r)}async svg(e){this.mindMap.renderer.textEdit.hideEditTextBox();let{node:t}=await this.getSvgData();t.first().before(Ae(`${e}`)),await this.drawBackgroundToSvg(t);let r=t.svg();return await this.fixSvgStrAndToBlob(r)}async fixSvgStrAndToBlob(e){e=b2(e),e=_2(e);let t=new Blob([e],{type:"image/svg+xml"});return await aa(t)}async json(e,t=!0){let r=this.mindMap.getData(t),n=JSON.stringify(r),s=new Blob([n]);return await aa(s)}async smm(e,t){return await this.json(e,t)}async md(){let e=this.mindMap.getData(),t=s6(e),r=new Blob([t]);return await aa(r)}async txt(){let e=this.mindMap.getData(),t=o6(e),r=new Blob([t]);return await aa(r)}};th.instanceName="doExport";fE=th});var c6={};tt(c6,{default:()=>mE});var ih,mE,u6=T(()=>{pe();Bu();ih=class{constructor({mindMap:e}){this.mindMap=e,this.rect=null,this.isMousedown=!1,this.mouseDownX=0,this.mouseDownY=0,this.mouseMoveX=0,this.mouseMoveY=0,this.isSelecting=!1,this.cacheActiveList=[],this.autoMove=new K0(e),this.bindEvent()}bindEvent(){this.onMousedown=this.onMousedown.bind(this),this.onMousemove=this.onMousemove.bind(this),this.onMouseup=this.onMouseup.bind(this),this.checkInNodes=Si(this.checkInNodes,300,this),this.mindMap.on("mousedown",this.onMousedown),this.mindMap.on("mousemove",this.onMousemove),this.mindMap.on("mouseup",this.onMouseup),this.mindMap.on("node_mouseup",this.onMouseup)}unBindEvent(){this.mindMap.off("mousedown",this.onMousedown),this.mindMap.off("mousemove",this.onMousemove),this.mindMap.off("mouseup",this.onMouseup),this.mindMap.off("node_mouseup",this.onMouseup)}onMousedown(e){let{readonly:t,mousedownEventPreventDefault:r}=this.mindMap.opt;if(t)return;let{useLeftKeySelectionRightKeyDrag:n}=this.mindMap.opt;if(!(e.ctrlKey||e.metaKey)&&(n?e.which!==1:e.which!==3))return;r&&e.preventDefault(),this.isMousedown=!0,this.cacheActiveList=[...this.mindMap.renderer.activeNodeList];let{x:s,y:a}=this.mindMap.toPos(e.clientX,e.clientY);this.mouseDownX=s,this.mouseDownY=a,this.createRect(s,a)}onMousemove(e){if(this.mindMap.opt.readonly||!this.isMousedown)return;let{x:t,y:r}=this.mindMap.toPos(e.clientX,e.clientY);this.mouseMoveX=t,this.mouseMoveY=r,!(Math.abs(t-this.mouseDownX)<=10&&Math.abs(r-this.mouseDownY)<=10)&&(this.autoMove.clearAutoMoveTimer(),this.autoMove.onMove(e.clientX,e.clientY,()=>{this.isSelecting=!0,this.rect&&this.rect.plot([[this.mouseDownX,this.mouseDownY],[this.mouseMoveX,this.mouseDownY],[this.mouseMoveX,this.mouseMoveY],[this.mouseDownX,this.mouseMoveY]]),this.checkInNodes()},(n,s)=>{switch(n){case"left":this.mouseDownX+=s;break;case"top":this.mouseDownY+=s;break;case"right":this.mouseDownX-=s;break;case"bottom":this.mouseDownY-=s;break;default:break}}))}onMouseup(){this.mindMap.opt.readonly||this.isMousedown&&(this.checkTriggerNodeActiveEvent(),this.autoMove.clearAutoMoveTimer(),this.isMousedown=!1,this.cacheActiveList=[],this.rect&&this.rect.remove(),this.rect=null,setTimeout(()=>{this.isSelecting=!1},0))}checkTriggerNodeActiveEvent(){let e=this.cacheActiveList.length!==this.mindMap.renderer.activeNodeList.length,t=!1;if(!e)for(let r=0;rs.getData("uid")===n.getData("uid"))){t=!0;break}}(e||t)&&this.mindMap.renderer.emitNodeActiveEvent()}createRect(e,t){this.rect&&this.rect.remove(),this.rect=this.mindMap.svg.polygon().stroke({color:"#0984e3"}).fill({color:"rgba(9,132,227,0.3)"}).plot([[e,t]])}checkInNodes(){let{scaleX:e,scaleY:t,translateX:r,translateY:n}=this.mindMap.draw.transform(),s=Math.min(this.mouseDownX,this.mouseMoveX),a=Math.min(this.mouseDownY,this.mouseMoveY),o=Math.max(this.mouseDownX,this.mouseMoveX),l=Math.max(this.mouseDownY,this.mouseMoveY),h=d=>{let{left:c,top:f,width:m,height:g}=d,x=(c+m)*e+r,y=(f+g)*t+n;if(c=c*e+r,f=f*t+n,k2(s,o,a,l,c,x,f,y)){if(d.getData("isActive"))return;this.mindMap.renderer.addNodeToActiveList(d),this.mindMap.renderer.emitNodeActiveEvent()}else if(d.getData("isActive")){if(!d.getData("isActive"))return;this.mindMap.renderer.removeNodeFromActiveList(d),this.mindMap.renderer.emitNodeActiveEvent()}};Xt(this.mindMap.renderer.root,d=>{h(d),d._generalizationList&&d._generalizationList.length>0&&d._generalizationList.forEach(c=>{h(c.generalizationNode)})})}hasSelectRange(){return this.isSelecting}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};ih.instanceName="select";mE=ih});var Go,Vo,rh,f6,m6,pE,kr,Uu,p6,$u,ju=T(()=>{pe();Go=(i,e)=>i.getData("associativeLineTargets").findIndex(t=>t===e.getData("uid")),Vo=(i,e,t,r)=>{let s=i+(t-i)/2,a=e,o=s,l=r;return Math.abs(i-t)<=5&&(s=i+(r-e)/2,o=s),Math.abs(e-r)<=5&&(s=i,a=e-(t-i)/2,o=t,l=a),[{x:s,y:a},{x:o,y:l}]},rh=(i,e,t,r)=>`M ${i.x},${i.y} C ${t.x},${t.y} ${r.x},${r.y} ${e.x},${e.y}`,f6=i=>{let{left:e,top:t,width:r,height:n}=i;return{right:e+r,bottom:t+n,left:e,top:t,width:r,height:n}},m6=(i,e,t,r)=>{let n=Vo(i,e,t,r);return rh({x:i,y:e},{x:t,y:r},n[0],n[1])},pE=(i,e)=>{let{left:t,top:r,translateLeft:n,translateTop:s,width:a,height:o}=i,l=e.clientX,h=e.clientY,d=n+a/2,c=s+o/2,f=t+a/2,m=r+o/2,g=Math.atan(o/a),x=l-d,y=c-h,b=Math.atan2(y,x),S=t+a,A=r+o;if(b=-g){let O=b*(a/2);return(b=0||b>=-g&&b<0)&&(A=m-O),{x:S,y:A,dir:"right",range:O}}else if(b>=g&&b=g){let q=o/2/b;O=-q,S=f+q}else if(b>=Math.PI/2-g&&b=g-Math.PI){let O=0;if(b>=g-Math.PI/2&&b<-g){let q=o/2/b;O=q,S=f-q}else if(b=g-Math.PI){let q=(d-l)/(c-h),P=o/2*q;O=-P,S=f+P}return{x:S,y:A,dir:"bottom",range:O}}S=t;let I=(c-h)/(d-l)*(a/2);return(b>=-Math.PI&&b=Math.PI-g)&&(A=m-I),{x:S,y:A,dir:"left",range:I}},kr=(i,e="right",t=0,r=null)=>{let{left:n,top:s,width:a,height:o}=i;if(r)return pE(i,r);switch(e){case"left":return{x:n,y:s+o/2-t,dir:e};case"right":return{x:n+a,y:s+o/2-t,dir:e};case"top":return{x:n+a/2-t,y:s,dir:e};case"bottom":return{x:n+a/2-t,y:s+o,dir:e};default:break}},Uu=(i,e)=>{let t=f6(i),r=f6(e),n="",s="";switch(z2({x:t.left,y:t.top,width:t.width,height:t.height},{x:r.left,y:r.top,width:r.width,height:r.height})){case"left-top":n="right",s="top";break;case"right-top":n="left",s="top";break;case"right-bottom":n="left",s="bottom";break;case"left-bottom":n="right",s="bottom";break;case"left":n="right",s="left";break;case"right":n="left",s="right";break;case"top":n="right",s="right";break;case"bottom":n="left",s="left";break;case"overlap":n="right",s="right";break;default:break}return[kr(i,n),kr(e,s)]},p6=(i,e,t,r)=>{let n=Go(t,r),s=[],a=t.getData("associativeLineTargetControlOffsets");if(a&&a[n]){let o=a[n];s=[{x:i.x+o[0].x,y:i.y+o[0].y},{x:e.x+o[1].x,y:e.y+o[1].y}]}else s=Vo(i.x,i.y,e.x,e.y);return{path:rh(i,e,s[0],s[1]),controlPoints:s}},$u=(i,e)=>{let t=Vo(i.x,i.y,e.x,e.y);return[{x:t[0].x-i.x,y:t[0].y-i.y},{x:t[1].x-e.x,y:t[1].y-e.y}]}});function gE(i,e){let{associativeLineActiveColor:t}=this.getStyleConfig(i,e);this.controlLine1=this.associativeLineDraw.line().stroke({color:t,width:2}),this.controlLine2=this.associativeLineDraw.line().stroke({color:t,width:2}),this.controlPoint1=this.createOneControlNode("controlPoint1",i,e),this.controlPoint2=this.createOneControlNode("controlPoint2",i,e)}function xE(i,e,t){let{associativeLineActiveColor:r}=this.getStyleConfig(e,t);return this.associativeLineDraw.circle(this.controlPointDiameter).stroke({color:r}).fill({color:"#fff"}).click(n=>{n.stopPropagation()}).mousedown(n=>{this.onControlPointMousedown(n,i)})}function yE(i,e){i.stopPropagation(),i.preventDefault(),this.isControlPointMousedown=!0,this.mousedownControlPointKey=e}function vE(i){if(!this.isControlPointMousedown||!this.mousedownControlPointKey||!this[this.mousedownControlPointKey])return;i.stopPropagation(),i.preventDefault();let e=this.controlPointDiameter/2,{x:t,y:r}=this.getTransformedEventPos(i);this.controlPointMousemoveState.pos={x:t,y:r},this[this.mousedownControlPointKey].x(t-e).y(r-e);let[,,,n,s]=this.activeLine,a=Go(n,s),{associativeLinePoint:o,associativeLineTargetControlOffsets:l}=n.getData();o=o||[];let h=this.getNodePos(n),d=this.getNodePos(s),[c,f]=this.updateAllLinesPos(n,s,o[a]);this.controlPointMousemoveState.startPoint=c,this.controlPointMousemoveState.endPoint=f,this.controlPointMousemoveState.targetIndex=a;let m=[];l?m=l[a]:m=$u(c,f);let g=null,x=null,{x:y,y:b}=this.mindMap.toPos(i.clientX,i.clientY),S={clientX:y,clientY:b};this.mousedownControlPointKey==="controlPoint1"?(c=kr(h,"",0,S),g={x:t,y:r},x={x:f.x+m[1].x,y:f.y+m[1].y},c&&(this.controlPointMousemoveState.startPoint=c,this.controlLine1.plot(c.x,c.y,g.x,g.y))):(f=kr(d,"",0,S),g={x:c.x+m[0].x,y:c.y+m[0].y},x={x:t,y:r},f&&(this.controlPointMousemoveState.endPoint=f,this.controlLine2.plot(f.x,f.y,x.x,x.y))),this.updataAassociativeLine(c,f,g,x,this.activeLine)}function bE(i,e,t,r,n){let[s,a,o]=n,l=rh(i,e,t,r);s.plot(l),a.plot(l),this.updateTextPos(s,o),this.updateTextEditBoxPos(o)}function wE(i){if(!this.isControlPointMousedown)return;i.stopPropagation(),i.preventDefault();let{pos:e,startPoint:t,endPoint:r,targetIndex:n}=this.controlPointMousemoveState,[,,,s]=this.activeLine,a=[],{associativeLinePoint:o,associativeLineTargetControlOffsets:l}=s.getData();o||(o=[]),o[n]=o[n]||{startPoint:t,endPoint:r},l?a=l:a[n]=$u(t,r);let h=null,d=null;this.mousedownControlPointKey==="controlPoint1"?(h={x:e.x-t.x,y:e.y-t.y},d=a[n][1],o[n].startPoint=t):(h=a[n][0],d={x:e.x-r.x,y:e.y-r.y},o[n].endPoint=r),a[n]=[h,d],this.mindMap.execCommand("SET_NODE_DATA",s,{associativeLineTargetControlOffsets:a,associativeLinePoint:o}),this.isNotRenderAllLines=!0,setTimeout(()=>{this.resetControlPoint()},0)}function ME(){this.isControlPointMousedown=!1,this.mousedownControlPointKey="",this.controlPointMousemoveState={pos:null,startPoint:null,endPoint:null,targetIndex:""}}function TE(i,e,t,r,n,s){if(!this.mindMap.opt.enableAdjustAssociativeLinePoints)return;this.controlLine1||this.createControlNodes(n,s);let a=this.controlPointDiameter/2;this.controlLine1.plot(i.x,i.y,t.x,t.y),this.controlLine2.plot(e.x,e.y,r.x,r.y),this.controlPoint1.x(t.x-a).y(t.y-a),this.controlPoint2.x(r.x-a).y(r.y-a)}function NE(){this.controlLine1&&([this.controlLine1,this.controlLine2,this.controlPoint1,this.controlPoint2].forEach(i=>{i.remove()}),this.controlLine1=null,this.controlLine2=null,this.controlPoint1=null,this.controlPoint2=null)}function EE(){this.controlLine1&&[this.controlLine1,this.controlLine2,this.controlPoint1,this.controlPoint2].forEach(i=>{i.hide()})}function SE(){this.controlLine1&&[this.controlLine1,this.controlLine2,this.controlPoint1,this.controlPoint2].forEach(i=>{i.show()})}var Gu,g6=T(()=>{ju();Gu={createControlNodes:gE,createOneControlNode:xE,onControlPointMousedown:yE,onControlPointMousemove:vE,onControlPointMouseup:wE,resetControlPoint:ME,renderControls:TE,removeControls:NE,hideControls:EE,showControls:SE,updataAassociativeLine:bE}});function kE(i){let e=this.associativeLineDraw.group(),t=()=>{(!this.activeLine||this.activeLine[3]!==i.node||this.activeLine[4]!==i.toNode)&&this.setActiveLine({...i,text:e})};return e.click(r=>{r.stopPropagation(),t()}),e.on("dblclick",r=>{r.stopPropagation(),t(),this.activeLine&&this.showEditTextBox(e)}),e}function CE(i){this.mindMap.emit("before_show_text_edit"),this.mindMap.keyCommand.addShortcut("Enter",()=>{this.hideEditTextBox()}),this.textEditNode||(this.textEditNode=document.createElement("div"),this.textEditNode.className=AE,this.textEditNode.style.cssText="position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;",this.textEditNode.setAttribute("contenteditable",!0),this.textEditNode.addEventListener("keyup",f=>{f.stopPropagation()}),this.textEditNode.addEventListener("click",f=>{f.stopPropagation()}),(this.mindMap.opt.customInnerElsAppendTo||document.body).appendChild(this.textEditNode));let[,,,e,t]=this.activeLine,{associativeLineTextFontSize:r,associativeLineTextFontFamily:n,associativeLineTextLineHeight:s}=this.getStyleConfig(e,t),{defaultAssociativeLineText:a,nodeTextEditZIndex:o}=this.mindMap.opt,l=this.mindMap.view.scale,h=this.getText(e,t),d=(h||a).split(/\n/gim);this.textEditNode.style.fontFamily=n,this.textEditNode.style.fontSize=r*l+"px",this.textEditNode.style.lineHeight=d.length>1?s:"normal",this.textEditNode.style.zIndex=o,this.textEditNode.innerHTML=d.join("
    "),this.textEditNode.style.display="block",this.updateTextEditBoxPos(i),this.setIsShowTextEdit(!0),h===""||h===a?la(this.textEditNode):oa(this.textEditNode)}function _E(i){this.showTextEdit=i,i?this.mindMap.keyCommand.stopCheckInSvg():this.mindMap.keyCommand.recoveryCheckInSvg()}function LE(){if(!this.textEditNode)return;(this.mindMap.opt.customInnerElsAppendTo||document.body).removeChild(this.textEditNode)}function IE(){this.hideEditTextBox()}function zE(i){let e=i.node.getBoundingClientRect();this.textEditNode&&(this.textEditNode.style.minWidth=`${e.width+10}px`,this.textEditNode.style.minHeight=`${e.height+6}px`,this.textEditNode.style.left=`${e.left}px`,this.textEditNode.style.top=`${e.top}px`)}function RE(){if(!this.showTextEdit)return;let[i,,e,t,r]=this.activeLine,n=na(this.textEditNode.innerHTML);n=n===this.mindMap.opt.defaultAssociativeLineText?"":n,this.mindMap.execCommand("SET_NODE_DATA",t,{associativeLineText:{...t.getData("associativeLineText")||{},[r.getData("uid")]:n}}),this.textEditNode.style.display="none",this.textEditNode.innerHTML="",this.setIsShowTextEdit(!1),this.renderText(n,i,e,t,r),this.mindMap.emit("hide_text_edit")}function DE(i,e){let t=i.getData("associativeLineText");return t&&t[e.getData("uid")]||""}function OE(i,e,t,r,n){if(!i)return;let{associativeLineTextFontSize:s,associativeLineTextLineHeight:a}=this.getStyleConfig(r,n);t.clear(),i.replace(/\n$/g,"").split(/\n/gim).forEach((l,h)=>{l===""&&(l="\uFEFF");let d=new Ce().text(l);d.y(s*a*h),this.styleText(d,r,n),t.add(d)}),x6(e,t)}function BE(i,e,t){let{associativeLineTextColor:r,associativeLineTextFontSize:n,associativeLineTextFontFamily:s}=this.getStyleConfig(e,t);i.fill({color:r}).css({"font-family":s,"font-size":n+"px"})}function x6(i,e){let t=i.length(),r=i.pointAt(t/2),{width:n,height:s}=e.bbox();e.x(r.x-n/2),e.y(r.y-s/2)}var AE,Vu,y6=T(()=>{yt();pe();AE="associative-line-text-edit-warp";Vu={getText:DE,createText:kE,styleText:BE,onScale:IE,showEditTextBox:CE,setIsShowTextEdit:_E,removeTextEditEl:LE,hideEditTextBox:RE,updateTextEditBoxPos:zE,renderText:OE,updateTextPos:x6}});var v6={};tt(v6,{default:()=>FE});var PE,Wu,nh,FE,b6=T(()=>{pe();Hc();ju();g6();y6();PE=["associativeLineWidth","associativeLineColor","associativeLineActiveWidth","associativeLineActiveColor","associativeLineDasharray","associativeLineTextColor","associativeLineTextFontSize","associativeLineTextLineHeight","associativeLineTextFontFamily"],Wu="associative-line-text-edit-warp",nh=class{constructor(e={}){this.mindMap=e.mindMap,this.associativeLineDraw=this.mindMap.associativeLineDraw,this.isNotRenderAllLines=!1,this.lineList=[],this.activeLine=null,this.isCreatingLine=!1,this.creatingStartNode=null,this.creatingLine=null,this.overlapNode=null,this.isNodeDragging=!1,this.controlLine1=null,this.controlLine2=null,this.controlPoint1=null,this.controlPoint2=null,this.controlPointDiameter=10,this.isControlPointMousedown=!1,this.mousedownControlPointKey="",this.controlPointMousemoveState={pos:null,startPoint:null,endPoint:null,targetIndex:""},this.checkOverlapNode=Si(this.checkOverlapNode,100,this),Object.keys(Gu).forEach(t=>{this[t]=Gu[t].bind(this)}),this.showTextEdit=!1,Object.keys(Vu).forEach(t=>{this[t]=Vu[t].bind(this)}),this.mindMap.addEditNodeClass(Wu),this.bindEvent()}bindEvent(){this.renderAllLines=this.renderAllLines.bind(this),this.onDrawClick=this.onDrawClick.bind(this),this.onNodeClick=this.onNodeClick.bind(this),this.removeLine=this.removeLine.bind(this),this.addLine=this.addLine.bind(this),this.onMousemove=this.onMousemove.bind(this),this.onNodeDragging=this.onNodeDragging.bind(this),this.onNodeDragend=this.onNodeDragend.bind(this),this.onControlPointMouseup=this.onControlPointMouseup.bind(this),this.onBeforeDestroy=this.onBeforeDestroy.bind(this),this.mindMap.on("node_tree_render_end",this.renderAllLines),this.mindMap.on("data_change",this.renderAllLines),this.mindMap.on("draw_click",this.onDrawClick),this.mindMap.on("node_click",this.onNodeClick),this.mindMap.on("contextmenu",this.onDrawClick),this.mindMap.keyCommand.addShortcut("Del|Backspace",this.removeLine),this.mindMap.command.add("ADD_ASSOCIATIVE_LINE",this.addLine),this.mindMap.on("mousemove",this.onMousemove),this.mindMap.on("node_dragging",this.onNodeDragging),this.mindMap.on("node_dragend",this.onNodeDragend),this.mindMap.on("mouseup",this.onControlPointMouseup),this.mindMap.on("scale",this.onScale),this.mindMap.on("beforeDestroy",this.onBeforeDestroy)}unBindEvent(){this.mindMap.off("node_tree_render_end",this.renderAllLines),this.mindMap.off("data_change",this.renderAllLines),this.mindMap.off("draw_click",this.onDrawClick),this.mindMap.off("node_click",this.onNodeClick),this.mindMap.off("contextmenu",this.onDrawClick),this.mindMap.keyCommand.removeShortcut("Del|Backspace",this.removeLine),this.mindMap.command.remove("ADD_ASSOCIATIVE_LINE",this.addLine),this.mindMap.off("mousemove",this.onMousemove),this.mindMap.off("node_dragging",this.onNodeDragging),this.mindMap.off("node_dragend",this.onNodeDragend),this.mindMap.off("mouseup",this.onControlPointMouseup),this.mindMap.off("scale",this.onScale),this.mindMap.off("beforeDestroy",this.onBeforeDestroy)}getStyleConfig(e,t){let r={};t&&(r=(e.getData("associativeLineStyle")||{})[t.getData("uid")]||{});let n={};return PE.forEach(s=>{typeof r[s]<"u"?n[s]=r[s]:n[s]=e.getStyle(s)}),n}onBeforeDestroy(){this.hideEditTextBox(),this.removeTextEditEl()}onDrawClick(){this.isCreatingLine&&this.cancelCreateLine(),this.isControlPointMousedown||(this.clearActiveLine(),this.renderAllLines())}onNodeClick(e){this.isCreatingLine?this.completeCreateLine(e):(this.clearActiveLine(),this.renderAllLines())}createMarker(e=()=>{}){return this.associativeLineDraw.marker(20,20,t=>{t.ref(12,5),t.size(10,10),t.attr("orient","auto-start-reverse"),e(t.path("M0,0 L2,5 L0,10 L10,5 Z"))})}updateAllLinesPos(e,t,r){r=r||{};let[n,s]=Uu(e,t),a=0,o="",l=0,h="";return r.startPoint&&(a=r.startPoint.range||0,o=r.startPoint.dir||"right",n=kr(e,o,a)),r.endPoint&&(l=r.endPoint.range||0,h=r.endPoint.dir||"right",s=kr(t,h,l)),[n,s]}renderAllLines(){if(this.isNotRenderAllLines){this.isNotRenderAllLines=!1;return}this.removeAllLines(),this.removeControls(),this.clearActiveLine();let e=this.mindMap.renderer.root;if(!e)return;let t=new Map,r=new Map;de(e,null,n=>{if(!n)return;let s=n.getData();s.associativeLineTargets&&s.associativeLineTargets.length>0&&r.set(n,s.associativeLineTargets),s.uid&&t.set(s.uid,n)},()=>{},!0,0),r.forEach((n,s)=>{n.forEach((a,o)=>{let l=t.get(a);if(!s||!l)return;let h=(s.getData("associativeLinePoint")||[])[o],[d,c]=this.updateAllLinesPos(s,l,h);this.drawLine(d,c,s,l)})})}drawLine(e,t,r,n){let{associativeLineWidth:s,associativeLineColor:a,associativeLineActiveWidth:o,associativeLineDasharray:l}=this.getStyleConfig(r,n),h=null,d=this.createMarker(y=>{h=y});h.stroke({color:a}).fill({color:a});let{path:c,controlPoints:f}=p6(e,t,r,n),m=this.associativeLineDraw.path();m.stroke({width:s,color:a,dasharray:l||"6,4"}).fill({color:"none"}),m.plot(c),m.marker("end",d);let g=this.associativeLineDraw.path();g.stroke({width:o,color:"transparent"}).fill({color:"none"}),g.plot(c);let x=this.createText({path:m,clickPath:g,markerPath:h,node:r,toNode:n,startPoint:e,endPoint:t,controlPoints:f});g.click(y=>{y.stopPropagation(),this.setActiveLine({path:m,clickPath:g,markerPath:h,text:x,node:r,toNode:n,startPoint:e,endPoint:t,controlPoints:f})}),g.dblclick(()=>{this.activeLine&&this.showEditTextBox(x)}),this.renderText(this.getText(r,n),m,x,r,n),this.lineList.push([m,g,x,r,n])}updateActiveLineStyle(){if(!this.activeLine)return;this.isNotRenderAllLines=!0;let[e,t,r,n,s,a]=this.activeLine,{associativeLineWidth:o,associativeLineColor:l,associativeLineDasharray:h,associativeLineActiveWidth:d,associativeLineActiveColor:c,associativeLineTextColor:f,associativeLineTextFontFamily:m,associativeLineTextFontSize:g}=this.getStyleConfig(n,s);e.stroke({width:o,color:l,dasharray:h||"6,4"}).fill({color:"none"}),t.stroke({width:d,color:c}).fill({color:"none"}),a.stroke({color:l}).fill({color:l}),r.find("text").forEach(x=>{x.fill({color:f}).css({"font-family":m,"font-size":g+"px"})}),this.controlLine1&&this.controlLine1.stroke({color:c}),this.controlLine2&&this.controlLine2.stroke({color:c}),this.controlPoint1&&this.controlPoint1.stroke({color:c}),this.controlPoint2&&this.controlPoint2.stroke({color:c}),this.updateTextPos(e,r)}setActiveLine({path:e,clickPath:t,markerPath:r,text:n,node:s,toNode:a,startPoint:o,endPoint:l,controlPoints:h}){let{associativeLineActiveColor:d}=this.getStyleConfig(s,a);this.mindMap.execCommand("CLEAR_ACTIVE_NODE"),this.clearActiveLine(),this.activeLine=[e,t,n,s,a,r],t.stroke({color:d}),this.getText(s,a)||this.renderText(this.mindMap.opt.defaultAssociativeLineText,e,n,s,a),this.renderControls(o,l,h[0],h[1],s,a),this.mindMap.emit("associative_line_click",e,t,s,a),this.front()}removeAllLines(){this.lineList.forEach(e=>{e[0].remove(),e[1].remove(),e[2].remove()}),this.lineList=[]}createLineFromActiveNode(){if(this.mindMap.renderer.activeNodeList.length<=0)return;let e=this.mindMap.renderer.activeNodeList[0];this.createLine(e)}createLine(e){let{associativeLineWidth:t,associativeLineColor:r,associativeLineDasharray:n}=this.getStyleConfig(e);if(this.isCreatingLine||!e)return;this.front(),this.isCreatingLine=!0,this.creatingStartNode=e,this.creatingLine=this.associativeLineDraw.path(),this.creatingLine.stroke({width:t,color:r,dasharray:n||"6,4"}).fill({color:"none"});let s=null,a=this.createMarker(o=>{s=o});s.stroke({color:r}).fill({color:r}),this.creatingLine.marker("end",a)}cancelCreateLine(){this.isCreatingLine=!1,this.creatingStartNode=null,this.creatingLine.remove(),this.creatingLine=null,this.overlapNode=null,this.back()}onMousemove(e){this.onControlPointMousemove(e),this.updateCreatingLine(e)}updateCreatingLine(e){if(!this.isCreatingLine)return;let{x:t,y:r}=this.getTransformedEventPos(e),n=kr(this.creatingStartNode),s=t>n.x?-10:10,a=m6(n.x,n.y,t+s,r);this.creatingLine.plot(a),this.checkOverlapNode(t,r)}getTransformedEventPos(e){let{x:t,y:r}=this.mindMap.toPos(e.clientX,e.clientY),{scaleX:n,scaleY:s,translateX:a,translateY:o}=this.mindMap.draw.transform();return{x:(t-a)/n,y:(r-o)/s}}getNodePos(e){let{scaleX:t,scaleY:r,translateX:n,translateY:s}=this.mindMap.draw.transform(),{left:a,top:o,width:l,height:h}=e,d=a*t+n,c=o*r+s;return{left:a,top:o,translateLeft:d,translateTop:c,width:l,height:h}}checkOverlapNode(e,t){this.overlapNode=null,Xt(this.mindMap.renderer.root,r=>{if(r.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",r,!1),r.uid===this.creatingStartNode.uid||this.overlapNode)return;let{left:n,top:s,width:a,height:o}=r,l=n+a,h=s+o;e>=n&&e<=l&&t>=s&&t<=h&&(this.overlapNode=r)}),this.overlapNode&&!this.overlapNode.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",this.overlapNode,!0)}completeCreateLine(e){if(this.creatingStartNode.uid===e.uid)return;let{beforeAssociativeLineConnection:t}=this.mindMap.opt,r=!1;typeof t=="function"&&(r=t(e)),!r&&(this.addLine(this.creatingStartNode,e),this.overlapNode&&this.overlapNode.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",this.overlapNode,!1),this.cancelCreateLine())}addLine(e,t){if(!e||!t)return;let r=t.getData("uid");r||(r=fo(),this.mindMap.execCommand("SET_NODE_DATA",t,{uid:r}));let n=e.getData("associativeLineTargets")||[];if(n.some(f=>f===r))return;n.push(r);let[a,o]=Uu(e,t),l=Vo(a.x,a.y,o.x,o.y),{associativeLineInitPointsPosition:h}=this.mindMap.opt;if(h){let{from:f,to:m}=h;f&&(a.dir=f),m&&(o.dir=m)}let d=e.getData("associativeLineTargetControlOffsets")||[];d[n.length-1]=[{x:l[0].x-a.x,y:l[0].y-a.y},{x:l[1].x-o.x,y:l[1].y-o.y}];let c=e.getData("associativeLinePoint")||[];c[n.length-1]={startPoint:a,endPoint:o},this.mindMap.execCommand("SET_NODE_DATA",e,{associativeLineTargets:n,associativeLineTargetControlOffsets:d,associativeLinePoint:c})}removeLine(){if(!this.activeLine)return;let[,,,e,t]=this.activeLine;this.removeControls();let{associativeLineTargets:r,associativeLinePoint:n,associativeLineTargetControlOffsets:s,associativeLineText:a,associativeLineStyle:o}=e.getData();n=n||[];let l=Go(e,t),h={};a&&Object.keys(a).forEach(c=>{c!==t.getData("uid")&&(h[c]=a[c])});let d={};o&&Object.keys(o).forEach(c=>{c!==t.getData("uid")&&(d[c]=o[c])}),this.mindMap.execCommand("SET_NODE_DATA",e,{associativeLineTargets:r.filter((c,f)=>f!==l),associativeLinePoint:n.filter((c,f)=>f!==l),associativeLineTargetControlOffsets:s?s.filter((c,f)=>f!==l):[],associativeLineText:h,associativeLineStyle:d})}clearActiveLine(){if(this.activeLine){let[,e,t,r,n]=this.activeLine;e.stroke({color:"transparent"}),this.hideEditTextBox(),this.getText(r,n)||t.clear(),this.activeLine=null,this.removeControls(),this.back(),this.mindMap.emit("associative_line_deactivate")}}onNodeDragging(){this.isNodeDragging||(this.isNodeDragging=!0,this.lineList.forEach(e=>{e[0].hide(),e[1].hide(),e[2].hide()}),this.hideControls())}onNodeDragend(){this.isNodeDragging&&(this.lineList.forEach(e=>{e[0].show(),e[1].show(),e[2].show()}),this.showControls(),this.isNodeDragging=!1)}front(){this.mindMap.opt.associativeLineIsAlwaysAboveNode||this.associativeLineDraw.front()}back(){this.mindMap.opt.associativeLineIsAlwaysAboveNode||(this.associativeLineDraw.back(),this.associativeLineDraw.forward())}beforePluginRemove(){this.mindMap.deleteEditNodeClass(Wu),this.unBindEvent()}beforePluginDestroy(){this.mindMap.deleteEditNodeClass(Wu),this.unBindEvent()}};nh.instanceName="associativeLine";FE=nh});var w6={};tt(w6,{default:()=>qE});var sh,qE,M6=T(()=>{pe();X0();$e();sh=class{constructor({mindMap:e}){this.mindMap=e,this.isSearching=!1,this.searchText="",this.matchNodeList=[],this.currentIndex=-1,this.notResetSearchText=!1,this.isJumpNext=!1,this.bindEvent()}bindEvent(){this.onDataChange=this.onDataChange.bind(this),this.onModeChange=this.onModeChange.bind(this),this.mindMap.on("data_change",this.onDataChange),this.mindMap.on("mode_change",this.onModeChange)}unBindEvent(){this.mindMap.off("data_change",this.onDataChange),this.mindMap.off("mode_change",this.onModeChange)}onDataChange(){if(this.isJumpNext){this.isJumpNext=!1,this.search(this.searchText);return}if(this.notResetSearchText){this.notResetSearchText=!1;return}this.searchText=""}onModeChange(e){!(e===k.MODE.READONLY)&&this.isSearching&&this.matchNodeList[this.currentIndex]&&this.matchNodeList[this.currentIndex].closeHighlight()}search(e,t=()=>{}){if(Yt(e))return this.endSearch();e=String(e),this.isSearching=!0,this.searchText===e?this.searchNext(t):(this.searchText=e,this.doSearch(),this.searchNext(t)),this.emitEvent()}updateMatchNodeList(e){this.matchNodeList=e,this.mindMap.emit("search_match_node_list_change",e)}endSearch(){this.isSearching&&(this.mindMap.opt.readonly&&this.matchNodeList[this.currentIndex]&&this.matchNodeList[this.currentIndex].closeHighlight(),this.searchText="",this.updateMatchNodeList([]),this.currentIndex=-1,this.notResetSearchText=!1,this.isSearching=!1,this.emitEvent())}doSearch(){this.clearHighlightOnReadonly(),this.updateMatchNodeList([]),this.currentIndex=-1;let{isOnlySearchCurrentRenderNodes:e}=this.mindMap.opt,t=e?this.mindMap.renderer.root:this.mindMap.renderer.renderTree;if(!t)return;let r=[];Xt(t,n=>{let{richText:s,text:a,generalization:o}=e?n.getData():n.data;s&&(a=sa(a)),a.includes(this.searchText)&&r.push(n),us({generalization:o}).forEach(h=>{let{richText:d,text:c,uid:f}=h;e&&!this.mindMap.renderer.findNodeByUid(f)||(d&&(c=sa(c)),c.includes(this.searchText)&&r.push({data:h}))})}),this.updateMatchNodeList(r)}isNodeInstance(e){return e instanceof da}searchNext(e,t){if(!this.isSearching||this.matchNodeList.length<=0)return;t!==void 0&&Number.isInteger(t)&&t>=0&&t{this.isNodeInstance(n)||(this.matchNodeList[this.currentIndex]=o,this.updateMatchNodeList(this.matchNodeList)),e(),r&&o.highlight(),a&&(this.notResetSearchText=!1)})}clearHighlightOnReadonly(){let{readonly:e}=this.mindMap.opt;e&&this.matchNodeList.forEach(t=>{this.isNodeInstance(t)&&t.closeHighlight()})}jump(e,t=()=>{}){this.searchNext(t,e)}replace(e,t=!1){if(e==null||!this.isSearching||this.matchNodeList.length<=0)return;this.isJumpNext=t,e=String(e);let r=this.matchNodeList[this.currentIndex];if(!r)return;let n=e.includes(this.searchText),s=this.getReplacedText(r,this.searchText,e);if(this.notResetSearchText=!0,r.setText(s,r.getData("richText")),n){this.updateMatchNodeList(this.matchNodeList);return}let a=this.matchNodeList.filter(o=>r!==o);this.updateMatchNodeList(a),this.currentIndex>this.matchNodeList.length-1?this.currentIndex=-1:this.currentIndex--,this.emitEvent()}replaceAll(e){if(e==null||!this.isSearching||this.matchNodeList.length<=0)return;e=String(e);let t=e.includes(this.searchText);this.notResetSearchText=!0,this.matchNodeList.forEach(r=>{let n=this.getReplacedText(r,this.searchText,e);if(this.isNodeInstance(r)){let s={text:n};this.mindMap.renderer.setNodeDataRender(r,s,!0)}else r.data.text=n}),this.mindMap.render(),this.mindMap.command.addHistory(),t?this.updateMatchNodeList(this.matchNodeList):this.endSearch()}getReplacedText(e,t,r){let{richText:n,text:s}=this.isNodeInstance(e)?e.getData():e.data;return n?M2(s,t,r):s.replace(new RegExp(t,"g"),r)}emitEvent(){this.mindMap.emit("search_info_change",{currentIndex:this.currentIndex,total:this.matchNodeList.length})}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};sh.instanceName="search";qE=sh});var T6,N6,E6=T(()=>{pe();T6=i=>{i=ds(i);let e={},t={};i.forEach(n=>{let s=n.parent;if(s){let a=s.uid;t[a]=s;let o=n.getIndexInBrothers(),l={node:n,index:o};e[a]?e[a].find(h=>h.index===l.index)||e[a].push(l):e[a]=[l]}});let r=[];return Object.keys(e).forEach(n=>{let s=e[n],a=t[n];if(s.length>1){let o=s.map(f=>f.index).sort((f,m)=>f-m),l=o[0],h=o[o.length-1],d=-1,c=-1;for(let f=l;f<=h;f++)o.includes(f)?(d===-1&&(d=f),c=f):(d!==-1&&c!==-1&&r.push({node:a,range:[d,c]}),d=-1,c=-1);d!==-1&&c!==-1&&r.push({node:a,range:[d,c]})}else r.push({node:a,range:[s[0].index,s[0].index]})}),r},N6=i=>{let e=i.children;if(!e||e.length<=0)return;let t=[],r={};return e.forEach((n,s)=>{let a=n.getData("outerFrame");if(!a)return;let o=a.groupId;o?(r[o]||(r[o]=[]),r[o].push({node:n,index:s})):t.push({nodeList:[n],range:[s,s]})}),Object.keys(r).forEach(n=>{let s=r[n];t.push({nodeList:s.map(a=>a.node),range:[s[0].index,s[s.length-1].index]})}),t}});function UE(i,e,t){let r=this.draw.group(),n=()=>{(!this.activeOuterFrame||this.activeOuterFrame.el!==i)&&this.setActiveOuterFrame(i,e,t,r)};return r.click(s=>{s.stopPropagation(),n()}),r.on("dblclick",s=>{s.stopPropagation(),n(),this.showEditTextBox(r)}),r}function $E(i){this.mindMap.emit("before_show_text_edit"),this.mindMap.keyCommand.addShortcut("Enter",()=>{this.hideEditTextBox()}),this.textEditNode||(this.textEditNode=document.createElement("div"),this.textEditNode.className=HE,this.textEditNode.style.cssText=` +`},()=>{},!0),e}});var T6={};tt(T6,{default:()=>BE});var hh,BE,N6=T(()=>{pe();wt();y6();b6();Ge();M6();hh=class{constructor(e){this.mindMap=e.mindMap}async export(e,t=!0,r="\u601D\u7EF4\u5BFC\u56FE",...n){if(this[e]){let s=await this[e](r,...n);return t&&k2(s,r+"."+e),s}else return null}createTransformImgTaskList(e,t,r,n){return e.find(t).map(async a=>{let o=n(a);if(/^data:/.test(o)||o==="none")return;let l=await Nu(o);a.attr(r,l)})}async getSvgData(e){let{exportPaddingX:t,exportPaddingY:r,errorHandler:n,resetCss:s,addContentToHeader:a,addContentToFooter:o,handleBeingExportSvg:l}=this.mindMap.opt,{svg:h,svgHTML:d,clipData:c}=this.mindMap.getSvgData({paddingX:t,paddingY:r,addContentToHeader:a,addContentToFooter:o,node:e});c&&(c.paddingX=t,c.paddingY=r);let f=!1,m=this.createTransformImgTaskList(h,"image","href",y=>y.attr("href")||y.attr("xlink:href")),g=this.createTransformImgTaskList(h,"img","src",y=>y.attr("src")),x=[...m,...g];try{await Promise.all(x)}catch(y){n(Ti.EXPORT_LOAD_IMAGE_ERROR,y)}if(this.mindMap.richText){let y=h.find("foreignObject");if(y.length>0&&(y[0].add(ke(``)),f=!0),this.mindMap.formula&&h.find(".ql-formula").length>0){let E=this.mindMap.formula.getStyleText();if(E){let k=document.createElement("style");k.innerHTML=E,sr(k),y[0].add(k),f=!0}}}return typeof l=="function"&&(f=!0,h=l(h)),(x.length>0||f)&&(d=h.svg()),{node:h,str:d,clipData:c}}svgToPng(e,t,r=null,n=!1,s="image/png"){let{maxCanvasSize:a,minExportImgCanvasScale:o}=this.mindMap.opt;return new Promise((l,h)=>{let d=new Image;d.setAttribute("crossOrigin","anonymous"),d.onload=async()=>{try{let c=document.createElement("canvas"),f=Math.max(window.devicePixelRatio,o),m=d.width,g=d.height,x=0,y=0;r&&(x=r.paddingX,y=r.paddingY,m=r.width+x*2,g=r.height+y*2);let b=0,E=0,{backgroundImage:k}=this.mindMap.themeConfig;if(n&&k&&!t){let Ce=await new Promise(te=>{let Q=new Image;Q.onload=()=>{te([Q.width,Q.height])},Q.onerror=()=>{te(null)},Q.src=k});if(Ce){let te=m/g,Q=Ce[0]/Ce[1];te>Q?(b=m,E=m/Q):(E=g,b=g*Q)}}let L=1,I=1,O=(b||m)*f,q=(E||g)*f;if(O>a||q>a){let Ce=null,te=null;O>a?Ce=a:q>a&&(te=a);let Q=K0(O,q,Ce,te);L=Q[0]/O,I=Q[1]/q,O=Q[0],q=Q[1]}c.width=O,c.height=q;let F=O/f,W=q/f;c.style.width=F+"px",c.style.height=W+"px";let ne=c.getContext("2d");ne.scale(f,f),t||await this.drawBackgroundToCanvas(ne,F,W);let re=(b>0?(b-m)/2:0)*L,le=(E>0?(E-g)/2:0)*I;r?ne.drawImage(d,r.left,r.top,r.width,r.height,x*L+re,y*I+le,r.width*L,r.height*I):ne.drawImage(d,re,le,m*L,g*I),l(c.toDataURL(s))}catch(c){h(c)}},d.onerror=c=>{h(c)},d.src=e})}drawBackgroundToCanvas(e,t,r){return new Promise((n,s)=>{let{backgroundColor:a="#fff",backgroundImage:o,backgroundRepeat:l="no-repeat",backgroundPosition:h="center center",backgroundSize:d="cover"}=this.mindMap.themeConfig;e.save(),e.rect(0,0,t,r),e.fillStyle=a,e.fill(),e.restore(),o&&o!=="none"?(e.save(),x6(e,t,r,o,{backgroundRepeat:l,backgroundPosition:h,backgroundSize:d},c=>{c?s(c):n(),e.restore()})):n()})}drawBackgroundToSvg(e){return new Promise(async t=>{let{backgroundColor:r="#fff",backgroundImage:n,backgroundRepeat:s="repeat"}=this.mindMap.themeConfig;if(e.css("background-color",r),n&&n!=="none"){let a=await Nu(n);e.css("background-image",`url(${a})`),e.css("background-repeat",s),t()}else t()})}async _image(e,t,r=!1,n=null,s=!1){this.mindMap.renderer.textEdit.hideEditTextBox(),this.handleNodeExport(n);let{str:a,clipData:o}=await this.getSvgData(n),l=await this.fixSvgStrAndToBlob(a);return await this.svgToPng(l,r,o,s,e)}async png(...e){return await this._image("image/png",...e)}async jpg(...e){return await this._image("image/jpg",...e)}handleNodeExport(e){if(e&&e.getData("isActive")){e.deactivate();let{alwaysShowExpandBtn:t,notShowExpandBtn:r}=this.mindMap.opt;!t&&!r&&e.getData("expand")&&e.removeExpandBtn()}}async pdf(e,t=!1,r=!1){if(!this.mindMap.doExportPDF)throw new Error("\u8BF7\u6CE8\u518CExportPDF\u63D2\u4EF6");let n=await this.png(e,t,null,r);return await this.mindMap.doExportPDF.pdf(n)}async xmind(e){if(!this.mindMap.doExportXMind)throw new Error("\u8BF7\u6CE8\u518CExportXMind\u63D2\u4EF6");let t=this.mindMap.getData(),r=await this.mindMap.doExportXMind.xmind(t,e);return await oa(r)}async svg(e){this.mindMap.renderer.textEdit.hideEditTextBox();let{node:t}=await this.getSvgData();t.first().before(ke(`${e}`)),await this.drawBackgroundToSvg(t);let r=t.svg();return await this.fixSvgStrAndToBlob(r)}async fixSvgStrAndToBlob(e){e=R2(e),e=j2(e);let t=new Blob([e],{type:"image/svg+xml"});return await oa(t)}async json(e,t=!0){let r=this.mindMap.getData(t),n=JSON.stringify(r),s=new Blob([n]);return await oa(s)}async smm(e,t){return await this.json(e,t)}async md(){let e=this.mindMap.getData(),t=v6(e),r=new Blob([t]);return await oa(r)}async txt(){let e=this.mindMap.getData(),t=w6(e),r=new Blob([t]);return await oa(r)}};hh.instanceName="doExport";BE=hh});var E6={};tt(E6,{default:()=>PE});var dh,PE,S6=T(()=>{pe();Yu();dh=class{constructor({mindMap:e}){this.mindMap=e,this.rect=null,this.isMousedown=!1,this.mouseDownX=0,this.mouseDownY=0,this.mouseMoveX=0,this.mouseMoveY=0,this.isSelecting=!1,this.cacheActiveList=[],this.autoMove=new nh(e),this.bindEvent()}bindEvent(){this.onMousedown=this.onMousedown.bind(this),this.onMousemove=this.onMousemove.bind(this),this.onMouseup=this.onMouseup.bind(this),this.checkInNodes=Ai(this.checkInNodes,300,this),this.mindMap.on("mousedown",this.onMousedown),this.mindMap.on("mousemove",this.onMousemove),this.mindMap.on("mouseup",this.onMouseup),this.mindMap.on("node_mouseup",this.onMouseup)}unBindEvent(){this.mindMap.off("mousedown",this.onMousedown),this.mindMap.off("mousemove",this.onMousemove),this.mindMap.off("mouseup",this.onMouseup),this.mindMap.off("node_mouseup",this.onMouseup)}onMousedown(e){let{readonly:t,mousedownEventPreventDefault:r}=this.mindMap.opt;if(t)return;let{useLeftKeySelectionRightKeyDrag:n}=this.mindMap.opt;if(!(e.ctrlKey||e.metaKey)&&(n?e.which!==1:e.which!==3))return;r&&e.preventDefault(),this.isMousedown=!0,this.cacheActiveList=[...this.mindMap.renderer.activeNodeList];let{x:s,y:a}=this.mindMap.toPos(e.clientX,e.clientY);this.mouseDownX=s,this.mouseDownY=a,this.createRect(s,a)}onMousemove(e){if(this.mindMap.opt.readonly||!this.isMousedown)return;let{x:t,y:r}=this.mindMap.toPos(e.clientX,e.clientY);this.mouseMoveX=t,this.mouseMoveY=r,!(Math.abs(t-this.mouseDownX)<=10&&Math.abs(r-this.mouseDownY)<=10)&&(this.autoMove.clearAutoMoveTimer(),this.autoMove.onMove(e.clientX,e.clientY,()=>{this.isSelecting=!0,this.rect&&this.rect.plot([[this.mouseDownX,this.mouseDownY],[this.mouseMoveX,this.mouseDownY],[this.mouseMoveX,this.mouseMoveY],[this.mouseDownX,this.mouseMoveY]]),this.checkInNodes()},(n,s)=>{switch(n){case"left":this.mouseDownX+=s;break;case"top":this.mouseDownY+=s;break;case"right":this.mouseDownX-=s;break;case"bottom":this.mouseDownY-=s;break;default:break}}))}onMouseup(){this.mindMap.opt.readonly||this.isMousedown&&(this.checkTriggerNodeActiveEvent(),this.autoMove.clearAutoMoveTimer(),this.isMousedown=!1,this.cacheActiveList=[],this.rect&&this.rect.remove(),this.rect=null,setTimeout(()=>{this.isSelecting=!1},0))}checkTriggerNodeActiveEvent(){let e=this.cacheActiveList.length!==this.mindMap.renderer.activeNodeList.length,t=!1;if(!e)for(let r=0;rs.getData("uid")===n.getData("uid"))){t=!0;break}}(e||t)&&this.mindMap.renderer.emitNodeActiveEvent()}createRect(e,t){this.rect&&this.rect.remove(),this.rect=this.mindMap.svg.polygon().stroke({color:"#0984e3"}).fill({color:"rgba(9,132,227,0.3)"}).plot([[e,t]])}checkInNodes(){let{scaleX:e,scaleY:t,translateX:r,translateY:n}=this.mindMap.draw.transform(),s=Math.min(this.mouseDownX,this.mouseMoveX),a=Math.min(this.mouseDownY,this.mouseMoveY),o=Math.max(this.mouseDownX,this.mouseMoveX),l=Math.max(this.mouseDownY,this.mouseMoveY),h=d=>{let{left:c,top:f,width:m,height:g}=d,x=(c+m)*e+r,y=(f+g)*t+n;if(c=c*e+r,f=f*t+n,U2(s,o,a,l,c,x,f,y)){if(d.getData("isActive"))return;this.mindMap.renderer.addNodeToActiveList(d),this.mindMap.renderer.emitNodeActiveEvent()}else if(d.getData("isActive")){if(!d.getData("isActive"))return;this.mindMap.renderer.removeNodeFromActiveList(d),this.mindMap.renderer.emitNodeActiveEvent()}};Zt(this.mindMap.renderer.root,d=>{h(d),d._generalizationList&&d._generalizationList.length>0&&d._generalizationList.forEach(c=>{h(c.generalizationNode)})})}hasSelectRange(){return this.isSelecting}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};dh.instanceName="select";PE=dh});var Zo,Qo,ch,A6,k6,FE,kr,Ju,C6,e1,t1=T(()=>{pe();Zo=(i,e)=>i.getData("associativeLineTargets").findIndex(t=>t===e.getData("uid")),Qo=(i,e,t,r)=>{let s=i+(t-i)/2,a=e,o=s,l=r;return Math.abs(i-t)<=5&&(s=i+(r-e)/2,o=s),Math.abs(e-r)<=5&&(s=i,a=e-(t-i)/2,o=t,l=a),[{x:s,y:a},{x:o,y:l}]},ch=(i,e,t,r)=>`M ${i.x},${i.y} C ${t.x},${t.y} ${r.x},${r.y} ${e.x},${e.y}`,A6=i=>{let{left:e,top:t,width:r,height:n}=i;return{right:e+r,bottom:t+n,left:e,top:t,width:r,height:n}},k6=(i,e,t,r)=>{let n=Qo(i,e,t,r);return ch({x:i,y:e},{x:t,y:r},n[0],n[1])},FE=(i,e)=>{let{left:t,top:r,translateLeft:n,translateTop:s,width:a,height:o}=i,l=e.clientX,h=e.clientY,d=n+a/2,c=s+o/2,f=t+a/2,m=r+o/2,g=Math.atan(o/a),x=l-d,y=c-h,b=Math.atan2(y,x),E=t+a,k=r+o;if(b=-g){let O=b*(a/2);return(b=0||b>=-g&&b<0)&&(k=m-O),{x:E,y:k,dir:"right",range:O}}else if(b>=g&&b=g){let q=o/2/b;O=-q,E=f+q}else if(b>=Math.PI/2-g&&b=g-Math.PI){let O=0;if(b>=g-Math.PI/2&&b<-g){let q=o/2/b;O=q,E=f-q}else if(b=g-Math.PI){let q=(d-l)/(c-h),F=o/2*q;O=-F,E=f+F}return{x:E,y:k,dir:"bottom",range:O}}E=t;let I=(c-h)/(d-l)*(a/2);return(b>=-Math.PI&&b=Math.PI-g)&&(k=m-I),{x:E,y:k,dir:"left",range:I}},kr=(i,e="right",t=0,r=null)=>{let{left:n,top:s,width:a,height:o}=i;if(r)return FE(i,r);switch(e){case"left":return{x:n,y:s+o/2-t,dir:e};case"right":return{x:n+a,y:s+o/2-t,dir:e};case"top":return{x:n+a/2-t,y:s,dir:e};case"bottom":return{x:n+a/2-t,y:s+o,dir:e};default:break}},Ju=(i,e)=>{let t=A6(i),r=A6(e),n="",s="";switch(V2({x:t.left,y:t.top,width:t.width,height:t.height},{x:r.left,y:r.top,width:r.width,height:r.height})){case"left-top":n="right",s="top";break;case"right-top":n="left",s="top";break;case"right-bottom":n="left",s="bottom";break;case"left-bottom":n="right",s="bottom";break;case"left":n="right",s="left";break;case"right":n="left",s="right";break;case"top":n="right",s="right";break;case"bottom":n="left",s="left";break;case"overlap":n="right",s="right";break;default:break}return[kr(i,n),kr(e,s)]},C6=(i,e,t,r)=>{let n=Zo(t,r),s=[],a=t.getData("associativeLineTargetControlOffsets");if(a&&a[n]){let o=a[n];s=[{x:i.x+o[0].x,y:i.y+o[0].y},{x:e.x+o[1].x,y:e.y+o[1].y}]}else s=Qo(i.x,i.y,e.x,e.y);return{path:ch(i,e,s[0],s[1]),controlPoints:s}},e1=(i,e)=>{let t=Qo(i.x,i.y,e.x,e.y);return[{x:t[0].x-i.x,y:t[0].y-i.y},{x:t[1].x-e.x,y:t[1].y-e.y}]}});function qE(i,e){let{associativeLineActiveColor:t}=this.getStyleConfig(i,e);this.controlLine1=this.associativeLineDraw.line().stroke({color:t,width:2}),this.controlLine2=this.associativeLineDraw.line().stroke({color:t,width:2}),this.controlPoint1=this.createOneControlNode("controlPoint1",i,e),this.controlPoint2=this.createOneControlNode("controlPoint2",i,e)}function HE(i,e,t){let{associativeLineActiveColor:r}=this.getStyleConfig(e,t);return this.associativeLineDraw.circle(this.controlPointDiameter).stroke({color:r}).fill({color:"#fff"}).click(n=>{n.stopPropagation()}).mousedown(n=>{this.onControlPointMousedown(n,i)})}function UE(i,e){i.stopPropagation(),i.preventDefault(),this.isControlPointMousedown=!0,this.mousedownControlPointKey=e}function $E(i){if(!this.isControlPointMousedown||!this.mousedownControlPointKey||!this[this.mousedownControlPointKey])return;i.stopPropagation(),i.preventDefault();let e=this.controlPointDiameter/2,{x:t,y:r}=this.getTransformedEventPos(i);this.controlPointMousemoveState.pos={x:t,y:r},this[this.mousedownControlPointKey].x(t-e).y(r-e);let[,,,n,s]=this.activeLine,a=Zo(n,s),{associativeLinePoint:o,associativeLineTargetControlOffsets:l}=n.getData();o=o||[];let h=this.getNodePos(n),d=this.getNodePos(s),[c,f]=this.updateAllLinesPos(n,s,o[a]);this.controlPointMousemoveState.startPoint=c,this.controlPointMousemoveState.endPoint=f,this.controlPointMousemoveState.targetIndex=a;let m=[];l?m=l[a]:m=e1(c,f);let g=null,x=null,{x:y,y:b}=this.mindMap.toPos(i.clientX,i.clientY),E={clientX:y,clientY:b};this.mousedownControlPointKey==="controlPoint1"?(c=kr(h,"",0,E),g={x:t,y:r},x={x:f.x+m[1].x,y:f.y+m[1].y},c&&(this.controlPointMousemoveState.startPoint=c,this.controlLine1.plot(c.x,c.y,g.x,g.y))):(f=kr(d,"",0,E),g={x:c.x+m[0].x,y:c.y+m[0].y},x={x:t,y:r},f&&(this.controlPointMousemoveState.endPoint=f,this.controlLine2.plot(f.x,f.y,x.x,x.y))),this.updataAassociativeLine(c,f,g,x,this.activeLine)}function jE(i,e,t,r,n){let[s,a,o]=n,l=ch(i,e,t,r);s.plot(l),a.plot(l),this.updateTextPos(s,o),this.updateTextEditBoxPos(o)}function GE(i){if(!this.isControlPointMousedown)return;i.stopPropagation(),i.preventDefault();let{pos:e,startPoint:t,endPoint:r,targetIndex:n}=this.controlPointMousemoveState,[,,,s]=this.activeLine,a=[],{associativeLinePoint:o,associativeLineTargetControlOffsets:l}=s.getData();o||(o=[]),o[n]=o[n]||{startPoint:t,endPoint:r},l?a=l:a[n]=e1(t,r);let h=null,d=null;this.mousedownControlPointKey==="controlPoint1"?(h={x:e.x-t.x,y:e.y-t.y},d=a[n][1],o[n].startPoint=t):(h=a[n][0],d={x:e.x-r.x,y:e.y-r.y},o[n].endPoint=r),a[n]=[h,d],this.mindMap.execCommand("SET_NODE_DATA",s,{associativeLineTargetControlOffsets:a,associativeLinePoint:o}),this.isNotRenderAllLines=!0,setTimeout(()=>{this.resetControlPoint()},0)}function WE(){this.isControlPointMousedown=!1,this.mousedownControlPointKey="",this.controlPointMousemoveState={pos:null,startPoint:null,endPoint:null,targetIndex:""}}function VE(i,e,t,r,n,s){if(!this.mindMap.opt.enableAdjustAssociativeLinePoints)return;this.controlLine1||this.createControlNodes(n,s);let a=this.controlPointDiameter/2;this.controlLine1.plot(i.x,i.y,t.x,t.y),this.controlLine2.plot(e.x,e.y,r.x,r.y),this.controlPoint1.x(t.x-a).y(t.y-a),this.controlPoint2.x(r.x-a).y(r.y-a)}function YE(){this.controlLine1&&([this.controlLine1,this.controlLine2,this.controlPoint1,this.controlPoint2].forEach(i=>{i.remove()}),this.controlLine1=null,this.controlLine2=null,this.controlPoint1=null,this.controlPoint2=null)}function XE(){this.controlLine1&&[this.controlLine1,this.controlLine2,this.controlPoint1,this.controlPoint2].forEach(i=>{i.hide()})}function KE(){this.controlLine1&&[this.controlLine1,this.controlLine2,this.controlPoint1,this.controlPoint2].forEach(i=>{i.show()})}var i1,_6=T(()=>{t1();i1={createControlNodes:qE,createOneControlNode:HE,onControlPointMousedown:UE,onControlPointMousemove:$E,onControlPointMouseup:GE,resetControlPoint:WE,renderControls:VE,removeControls:YE,hideControls:XE,showControls:KE,updataAassociativeLine:jE}});function QE(i){let e=this.associativeLineDraw.group(),t=()=>{(!this.activeLine||this.activeLine[3]!==i.node||this.activeLine[4]!==i.toNode)&&this.setActiveLine({...i,text:e})};return e.click(r=>{r.stopPropagation(),t()}),e.on("dblclick",r=>{r.stopPropagation(),t(),this.activeLine&&this.showEditTextBox(e)}),e}function JE(i){this.mindMap.emit("before_show_text_edit"),this.mindMap.keyCommand.addShortcut("Enter",()=>{this.hideEditTextBox()}),this.textEditNode||(this.textEditNode=document.createElement("div"),this.textEditNode.className=ZE,this.textEditNode.style.cssText="position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;",this.textEditNode.setAttribute("contenteditable",!0),this.textEditNode.addEventListener("keyup",f=>{f.stopPropagation()}),this.textEditNode.addEventListener("click",f=>{f.stopPropagation()}),(this.mindMap.opt.customInnerElsAppendTo||document.body).appendChild(this.textEditNode));let[,,,e,t]=this.activeLine,{associativeLineTextFontSize:r,associativeLineTextFontFamily:n,associativeLineTextLineHeight:s}=this.getStyleConfig(e,t),{defaultAssociativeLineText:a,nodeTextEditZIndex:o}=this.mindMap.opt,l=this.mindMap.view.scale,h=this.getText(e,t),d=(h||a).split(/\n/gim);this.textEditNode.style.fontFamily=n,this.textEditNode.style.fontSize=r*l+"px",this.textEditNode.style.lineHeight=d.length>1?s:"normal",this.textEditNode.style.zIndex=o,this.textEditNode.innerHTML=d.join("
    "),this.textEditNode.style.display="block",this.updateTextEditBoxPos(i),this.setIsShowTextEdit(!0),h===""||h===a?ha(this.textEditNode):la(this.textEditNode)}function eS(i){this.showTextEdit=i,i?this.mindMap.keyCommand.stopCheckInSvg():this.mindMap.keyCommand.recoveryCheckInSvg()}function tS(){if(!this.textEditNode)return;(this.mindMap.opt.customInnerElsAppendTo||document.body).removeChild(this.textEditNode)}function iS(){this.hideEditTextBox()}function rS(i){let e=i.node.getBoundingClientRect();this.textEditNode&&(this.textEditNode.style.minWidth=`${e.width+10}px`,this.textEditNode.style.minHeight=`${e.height+6}px`,this.textEditNode.style.left=`${e.left}px`,this.textEditNode.style.top=`${e.top}px`)}function nS(){if(!this.showTextEdit)return;let[i,,e,t,r]=this.activeLine,n=sa(this.textEditNode.innerHTML);n=n===this.mindMap.opt.defaultAssociativeLineText?"":n,this.mindMap.execCommand("SET_NODE_DATA",t,{associativeLineText:{...t.getData("associativeLineText")||{},[r.getData("uid")]:n}}),this.textEditNode.style.display="none",this.textEditNode.innerHTML="",this.setIsShowTextEdit(!1),this.renderText(n,i,e,t,r),this.mindMap.emit("hide_text_edit")}function sS(i,e){let t=i.getData("associativeLineText");return t&&t[e.getData("uid")]||""}function aS(i,e,t,r,n){if(!i)return;let{associativeLineTextFontSize:s,associativeLineTextLineHeight:a}=this.getStyleConfig(r,n);t.clear(),i.replace(/\n$/g,"").split(/\n/gim).forEach((l,h)=>{l===""&&(l="\uFEFF");let d=new _e().text(l);d.y(s*a*h),this.styleText(d,r,n),t.add(d)}),L6(e,t)}function oS(i,e,t){let{associativeLineTextColor:r,associativeLineTextFontSize:n,associativeLineTextFontFamily:s}=this.getStyleConfig(e,t);i.fill({color:r}).css({"font-family":s,"font-size":n+"px"})}function L6(i,e){let t=i.length(),r=i.pointAt(t/2),{width:n,height:s}=e.bbox();e.x(r.x-n/2),e.y(r.y-s/2)}var ZE,r1,I6=T(()=>{wt();pe();ZE="associative-line-text-edit-warp";r1={getText:sS,createText:QE,styleText:oS,onScale:iS,showEditTextBox:JE,setIsShowTextEdit:eS,removeTextEditEl:tS,hideEditTextBox:nS,updateTextEditBoxPos:rS,renderText:aS,updateTextPos:L6}});var z6={};tt(z6,{default:()=>hS});var lS,n1,uh,hS,R6=T(()=>{pe();Qc();t1();_6();I6();lS=["associativeLineWidth","associativeLineColor","associativeLineActiveWidth","associativeLineActiveColor","associativeLineDasharray","associativeLineTextColor","associativeLineTextFontSize","associativeLineTextLineHeight","associativeLineTextFontFamily"],n1="associative-line-text-edit-warp",uh=class{constructor(e={}){this.mindMap=e.mindMap,this.associativeLineDraw=this.mindMap.associativeLineDraw,this.isNotRenderAllLines=!1,this.lineList=[],this.activeLine=null,this.isCreatingLine=!1,this.creatingStartNode=null,this.creatingLine=null,this.overlapNode=null,this.isNodeDragging=!1,this.controlLine1=null,this.controlLine2=null,this.controlPoint1=null,this.controlPoint2=null,this.controlPointDiameter=10,this.isControlPointMousedown=!1,this.mousedownControlPointKey="",this.controlPointMousemoveState={pos:null,startPoint:null,endPoint:null,targetIndex:""},this.checkOverlapNode=Ai(this.checkOverlapNode,100,this),Object.keys(i1).forEach(t=>{this[t]=i1[t].bind(this)}),this.showTextEdit=!1,Object.keys(r1).forEach(t=>{this[t]=r1[t].bind(this)}),this.mindMap.addEditNodeClass(n1),this.bindEvent()}bindEvent(){this.renderAllLines=this.renderAllLines.bind(this),this.onDrawClick=this.onDrawClick.bind(this),this.onNodeClick=this.onNodeClick.bind(this),this.removeLine=this.removeLine.bind(this),this.addLine=this.addLine.bind(this),this.onMousemove=this.onMousemove.bind(this),this.onNodeDragging=this.onNodeDragging.bind(this),this.onNodeDragend=this.onNodeDragend.bind(this),this.onControlPointMouseup=this.onControlPointMouseup.bind(this),this.onBeforeDestroy=this.onBeforeDestroy.bind(this),this.mindMap.on("node_tree_render_end",this.renderAllLines),this.mindMap.on("data_change",this.renderAllLines),this.mindMap.on("draw_click",this.onDrawClick),this.mindMap.on("node_click",this.onNodeClick),this.mindMap.on("contextmenu",this.onDrawClick),this.mindMap.keyCommand.addShortcut("Del|Backspace",this.removeLine),this.mindMap.command.add("ADD_ASSOCIATIVE_LINE",this.addLine),this.mindMap.on("mousemove",this.onMousemove),this.mindMap.on("node_dragging",this.onNodeDragging),this.mindMap.on("node_dragend",this.onNodeDragend),this.mindMap.on("mouseup",this.onControlPointMouseup),this.mindMap.on("scale",this.onScale),this.mindMap.on("beforeDestroy",this.onBeforeDestroy)}unBindEvent(){this.mindMap.off("node_tree_render_end",this.renderAllLines),this.mindMap.off("data_change",this.renderAllLines),this.mindMap.off("draw_click",this.onDrawClick),this.mindMap.off("node_click",this.onNodeClick),this.mindMap.off("contextmenu",this.onDrawClick),this.mindMap.keyCommand.removeShortcut("Del|Backspace",this.removeLine),this.mindMap.command.remove("ADD_ASSOCIATIVE_LINE",this.addLine),this.mindMap.off("mousemove",this.onMousemove),this.mindMap.off("node_dragging",this.onNodeDragging),this.mindMap.off("node_dragend",this.onNodeDragend),this.mindMap.off("mouseup",this.onControlPointMouseup),this.mindMap.off("scale",this.onScale),this.mindMap.off("beforeDestroy",this.onBeforeDestroy)}getStyleConfig(e,t){let r={};t&&(r=(e.getData("associativeLineStyle")||{})[t.getData("uid")]||{});let n={};return lS.forEach(s=>{typeof r[s]<"u"?n[s]=r[s]:n[s]=e.getStyle(s)}),n}onBeforeDestroy(){this.hideEditTextBox(),this.removeTextEditEl()}onDrawClick(){this.isCreatingLine&&this.cancelCreateLine(),this.isControlPointMousedown||(this.clearActiveLine(),this.renderAllLines())}onNodeClick(e){this.isCreatingLine?this.completeCreateLine(e):(this.clearActiveLine(),this.renderAllLines())}createMarker(e=()=>{}){return this.associativeLineDraw.marker(20,20,t=>{t.ref(12,5),t.size(10,10),t.attr("orient","auto-start-reverse"),e(t.path("M0,0 L2,5 L0,10 L10,5 Z"))})}updateAllLinesPos(e,t,r){r=r||{};let[n,s]=Ju(e,t),a=0,o="",l=0,h="";return r.startPoint&&(a=r.startPoint.range||0,o=r.startPoint.dir||"right",n=kr(e,o,a)),r.endPoint&&(l=r.endPoint.range||0,h=r.endPoint.dir||"right",s=kr(t,h,l)),[n,s]}renderAllLines(){if(this.isNotRenderAllLines){this.isNotRenderAllLines=!1;return}this.removeAllLines(),this.removeControls(),this.clearActiveLine();let e=this.mindMap.renderer.root;if(!e)return;let t=new Map,r=new Map;ue(e,null,n=>{if(!n)return;let s=n.getData();s.associativeLineTargets&&s.associativeLineTargets.length>0&&r.set(n,s.associativeLineTargets),s.uid&&t.set(s.uid,n)},()=>{},!0,0),r.forEach((n,s)=>{n.forEach((a,o)=>{let l=t.get(a);if(!s||!l)return;let h=(s.getData("associativeLinePoint")||[])[o],[d,c]=this.updateAllLinesPos(s,l,h);this.drawLine(d,c,s,l)})})}drawLine(e,t,r,n){let{associativeLineWidth:s,associativeLineColor:a,associativeLineActiveWidth:o,associativeLineDasharray:l}=this.getStyleConfig(r,n),h=null,d=this.createMarker(y=>{h=y});h.stroke({color:a}).fill({color:a});let{path:c,controlPoints:f}=C6(e,t,r,n),m=this.associativeLineDraw.path();m.stroke({width:s,color:a,dasharray:l||"6,4"}).fill({color:"none"}),m.plot(c),m.marker("end",d);let g=this.associativeLineDraw.path();g.stroke({width:o,color:"transparent"}).fill({color:"none"}),g.plot(c);let x=this.createText({path:m,clickPath:g,markerPath:h,node:r,toNode:n,startPoint:e,endPoint:t,controlPoints:f});g.click(y=>{y.stopPropagation(),this.setActiveLine({path:m,clickPath:g,markerPath:h,text:x,node:r,toNode:n,startPoint:e,endPoint:t,controlPoints:f})}),g.dblclick(()=>{this.activeLine&&this.showEditTextBox(x)}),this.renderText(this.getText(r,n),m,x,r,n),this.lineList.push([m,g,x,r,n])}updateActiveLineStyle(){if(!this.activeLine)return;this.isNotRenderAllLines=!0;let[e,t,r,n,s,a]=this.activeLine,{associativeLineWidth:o,associativeLineColor:l,associativeLineDasharray:h,associativeLineActiveWidth:d,associativeLineActiveColor:c,associativeLineTextColor:f,associativeLineTextFontFamily:m,associativeLineTextFontSize:g}=this.getStyleConfig(n,s);e.stroke({width:o,color:l,dasharray:h||"6,4"}).fill({color:"none"}),t.stroke({width:d,color:c}).fill({color:"none"}),a.stroke({color:l}).fill({color:l}),r.find("text").forEach(x=>{x.fill({color:f}).css({"font-family":m,"font-size":g+"px"})}),this.controlLine1&&this.controlLine1.stroke({color:c}),this.controlLine2&&this.controlLine2.stroke({color:c}),this.controlPoint1&&this.controlPoint1.stroke({color:c}),this.controlPoint2&&this.controlPoint2.stroke({color:c}),this.updateTextPos(e,r)}setActiveLine({path:e,clickPath:t,markerPath:r,text:n,node:s,toNode:a,startPoint:o,endPoint:l,controlPoints:h}){let{associativeLineActiveColor:d}=this.getStyleConfig(s,a);this.mindMap.execCommand("CLEAR_ACTIVE_NODE"),this.clearActiveLine(),this.activeLine=[e,t,n,s,a,r],t.stroke({color:d}),this.getText(s,a)||this.renderText(this.mindMap.opt.defaultAssociativeLineText,e,n,s,a),this.renderControls(o,l,h[0],h[1],s,a),this.mindMap.emit("associative_line_click",e,t,s,a),this.front()}removeAllLines(){this.lineList.forEach(e=>{e[0].remove(),e[1].remove(),e[2].remove()}),this.lineList=[]}createLineFromActiveNode(){if(this.mindMap.renderer.activeNodeList.length<=0)return;let e=this.mindMap.renderer.activeNodeList[0];this.createLine(e)}createLine(e){let{associativeLineWidth:t,associativeLineColor:r,associativeLineDasharray:n}=this.getStyleConfig(e);if(this.isCreatingLine||!e)return;this.front(),this.isCreatingLine=!0,this.creatingStartNode=e,this.creatingLine=this.associativeLineDraw.path(),this.creatingLine.stroke({width:t,color:r,dasharray:n||"6,4"}).fill({color:"none"});let s=null,a=this.createMarker(o=>{s=o});s.stroke({color:r}).fill({color:r}),this.creatingLine.marker("end",a)}cancelCreateLine(){this.isCreatingLine=!1,this.creatingStartNode=null,this.creatingLine.remove(),this.creatingLine=null,this.overlapNode=null,this.back()}onMousemove(e){this.onControlPointMousemove(e),this.updateCreatingLine(e)}updateCreatingLine(e){if(!this.isCreatingLine)return;let{x:t,y:r}=this.getTransformedEventPos(e),n=kr(this.creatingStartNode),s=t>n.x?-10:10,a=k6(n.x,n.y,t+s,r);this.creatingLine.plot(a),this.checkOverlapNode(t,r)}getTransformedEventPos(e){let{x:t,y:r}=this.mindMap.toPos(e.clientX,e.clientY),{scaleX:n,scaleY:s,translateX:a,translateY:o}=this.mindMap.draw.transform();return{x:(t-a)/n,y:(r-o)/s}}getNodePos(e){let{scaleX:t,scaleY:r,translateX:n,translateY:s}=this.mindMap.draw.transform(),{left:a,top:o,width:l,height:h}=e,d=a*t+n,c=o*r+s;return{left:a,top:o,translateLeft:d,translateTop:c,width:l,height:h}}checkOverlapNode(e,t){this.overlapNode=null,Zt(this.mindMap.renderer.root,r=>{if(r.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",r,!1),r.uid===this.creatingStartNode.uid||this.overlapNode)return;let{left:n,top:s,width:a,height:o}=r,l=n+a,h=s+o;e>=n&&e<=l&&t>=s&&t<=h&&(this.overlapNode=r)}),this.overlapNode&&!this.overlapNode.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",this.overlapNode,!0)}completeCreateLine(e){if(this.creatingStartNode.uid===e.uid)return;let{beforeAssociativeLineConnection:t}=this.mindMap.opt,r=!1;typeof t=="function"&&(r=t(e)),!r&&(this.addLine(this.creatingStartNode,e),this.overlapNode&&this.overlapNode.getData("isActive")&&this.mindMap.execCommand("SET_NODE_ACTIVE",this.overlapNode,!1),this.cancelCreateLine())}addLine(e,t){if(!e||!t)return;let r=t.getData("uid");r||(r=vo(),this.mindMap.execCommand("SET_NODE_DATA",t,{uid:r}));let n=e.getData("associativeLineTargets")||[];if(n.some(f=>f===r))return;n.push(r);let[a,o]=Ju(e,t),l=Qo(a.x,a.y,o.x,o.y),{associativeLineInitPointsPosition:h}=this.mindMap.opt;if(h){let{from:f,to:m}=h;f&&(a.dir=f),m&&(o.dir=m)}let d=e.getData("associativeLineTargetControlOffsets")||[];d[n.length-1]=[{x:l[0].x-a.x,y:l[0].y-a.y},{x:l[1].x-o.x,y:l[1].y-o.y}];let c=e.getData("associativeLinePoint")||[];c[n.length-1]={startPoint:a,endPoint:o},this.mindMap.execCommand("SET_NODE_DATA",e,{associativeLineTargets:n,associativeLineTargetControlOffsets:d,associativeLinePoint:c})}removeLine(){if(!this.activeLine)return;let[,,,e,t]=this.activeLine;this.removeControls();let{associativeLineTargets:r,associativeLinePoint:n,associativeLineTargetControlOffsets:s,associativeLineText:a,associativeLineStyle:o}=e.getData();n=n||[];let l=Zo(e,t),h={};a&&Object.keys(a).forEach(c=>{c!==t.getData("uid")&&(h[c]=a[c])});let d={};o&&Object.keys(o).forEach(c=>{c!==t.getData("uid")&&(d[c]=o[c])}),this.mindMap.execCommand("SET_NODE_DATA",e,{associativeLineTargets:r.filter((c,f)=>f!==l),associativeLinePoint:n.filter((c,f)=>f!==l),associativeLineTargetControlOffsets:s?s.filter((c,f)=>f!==l):[],associativeLineText:h,associativeLineStyle:d})}clearActiveLine(){if(this.activeLine){let[,e,t,r,n]=this.activeLine;e.stroke({color:"transparent"}),this.hideEditTextBox(),this.getText(r,n)||t.clear(),this.activeLine=null,this.removeControls(),this.back(),this.mindMap.emit("associative_line_deactivate")}}onNodeDragging(){this.isNodeDragging||(this.isNodeDragging=!0,this.lineList.forEach(e=>{e[0].hide(),e[1].hide(),e[2].hide()}),this.hideControls())}onNodeDragend(){this.isNodeDragging&&(this.lineList.forEach(e=>{e[0].show(),e[1].show(),e[2].show()}),this.showControls(),this.isNodeDragging=!1)}front(){this.mindMap.opt.associativeLineIsAlwaysAboveNode||this.associativeLineDraw.front()}back(){this.mindMap.opt.associativeLineIsAlwaysAboveNode||(this.associativeLineDraw.back(),this.associativeLineDraw.forward())}beforePluginRemove(){this.mindMap.deleteEditNodeClass(n1),this.unBindEvent()}beforePluginDestroy(){this.mindMap.deleteEditNodeClass(n1),this.unBindEvent()}};uh.instanceName="associativeLine";hS=uh});var D6={};tt(D6,{default:()=>dS});var fh,dS,O6=T(()=>{pe();rh();Ge();fh=class{constructor({mindMap:e}){this.mindMap=e,this.isSearching=!1,this.searchText="",this.matchNodeList=[],this.currentIndex=-1,this.notResetSearchText=!1,this.isJumpNext=!1,this.bindEvent()}bindEvent(){this.onDataChange=this.onDataChange.bind(this),this.onModeChange=this.onModeChange.bind(this),this.mindMap.on("data_change",this.onDataChange),this.mindMap.on("mode_change",this.onModeChange)}unBindEvent(){this.mindMap.off("data_change",this.onDataChange),this.mindMap.off("mode_change",this.onModeChange)}onDataChange(){if(this.isJumpNext){this.isJumpNext=!1,this.search(this.searchText);return}if(this.notResetSearchText){this.notResetSearchText=!1;return}this.searchText=""}onModeChange(e){!(e===C.MODE.READONLY)&&this.isSearching&&this.matchNodeList[this.currentIndex]&&this.matchNodeList[this.currentIndex].closeHighlight()}search(e,t=()=>{}){if(Kt(e))return this.endSearch();e=String(e),this.isSearching=!0,this.searchText===e?this.searchNext(t):(this.searchText=e,this.doSearch(),this.searchNext(t)),this.emitEvent()}updateMatchNodeList(e){this.matchNodeList=e,this.mindMap.emit("search_match_node_list_change",e)}endSearch(){this.isSearching&&(this.mindMap.opt.readonly&&this.matchNodeList[this.currentIndex]&&this.matchNodeList[this.currentIndex].closeHighlight(),this.searchText="",this.updateMatchNodeList([]),this.currentIndex=-1,this.notResetSearchText=!1,this.isSearching=!1,this.emitEvent())}doSearch(){this.clearHighlightOnReadonly(),this.updateMatchNodeList([]),this.currentIndex=-1;let{isOnlySearchCurrentRenderNodes:e}=this.mindMap.opt,t=e?this.mindMap.renderer.root:this.mindMap.renderer.renderTree;if(!t)return;let r=[];Zt(t,n=>{let{richText:s,text:a,generalization:o}=e?n.getData():n.data;s&&(a=aa(a)),a.includes(this.searchText)&&r.push(n),us({generalization:o}).forEach(h=>{let{richText:d,text:c,uid:f}=h;e&&!this.mindMap.renderer.findNodeByUid(f)||(d&&(c=aa(c)),c.includes(this.searchText)&&r.push({data:h}))})}),this.updateMatchNodeList(r)}isNodeInstance(e){return e instanceof ca}searchNext(e,t){if(!this.isSearching||this.matchNodeList.length<=0)return;t!==void 0&&Number.isInteger(t)&&t>=0&&t{this.isNodeInstance(n)||(this.matchNodeList[this.currentIndex]=o,this.updateMatchNodeList(this.matchNodeList)),e(),r&&o.highlight(),a&&(this.notResetSearchText=!1)})}clearHighlightOnReadonly(){let{readonly:e}=this.mindMap.opt;e&&this.matchNodeList.forEach(t=>{this.isNodeInstance(t)&&t.closeHighlight()})}jump(e,t=()=>{}){this.searchNext(t,e)}replace(e,t=!1){if(e==null||!this.isSearching||this.matchNodeList.length<=0)return;this.isJumpNext=t,e=String(e);let r=this.matchNodeList[this.currentIndex];if(!r)return;let n=e.includes(this.searchText),s=this.getReplacedText(r,this.searchText,e);if(this.notResetSearchText=!0,r.setText(s,r.getData("richText")),n){this.updateMatchNodeList(this.matchNodeList);return}let a=this.matchNodeList.filter(o=>r!==o);this.updateMatchNodeList(a),this.currentIndex>this.matchNodeList.length-1?this.currentIndex=-1:this.currentIndex--,this.emitEvent()}replaceAll(e){if(e==null||!this.isSearching||this.matchNodeList.length<=0)return;e=String(e);let t=e.includes(this.searchText);this.notResetSearchText=!0,this.matchNodeList.forEach(r=>{let n=this.getReplacedText(r,this.searchText,e);if(this.isNodeInstance(r)){let s={text:n};this.mindMap.renderer.setNodeDataRender(r,s,!0)}else r.data.text=n}),this.mindMap.render(),this.mindMap.command.addHistory(),t?this.updateMatchNodeList(this.matchNodeList):this.endSearch()}getReplacedText(e,t,r){let{richText:n,text:s}=this.isNodeInstance(e)?e.getData():e.data;return n?O2(s,t,r):s.replace(new RegExp(t,"g"),r)}emitEvent(){this.mindMap.emit("search_info_change",{currentIndex:this.currentIndex,total:this.matchNodeList.length})}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};fh.instanceName="search";dS=fh});var B6,P6,F6=T(()=>{pe();B6=i=>{i=ds(i);let e={},t={};i.forEach(n=>{let s=n.parent;if(s){let a=s.uid;t[a]=s;let o=n.getIndexInBrothers(),l={node:n,index:o};e[a]?e[a].find(h=>h.index===l.index)||e[a].push(l):e[a]=[l]}});let r=[];return Object.keys(e).forEach(n=>{let s=e[n],a=t[n];if(s.length>1){let o=s.map(f=>f.index).sort((f,m)=>f-m),l=o[0],h=o[o.length-1],d=-1,c=-1;for(let f=l;f<=h;f++)o.includes(f)?(d===-1&&(d=f),c=f):(d!==-1&&c!==-1&&r.push({node:a,range:[d,c]}),d=-1,c=-1);d!==-1&&c!==-1&&r.push({node:a,range:[d,c]})}else r.push({node:a,range:[s[0].index,s[0].index]})}),r},P6=i=>{let e=i.children;if(!e||e.length<=0)return;let t=[],r={};return e.forEach((n,s)=>{let a=n.getData("outerFrame");if(!a)return;let o=a.groupId;o?(r[o]||(r[o]=[]),r[o].push({node:n,index:s})):t.push({nodeList:[n],range:[s,s]})}),Object.keys(r).forEach(n=>{let s=r[n];t.push({nodeList:s.map(a=>a.node),range:[s[0].index,s[s.length-1].index]})}),t}});function uS(i,e,t){let r=this.draw.group(),n=()=>{(!this.activeOuterFrame||this.activeOuterFrame.el!==i)&&this.setActiveOuterFrame(i,e,t,r)};return r.click(s=>{s.stopPropagation(),n()}),r.on("dblclick",s=>{s.stopPropagation(),n(),this.showEditTextBox(r)}),r}function fS(i){this.mindMap.emit("before_show_text_edit"),this.mindMap.keyCommand.addShortcut("Enter",()=>{this.hideEditTextBox()}),this.textEditNode||(this.textEditNode=document.createElement("div"),this.textEditNode.className=cS,this.textEditNode.style.cssText=` position: fixed; box-sizing: border-box; background-color: #fff; box-shadow: 0 0 20px rgba(0,0,0,.5); outline: none; word-break: break-all; - `,this.textEditNode.setAttribute("contenteditable",!0),this.textEditNode.addEventListener("keyup",g=>{g.stopPropagation()}),this.textEditNode.addEventListener("click",g=>{g.stopPropagation()}),(this.mindMap.opt.customInnerElsAppendTo||document.body).appendChild(this.textEditNode));let{node:e,range:t}=this.activeOuterFrame,r=this.getStyle(this.getNodeRangeFirstNode(e,t)),[n,s,a,o]=r.textFillPadding,{defaultOuterFrameText:l,nodeTextEditZIndex:h}=this.mindMap.opt,d=this.mindMap.view.scale,c=this.getText(this.getNodeRangeFirstNode(e,t)),f=(c||l).split(/\n/gim);this.textEditNode.style.padding=`${n}px ${s}px ${a}px ${o}px`,this.textEditNode.style.fontFamily=r.fontFamily,this.textEditNode.style.fontSize=r.fontSize*d+"px",this.textEditNode.style.fontWeight=r.fontWeight,this.textEditNode.style.fontStyle=r.fontStyle,this.textEditNode.style.lineHeight=f.length>1?r.lineHeight:"normal",this.textEditNode.style.zIndex=h,this.textEditNode.innerHTML=f.join("
    "),this.textEditNode.style.display="block",this.updateTextEditBoxPos(i),this.setIsShowTextEdit(!0),c===""||c===l?la(this.textEditNode):oa(this.textEditNode)}function jE(i){this.showTextEdit=i,i?this.mindMap.keyCommand.stopCheckInSvg():this.mindMap.keyCommand.recoveryCheckInSvg()}function GE(){if(!this.textEditNode)return;(this.mindMap.opt.customInnerElsAppendTo||document.body).removeChild(this.textEditNode)}function VE(){this.hideEditTextBox()}function WE(i){let e=i.node.getBoundingClientRect();this.textEditNode&&(this.textEditNode.style.minWidth=`${e.width}px`,this.textEditNode.style.minHeight=`${e.height}px`,this.textEditNode.style.left=`${e.left}px`,this.textEditNode.style.top=`${e.top}px`)}function YE(){if(!this.showTextEdit)return;let{el:i,textNode:e,node:t,range:r}=this.activeOuterFrame,n=na(this.textEditNode.innerHTML);n=n===this.mindMap.opt.defaultOuterFrameText?"":n,this.updateActiveOuterFrame({text:n}),this.textEditNode.style.display="none",this.textEditNode.innerHTML="",this.setIsShowTextEdit(!1),this.renderText(n,i,e,t,r),this.mindMap.emit("hide_text_edit")}function XE(i,e,t,r,n){if(!i)return;t.clear();let s=new Ve;t.add(s);let a=this.getStyle(this.getNodeRangeFirstNode(r,n)),[o,l,h,d]=a.textFillPadding,c=i.replace(/\n$/g,"").split(/\n/gim),f=new _e;c.forEach((A,C)=>{A===""&&(A="\uFEFF");let I=new Ce().text(A);I.y(a.fontSize*a.lineHeight*C),this.styleText(I,a),f.add(I)}),t.add(f);let{width:m,height:g}=t.bbox(),x=m+o+h,y=g+l+d;s.size(x,y).x(0).dy(0),this.styleTextShape(s,a);let b=0;switch(a.textAlign){case"left":b=e.x();break;case"center":b=e.x()+e.width()/2-x/2;break;case"right":b=e.x()+e.width()-x;break;default:break}let S=e.y()-y;s.x(b),s.y(S),f.x(b+o),f.y(S+l)}function KE(i,e){i.fill({color:e.textFill}).radius(e.textFillRadius)}function ZE(i,e){i.fill({color:e.color}).css({"font-family":e.fontFamily,"font-size":e.fontSize+"px","font-weight":e.fontWeight,"font-style":e.fontStyle})}function QE(i){let e=i.getData("outerFrame");return e&&e.text?e.text:""}var HE,Yu,S6=T(()=>{yt();pe();HE="outer-frame-text-edit-warp";Yu={getText:QE,createText:UE,styleTextShape:KE,styleText:ZE,onScale:VE,showEditTextBox:$E,setIsShowTextEdit:jE,removeTextEditEl:GE,hideEditTextBox:YE,updateTextEditBoxPos:WE,renderText:XE}});var A6={};tt(A6,{default:()=>JE});var Ku,Xu,Wo,JE,k6=T(()=>{pe();E6();S6();Ku={radius:5,strokeWidth:2,strokeColor:"#0984e3",strokeDasharray:"5,5",fill:"rgba(9,132,227,0.05)",fontSize:14,fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",fontWeight:"normal",fontStyle:"normal",color:"#fff",lineHeight:1.2,textFill:"#0984e3",textFillRadius:5,textFillPadding:[5,5,5,5],textAlign:"left"},Xu="outer-frame-text-edit-warp",Wo=class{constructor(e={}){this.mindMap=e.mindMap,this.draw=null,this.createDrawContainer(),this.isNotRenderOuterFrames=!1,this.textNodeList=[],this.outerFrameElList=[],this.activeOuterFrame=null,this.textEditNode=null,this.showTextEdit=!1,Object.keys(Yu).forEach(t=>{this[t]=Yu[t].bind(this)}),this.mindMap.addEditNodeClass(Xu),this.bindEvent()}createDrawContainer(){this.draw=this.mindMap.draw.group(),this.draw.addClass("smm-outer-frame-container"),this.draw.back(),this.draw.forward()}bindEvent(){this.renderOuterFrames=this.renderOuterFrames.bind(this),this.mindMap.on("node_tree_render_end",this.renderOuterFrames),this.mindMap.on("data_change",this.renderOuterFrames),this.clearActiveOuterFrame=this.clearActiveOuterFrame.bind(this),this.mindMap.on("draw_click",this.clearActiveOuterFrame),this.mindMap.on("node_click",this.clearActiveOuterFrame),this.mindMap.on("scale",this.onScale),this.onBeforeDestroy=this.onBeforeDestroy.bind(this),this.mindMap.on("beforeDestroy",this.onBeforeDestroy),this.addOuterFrame=this.addOuterFrame.bind(this),this.mindMap.command.add("ADD_OUTER_FRAME",this.addOuterFrame),this.removeActiveOuterFrame=this.removeActiveOuterFrame.bind(this),this.mindMap.keyCommand.addShortcut("Del|Backspace",this.removeActiveOuterFrame)}unBindEvent(){this.mindMap.off("node_tree_render_end",this.renderOuterFrames),this.mindMap.off("data_change",this.renderOuterFrames),this.mindMap.off("draw_click",this.clearActiveOuterFrame),this.mindMap.off("node_click",this.clearActiveOuterFrame),this.mindMap.off("scale",this.onScale),this.mindMap.off("beforeDestroy",this.onBeforeDestroy),this.mindMap.command.remove("ADD_OUTER_FRAME",this.addOuterFrame),this.mindMap.keyCommand.removeShortcut("Del|Backspace",this.removeActiveOuterFrame)}onBeforeDestroy(){this.hideEditTextBox(),this.removeTextEditEl()}addOuterFrame(e,t={}){e=Ot(e);let r=this.mindMap.renderer.activeNodeList;if(r.length<=0&&e.length<=0)return;let n=e.length>0?e:r;n=n.filter(a=>!a.isRoot&&!a.isGeneralization),T6(n).forEach(({node:a,range:o})=>{let l=a.children.slice(o[0],o[1]+1),h=wt();l.forEach(d=>{let c=d.getData("outerFrame");c?c={...c,...t,groupId:h}:c={...t,groupId:h},this.mindMap.execCommand("SET_NODE_DATA",d,{outerFrame:c})})})}getActiveOuterFrame(){return this.activeOuterFrame?{...this.activeOuterFrame}:null}removeActiveOuterFrame(){if(!this.activeOuterFrame)return;let{node:e,range:t}=this.activeOuterFrame;this.getRangeNodeList(e,t).forEach(r=>{this.mindMap.execCommand("SET_NODE_DATA",r,{outerFrame:null})}),this.mindMap.emit("outer_frame_delete")}removeActiveOuterFrameText(){this.updateActiveOuterFrame({text:""})}updateActiveOuterFrame(e={}){if(!this.activeOuterFrame)return;this.isNotRenderOuterFrames=!0;let{el:t,node:r,range:n}=this.activeOuterFrame,s="";this.getRangeNodeList(r,n).forEach(a=>{let l={...a.getData("outerFrame"),...e};s=l.strokeDasharray,this.mindMap.execCommand("SET_NODE_DATA",a,{outerFrame:l})}),t.cacheStyle={dasharray:s},this.updateOuterFrameStyle()}updateOuterFrameStyle(){let{el:e,node:t,range:r,textNode:n}=this.activeOuterFrame,s=this.getNodeRangeFirstNode(t,r),a=this.getStyle(s);this.styleOuterFrame(e,{...a,strokeDasharray:"none"});let o=this.getText(s);this.renderText(o,e,n,t,r)}getRangeNodeList(e,t){return e.children.slice(t[0],t[1]+1).filter(r=>r.getData("outerFrame"))}getNodeRangeFirstNode(e,t){return e.children[t[0]]}renderOuterFrames(){if(this.isNotRenderOuterFrames){this.isNotRenderOuterFrames=!1;return}this.clearActiveOuterFrame(),this.clearTextNodes(),this.clearOuterFrameElList();let e=this.mindMap.renderer.root;if(!e)return;let t=this.mindMap.draw.transform(),{outerFramePaddingX:r,outerFramePaddingY:n}=this.mindMap.opt;de(e,null,s=>{if(!s)return;let a=N6(s);a&&a.length>0&&a.forEach(({nodeList:o,range:l})=>{if(l[0]===-1||l[1]===-1)return;let{left:h,top:d,width:c,height:f}=D2(o);if(!Number.isFinite(h)||!Number.isFinite(d)||!Number.isFinite(c)||!Number.isFinite(f))return;let m=this.createOuterFrameEl((h-r-this.mindMap.elRect.left-t.translateX)/t.scaleX,(d-n-this.mindMap.elRect.top-t.translateY)/t.scaleY,(c+r*2)/t.scaleX,(f+n*2)/t.scaleY,this.getStyle(o[0])),g=this.createText(m,s,l);this.textNodeList.push(g),this.renderText(this.getText(o[0]),m,g,s,l),m.on("click",x=>{x.stopPropagation(),this.setActiveOuterFrame(m,s,l,g)})})},()=>{},!0,0)}setActiveOuterFrame(e,t,r,n){this.mindMap.execCommand("CLEAR_ACTIVE_NODE"),this.clearActiveOuterFrame(),this.activeOuterFrame={el:e,node:t,range:r,textNode:n},e.stroke({dasharray:"none"}),this.getText(this.getNodeRangeFirstNode(t,r))||this.renderText(this.mindMap.opt.defaultOuterFrameText,e,n,t,r),this.mindMap.emit("outer_frame_active",e,t,r)}clearActiveOuterFrame(){if(!this.activeOuterFrame)return;let{el:e,textNode:t,node:r,range:n}=this.activeOuterFrame;e.stroke({dasharray:e.cacheStyle.dasharray||Ku.strokeDasharray}),this.hideEditTextBox(),this.getText(this.getNodeRangeFirstNode(r,n))||t.clear(),this.activeOuterFrame=null,this.mindMap.emit("outer_frame_deactivate")}getStyle(e){return{...Ku,...e.getData("outerFrame")||{}}}createOuterFrameEl(e,t,r,n,s={}){let a=this.draw.rect().size(r,n).x(e).y(t);return this.styleOuterFrame(a,s),a.cacheStyle={dasharray:s.strokeDasharray},this.outerFrameElList.push(a),a}styleOuterFrame(e,t){e.radius(t.radius).stroke({width:t.strokeWidth,color:t.strokeColor,dasharray:t.strokeDasharray}).fill({color:t.fill})}clearTextNodes(){this.textNodeList.forEach(e=>{e.remove()})}clearOuterFrameElList(){this.outerFrameElList.forEach(e=>{e.remove()}),this.outerFrameElList=[],this.activeOuterFrame=null}beforePluginRemove(){this.mindMap.deleteEditNodeClass(Xu),this.unBindEvent()}beforePluginDestroy(){this.mindMap.deleteEditNodeClass(Xu),this.unBindEvent()}};Wo.instanceName="outerFrame";Wo.defaultStyle=Ku;JE=Wo});var C6={};tt(C6,{default:()=>eS});var ah,eS,_6=T(()=>{pe();$e();ah=class{constructor(e){this.mindMap=e.mindMap,this.scrollbarWrapSize={width:0,height:0},this.chartHeight=0,this.chartWidth=0,this.reset(),this.bindEvent()}reset(){this.currentScrollType="",this.isMousedown=!1,this.mousedownPos={x:0,y:0},this.mousedownScrollbarPos=0}bindEvent(){this.onMousemove=this.onMousemove.bind(this),this.onMouseup=this.onMouseup.bind(this),this.updateScrollbar=this.updateScrollbar.bind(this),this.updateScrollbar=Si(this.updateScrollbar,16,this),this.mindMap.on("mousemove",this.onMousemove),this.mindMap.on("mouseup",this.onMouseup),this.mindMap.on("node_tree_render_end",this.updateScrollbar),this.mindMap.on("view_data_change",this.updateScrollbar),this.mindMap.on("resize",this.updateScrollbar)}unBindEvent(){this.mindMap.off("mousemove",this.onMousemove),this.mindMap.off("mouseup",this.onMouseup),this.mindMap.off("node_tree_render_end",this.updateScrollbar),this.mindMap.off("view_data_change",this.updateScrollbar),this.mindMap.off("resize",this.updateScrollbar)}updateScrollbar(){if(this.isMousedown)return;let e=this.calculationScrollbar();this.emitEvent(e)}emitEvent(e){this.mindMap.emit("scrollbar_change",e)}setScrollBarWrapSize(e,t){this.scrollbarWrapSize.width=e,this.scrollbarWrapSize.height=t}calculationScrollbar(){let e=this.mindMap.draw.rbox(),t=this.mindMap.elRect;e.x-=t.left,e.y-=t.top;let r=this.mindMap.height,n=r/2,s=e.height+n*2;this.chartHeight=s;let a=e.y-n,o=Math.min(r/s*100,100),l=-a/s*100;l<0&&(l=0),l>100-o&&(l=100-o);let h=this.mindMap.width,d=h/2,c=e.width+d*2;this.chartWidth=c;let f=e.x-d,m=Math.min(h/c*100,100),g=-f/c*100;return g<0&&(g=0),g>100-m&&(g=100-m),{vertical:{top:l,height:o},horizontal:{left:g,width:m}}}onMousedown(e,t){e.preventDefault(),e.stopPropagation(),this.currentScrollType=t,this.isMousedown=!0,this.mousedownPos={x:e.clientX,y:e.clientY};let r=window.getComputedStyle(e.target);t===k.SCROLL_BAR_DIR.VERTICAL?this.mousedownScrollbarPos=Number.parseFloat(r.top):this.mousedownScrollbarPos=Number.parseFloat(r.left)}onMousemove(e){if(this.isMousedown)if(e.preventDefault(),e.stopPropagation(),this.currentScrollType===k.SCROLL_BAR_DIR.VERTICAL){let t=e.clientY-this.mousedownPos.y+this.mousedownScrollbarPos;this.updateMindMapView(k.SCROLL_BAR_DIR.VERTICAL,t)}else{let t=e.clientX-this.mousedownPos.x+this.mousedownScrollbarPos;this.updateMindMapView(k.SCROLL_BAR_DIR.HORIZONTAL,t)}}onMouseup(){this.isMousedown=!1,this.reset()}updateMindMapView(e,t){let r=this.calculationScrollbar(),n=this.mindMap.draw.transform(),s=this.mindMap.draw.rbox(),a=this.mindMap.renderer.root.group.rbox(),o=this.mindMap.renderer.layout.getRootCenterOffset(a.width,a.height);if(e===k.SCROLL_BAR_DIR.VERTICAL){let l=t;l<=0&&(l=0);let h=(100-r.vertical.height)/100*this.scrollbarWrapSize.height;l>=h&&(l=h);let d=l/this.scrollbarWrapSize.height*100,c=-d/100*this.chartHeight,f=a.y-s.y,m=this.mindMap.height/2,g=c+f-m*n.scaleY+m-o.y*n.scaleY+(this.mindMap.height-this.mindMap.initHeight)/2*n.scaleY;this.mindMap.view.translateYTo(g),this.emitEvent({horizontal:r.horizontal,vertical:{top:d,height:r.vertical.height}})}else{let l=t;l<=0&&(l=0);let h=(100-r.horizontal.width)/100*this.scrollbarWrapSize.width;l>=h&&(l=h);let d=l/this.scrollbarWrapSize.width*100,c=-d/100*this.chartWidth,f=a.x-s.x,m=this.mindMap.width/2,g=c+f-m*n.scaleX+m-o.x*n.scaleX+(this.mindMap.width-this.mindMap.initWidth)/2*n.scaleX;this.mindMap.view.translateXTo(g),this.emitEvent({vertical:r.vertical,horizontal:{left:d,width:r.horizontal.width}})}}onClick(e,t){let r=0;t===k.SCROLL_BAR_DIR.VERTICAL?r=e.clientY-e.currentTarget.getBoundingClientRect().top:r=e.clientX-e.currentTarget.getBoundingClientRect().left,this.updateMindMapView(t,r)}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};ah.instanceName="scrollbar";eS=ah});var L6={};tt(L6,{default:()=>tS});var oh,tS,I6=T(()=>{pe();oh=class{constructor(e){this.mindMap=e.mindMap,this.isMousedown=!1,this.mousedownPos={x:0,y:0},this.startViewPos={x:0,y:0},this.currentState=null}calculationMiniMap(e,t){let{svg:r,rect:n,origWidth:s,origHeight:a,scaleX:o,scaleY:l}=this.mindMap.getSvgData({ignoreWatermark:!0}),h=this.mindMap.elRect;n.x-=h.left,n.x2-=h.left,n.y-=h.top,n.y2-=h.top;let d=e/t,c=0,f=0;d>n.ratio?(f=t,c=n.ratio*f):(c=e,f=c/n.ratio);let m=c/n.width,g=(e-c)/2,x=(t-f)/2,y=n.width*o,b=n.height*l,S=(y-n.width)/2,A=(b-n.height)/2,C=n.x-S,I=n.x2+S,O=n.y-A,q=n.y2+A,P={left:0,top:0,right:0,bottom:0};P.left=Math.max(0,-C/y*c)+g,P.right=Math.max(0,(I-s)/y*c)+g,P.top=Math.max(0,-O/b*f)+x,P.bottom=Math.max(0,(q-a)/b*f)+x,P.top>x+f&&(P.top=x+f),P.left>g+c&&(P.left=g+c),Object.keys(P).forEach(ne=>{P[ne]=P[ne]+"px"}),this.removeNodeContent(r);let W=r.svg();return this.currentState={viewBoxStyle:{...P},miniMapBoxScale:m,miniMapBoxLeft:g,miniMapBoxTop:x},{getImgUrl:async ne=>{let re=await this.mindMap.doExport.fixSvgStrAndToBlob(W);ne(re)},svgHTML:W,viewBoxStyle:P,miniMapBoxScale:m,miniMapBoxLeft:g,miniMapBoxTop:x}}removeNodeContent(e){if(e.hasClass("smm-node")){let r=e.findOne(".smm-node-shape"),n=r.attr("fill");(Oo(n)||mu(n))&&r.attr("fill",$0(this.mindMap.themeConfig)),e.clear(),e.add(r);return}let t=e.children();t&&t.length>0&&t.forEach(r=>{this.removeNodeContent(r)})}onMousedown(e){this.isMousedown=!0,this.mousedownPos={x:e.clientX,y:e.clientY};let t=this.mindMap.view.getTransformData();this.startViewPos={x:t.state.x,y:t.state.y}}onMousemove(e,t=5){if(!this.isMousedown||this.isViewBoxMousedown)return;let r=e.clientX-this.mousedownPos.x,n=e.clientY-this.mousedownPos.y;this.mindMap.view.translateXTo(r*t+this.startViewPos.x),this.mindMap.view.translateYTo(n*t+this.startViewPos.y)}onMouseup(){this.isMousedown=!1,this.isViewBoxMousedown=!1}onViewBoxMousedown(e){this.isViewBoxMousedown=!0,this.mousedownPos={x:e.clientX,y:e.clientY};let t=this.mindMap.view.getTransformData();this.startViewPos={x:t.state.x,y:t.state.y}}onViewBoxMousemove(e){if(!this.isViewBoxMousedown||!this.currentState||this.isMousedown)return;let t=e.clientX-this.mousedownPos.x,r=e.clientY-this.mousedownPos.y,{viewBoxStyle:n,miniMapBoxScale:s,miniMapBoxLeft:a,miniMapBoxTop:o}=this.currentState,l=Math.max(a,Number.parseFloat(n.left)+t),h=Math.max(a,Number.parseFloat(n.right)-t),d=Math.max(o,Number.parseFloat(n.top)+r),c=Math.max(o,Number.parseFloat(n.bottom)-r);this.mindMap.emit("mini_map_view_box_position_change",{left:l+"px",right:h+"px",top:d+"px",bottom:c+"px"}),this.mindMap.view.translateXTo(-t/s+this.startViewPos.x),this.mindMap.view.translateYTo(-r/s+this.startViewPos.y)}};oh.instanceName="miniMap";tS=oh});var z6={};tt(z6,{default:()=>iS});var lh,iS,R6=T(()=>{pe();lh=class{constructor({mindMap:e}){this.mindMap=e,this.isInPainter=!1,this.painterNode=null,this.bindEvent()}bindEvent(){this.painterOneNode=this.painterOneNode.bind(this),this.onEndPainter=this.onEndPainter.bind(this),this.mindMap.on("node_click",this.painterOneNode),this.mindMap.on("draw_click",this.onEndPainter)}unBindEvent(){this.mindMap.off("node_click",this.painterOneNode),this.mindMap.off("draw_click",this.onEndPainter)}startPainter(){if(this.mindMap.opt.readonly)return;let e=this.mindMap.renderer.activeNodeList;e.length<=0||(this.painterNode=e[0],this.isInPainter=!0,this.mindMap.emit("painter_start"))}endPainter(){this.painterNode=null,this.isInPainter=!1}onEndPainter(){this.isInPainter&&(this.endPainter(),this.mindMap.emit("painter_end"))}painterOneNode(e){if(!e||!this.isInPainter||!this.painterNode||!e||e.uid===this.painterNode.uid)return;let t={};this.mindMap.opt.onlyPainterNodeCustomStyles||(t={...this.painterNode.effectiveStyles});let r=this.painterNode.getData();Object.keys(r).forEach(n=>{hs(n)&&(t[n]=r[n])}),this.mindMap.renderer._handleRemoveCustomStyles(e.getData()),e.setStyles(t)}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};lh.instanceName="painter";iS=lh});function lS(i){return String(i).replace(oS,e=>aS[e])}function uS(i){if(i.default)return i.default;var e=i.type,t=Array.isArray(e)?e[0]:e;if(typeof t!="string")return t.enum[0];switch(t){case"boolean":return!1;case"string":return"";case"number":return 0;case"object":return{}}}function vS(i){for(var e=0;e=n[0]&&i<=n[1])return t.name}return null}function f4(i){for(var e=0;e=wh[e]&&i<=wh[e+1])return!0;return!1}function _S(i,e){dr[i]=e}function T1(i,e,t){if(!dr[e])throw new Error("Font metrics not found for font: "+e+".");var r=i.charCodeAt(0),n=dr[e][r];if(!n&&i[0]in O6&&(r=O6[i[0]].charCodeAt(0),n=dr[e][r]),!n&&t==="text"&&f4(r)&&(n=dr[e][77]),n)return{depth:n[0],height:n[1],italic:n[2],skew:n[3],width:n[4]}}function LS(i){var e;if(i>=5?e=0:i>=3?e=1:e=2,!Zu[e]){var t=Zu[e]={cssEmPerMu:hh.quad[e]/18};for(var r in hh)hh.hasOwnProperty(r)&&(t[r]=hh[r][e])}return Zu[e]}function F6(i){if(i instanceof ei)return i;throw new Error("Expected symbolNode but got "+String(i)+".")}function OS(i){if(i instanceof ps)return i;throw new Error("Expected span but got "+String(i)+".")}function u(i,e,t,r,n,s){Oe[i][n]={font:e,group:t,replace:r},s&&r&&(Oe[i][r]=Oe[i][n])}function Z(i){for(var{type:e,names:t,props:r,handler:n,htmlBuilder:s,mathmlBuilder:a}=i,o={type:e,numArgs:r.numArgs,argTypes:r.argTypes,allowedInArgument:!!r.allowedInArgument,allowedInText:!!r.allowedInText,allowedInMath:r.allowedInMath===void 0?!0:r.allowedInMath,numOptionalArgs:r.numOptionalArgs||0,infix:!!r.infix,primitive:!!r.primitive,handler:n},l=0;l0&&(s.push(gh(a,e)),a=[]),s.push(r[o]));a.length>0&&s.push(gh(a,e));var h;t?(h=gh(ct(t,e,!0)),h.classes=["tag"],s.push(h)):n&&s.push(n);var d=zr(["katex-html"],s);if(d.setAttribute("aria-hidden","true"),h){var c=h.children[0];c.style.height=V(d.height+d.depth),d.depth&&(c.style.verticalAlign=V(-d.depth))}return d}function E4(i){return new ms(i)}function e1(i){if(!i)return!1;if(i.type==="mi"&&i.children.length===1){var e=i.children[0];return e instanceof ki&&e.text==="."}else if(i.type==="mo"&&i.children.length===1&&i.getAttribute("separator")==="true"&&i.getAttribute("lspace")==="0em"&&i.getAttribute("rspace")==="0em"){var t=i.children[0];return t instanceof ki&&t.text===","}else return!1}function j6(i,e,t,r,n){var s=ti(i,t),a;s.length===1&&s[0]instanceof Bt&&["mrow","mtable"].includes(s[0].type)?a=s[0]:a=new $.MathNode("mrow",s);var o=new $.MathNode("annotation",[new $.TextNode(e)]);o.setAttribute("encoding","application/x-tex");var l=new $.MathNode("semantics",[a,o]),h=new $.MathNode("math",[l]);h.setAttribute("xmlns","http://www.w3.org/1998/Math/MathML"),r&&h.setAttribute("display","block");var d=n?"katex":"katex-mathml";return z.makeSpan([d],[h])}function me(i,e){if(!i||i.type!==e)throw new Error("Expected node of type "+e+", but got "+(i?"node of type "+i.type:String(i)));return i}function A1(i){var e=Ih(i);if(!e)throw new Error("Expected node of symbol group type, but got "+(i?"node of type "+i.type:String(i)));return e}function Ih(i){return i&&(i.type==="atom"||PS.hasOwnProperty(i.type))?i:null}function C4(i,e){var t=ct(i.body,e,!0);return mA([i.mclass],t,e)}function _4(i,e){var t,r=ti(i.body,e);return i.mclass==="minner"?t=new $.MathNode("mpadded",r):i.mclass==="mord"?i.isCharacterBox?(t=r[0],t.type="mi"):t=new $.MathNode("mi",r):(i.isCharacterBox?(t=r[0],t.type="mo"):t=new $.MathNode("mo",r),i.mclass==="mbin"?(t.attributes.lspace="0.22em",t.attributes.rspace="0.22em"):i.mclass==="mpunct"?(t.attributes.lspace="0em",t.attributes.rspace="0.17em"):i.mclass==="mopen"||i.mclass==="mclose"?(t.attributes.lspace="0em",t.attributes.rspace="0em"):i.mclass==="minner"&&(t.attributes.lspace="0.0556em",t.attributes.width="+0.1111em")),t}function xA(i,e,t){var r=pA[i];switch(r){case"\\\\cdrightarrow":case"\\\\cdleftarrow":return t.callFunction(r,[e[0]],[e[1]]);case"\\uparrow":case"\\downarrow":{var n=t.callFunction("\\\\cdleft",[e[0]],[]),s={type:"atom",text:r,mode:"math",family:"rel"},a=t.callFunction("\\Big",[s],[]),o=t.callFunction("\\\\cdright",[e[1]],[]),l={type:"ordgroup",mode:"math",body:[n,a,o]};return t.callFunction("\\\\cdparent",[l],[])}case"\\\\cdlongequal":return t.callFunction("\\\\cdlongequal",[],[]);case"\\Vert":{var h={type:"textord",text:"\\Vert",mode:"math"};return t.callFunction("\\Big",[h],[])}default:return{type:"textord",text:" ",mode:"math"}}}function yA(i){var e=[];for(i.gullet.beginGroup(),i.gullet.macros.set("\\cr","\\\\\\relax"),i.gullet.beginGroup();;){e.push(i.parseExpression(!1,"\\\\")),i.gullet.endGroup(),i.gullet.beginGroup();var t=i.fetch().text;if(t==="&"||t==="\\\\")i.consume();else if(t==="\\end"){e[e.length-1].length===0&&e.pop();break}else throw new G("Expected \\\\ or \\cr or \\end",i.nextToken)}for(var r=[],n=[r],s=0;s-1))if("<>AV".indexOf(h)>-1)for(var c=0;c<2;c++){for(var f=!0,m=l+1;mAV=|." after @',a[l]);var g=xA(h,d,i),x={type:"styling",body:[g],mode:"math",style:"display"};r.push(x),o=G6()}s%2===0?r.push(o):r.shift(),r=[],n.push(r)}i.gullet.endGroup(),i.gullet.endGroup();var y=new Array(n[0].length).fill({type:"align",align:"c",pregap:.25,postgap:.25});return{type:"array",mode:"math",body:n,arraystretch:1,addJot:!0,rowGaps:[null],cols:y,colSeparationType:"CD",hLinesBeforeRow:new Array(n.length+1).fill([])}}function Rh(i,e){var t=Ih(i);if(t&&LA.includes(t.text))return t;throw t?new G("Invalid delimiter '"+t.text+"' after '"+e.funcName+"'",i):new G("Invalid delimiter type '"+i.type+"'",i)}function Y6(i){if(!i.body)throw new Error("Bug: The leftright ParseNode wasn't fully parsed.")}function ur(i){for(var{type:e,names:t,props:r,handler:n,htmlBuilder:s,mathmlBuilder:a}=i,o={type:e,numArgs:r.numArgs||0,allowedInText:!1,numOptionalArgs:0,handler:n},l=0;l1||!d)&&x.pop(),b.length{Jt=class i{constructor(e,t,r){this.lexer=void 0,this.start=void 0,this.end=void 0,this.lexer=e,this.start=t,this.end=r}static range(e,t){return t?!e||!e.loc||!t.loc||e.loc.lexer!==t.loc.lexer?null:new i(e.loc.lexer,e.loc.start,t.loc.end):e&&e.loc}},ui=class i{constructor(e,t){this.text=void 0,this.loc=void 0,this.noexpand=void 0,this.treatAsRelax=void 0,this.text=e,this.loc=t}range(e,t){return new i(t,Jt.range(this,e))}},G=class i{constructor(e,t){this.name=void 0,this.position=void 0,this.length=void 0,this.rawMessage=void 0;var r="KaTeX parse error: "+e,n,s,a=t&&t.loc;if(a&&a.start<=a.end){var o=a.lexer.input;n=a.start,s=a.end,n===o.length?r+=" at end of input: ":r+=" at position "+(n+1)+": ";var l=o.slice(n,s).replace(/[^]/g,"$&\u0332"),h;n>15?h="\u2026"+o.slice(n-15,n):h=o.slice(0,n);var d;s+15":">","<":"<",'"':""","'":"'"},oS=/[&><"']/g;u4=function i(e){return e.type==="ordgroup"||e.type==="color"?e.body.length===1?i(e.body[0]):e:e.type==="font"?i(e.body):e},hS=function(e){var t=u4(e);return t.type==="mathord"||t.type==="textord"||t.type==="atom"},dS=function(e){if(!e)throw new Error("Expected non-null, but got "+String(e));return e},cS=function(e){var t=/^[\x00-\x20]*([^\\/#?]*?)(:|�*58|�*3a|&colon)/i.exec(e);return t?t[2]!==":"||!/^[a-zA-Z][a-zA-Z0-9+\-.]*$/.test(t[1])?null:t[1].toLowerCase():"_relative"},Ne={deflt:rS,escape:lS,hyphenate:sS,getBaseElem:u4,isCharacterBox:hS,protocolFromUrl:cS},bh={displayMode:{type:"boolean",description:"Render math in display mode, which puts the math in display style (so \\int and \\sum are large, for example), and centers the math on the page on its own line.",cli:"-d, --display-mode"},output:{type:{enum:["htmlAndMathml","html","mathml"]},description:"Determines the markup language of the output.",cli:"-F, --format "},leqno:{type:"boolean",description:"Render display math in leqno style (left-justified tags)."},fleqn:{type:"boolean",description:"Render display math flush left."},throwOnError:{type:"boolean",default:!0,cli:"-t, --no-throw-on-error",cliDescription:"Render errors (in the color given by --error-color) instead of throwing a ParseError exception when encountering an error."},errorColor:{type:"string",default:"#cc0000",cli:"-c, --error-color ",cliDescription:"A color string given in the format 'rgb' or 'rrggbb' (no #). This option determines the color of errors rendered by the -t option.",cliProcessor:i=>"#"+i},macros:{type:"object",cli:"-m, --macro ",cliDescription:"Define custom macro of the form '\\foo:expansion' (use multiple -m arguments for multiple macros).",cliDefault:[],cliProcessor:(i,e)=>(e.push(i),e)},minRuleThickness:{type:"number",description:"Specifies a minimum thickness, in ems, for fraction lines, `\\sqrt` top lines, `{array}` vertical lines, `\\hline`, `\\hdashline`, `\\underline`, `\\overline`, and the borders of `\\fbox`, `\\boxed`, and `\\fcolorbox`.",processor:i=>Math.max(0,i),cli:"--min-rule-thickness ",cliProcessor:parseFloat},colorIsTextColor:{type:"boolean",description:"Makes \\color behave like LaTeX's 2-argument \\textcolor, instead of LaTeX's one-argument \\color mode change.",cli:"-b, --color-is-text-color"},strict:{type:[{enum:["warn","ignore","error"]},"boolean","function"],description:"Turn on strict / LaTeX faithfulness mode, which throws an error if the input uses features that are not supported by LaTeX.",cli:"-S, --strict",cliDefault:!1},trust:{type:["boolean","function"],description:"Trust the input, enabling all HTML features such as \\url.",cli:"-T, --trust"},maxSize:{type:"number",default:1/0,description:"If non-zero, all user-specified sizes, e.g. in \\rule{500em}{500em}, will be capped to maxSize ems. Otherwise, elements and spaces can be arbitrarily large",processor:i=>Math.max(0,i),cli:"-s, --max-size ",cliProcessor:parseInt},maxExpand:{type:"number",default:1e3,description:"Limit the number of macro expansions to the specified number, to prevent e.g. infinite macro loops. If set to Infinity, the macro expander will try to fully expand as in LaTeX.",processor:i=>Math.max(0,i),cli:"-e, --max-expand ",cliProcessor:i=>i==="Infinity"?1/0:parseInt(i)},globalGroup:{type:"boolean",cli:!1}};Qo=class{constructor(e){this.displayMode=void 0,this.output=void 0,this.leqno=void 0,this.fleqn=void 0,this.throwOnError=void 0,this.errorColor=void 0,this.macros=void 0,this.minRuleThickness=void 0,this.colorIsTextColor=void 0,this.strict=void 0,this.trust=void 0,this.maxSize=void 0,this.maxExpand=void 0,this.globalGroup=void 0,e=e||{};for(var t in bh)if(bh.hasOwnProperty(t)){var r=bh[t];this[t]=e[t]!==void 0?r.processor?r.processor(e[t]):e[t]:uS(r)}}reportNonstrict(e,t,r){var n=this.strict;if(typeof n=="function"&&(n=n(e,t,r)),!(!n||n==="ignore")){if(n===!0||n==="error")throw new G("LaTeX-incompatible input and strict mode is set to 'error': "+(t+" ["+e+"]"),r);n==="warn"?typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+(t+" ["+e+"]")):typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to "+("unrecognized '"+n+"': "+t+" ["+e+"]"))}}useStrictBehavior(e,t,r){var n=this.strict;if(typeof n=="function")try{n=n(e,t,r)}catch{n="error"}return!n||n==="ignore"?!1:n===!0||n==="error"?!0:n==="warn"?(typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+(t+" ["+e+"]")),!1):(typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to "+("unrecognized '"+n+"': "+t+" ["+e+"]")),!1)}isTrusted(e){if(e.url&&!e.protocol){var t=Ne.protocolFromUrl(e.url);if(t==null)return!1;e.protocol=t}var r=typeof this.trust=="function"?this.trust(e):this.trust;return!!r}},lr=class{constructor(e,t,r){this.id=void 0,this.size=void 0,this.cramped=void 0,this.id=e,this.size=t,this.cramped=r}sup(){return hr[fS[this.id]]}sub(){return hr[mS[this.id]]}fracNum(){return hr[pS[this.id]]}fracDen(){return hr[gS[this.id]]}cramp(){return hr[xS[this.id]]}text(){return hr[yS[this.id]]}isTight(){return this.size>=2}},M1=0,Mh=1,ua=2,Lr=3,Jo=4,Ai=5,fa=6,Pt=7,hr=[new lr(M1,0,!1),new lr(Mh,0,!0),new lr(ua,1,!1),new lr(Lr,1,!0),new lr(Jo,2,!1),new lr(Ai,2,!0),new lr(fa,3,!1),new lr(Pt,3,!0)],fS=[Jo,Ai,Jo,Ai,fa,Pt,fa,Pt],mS=[Ai,Ai,Ai,Ai,Pt,Pt,Pt,Pt],pS=[ua,Lr,Jo,Ai,fa,Pt,fa,Pt],gS=[Lr,Lr,Ai,Ai,Pt,Pt,Pt,Pt],xS=[Mh,Mh,Lr,Lr,Ai,Ai,Pt,Pt],yS=[M1,Mh,ua,Lr,ua,Lr,ua,Lr],ae={DISPLAY:hr[M1],TEXT:hr[ua],SCRIPT:hr[Jo],SCRIPTSCRIPT:hr[fa]},d1=[{name:"latin",blocks:[[256,591],[768,879]]},{name:"cyrillic",blocks:[[1024,1279]]},{name:"armenian",blocks:[[1328,1423]]},{name:"brahmic",blocks:[[2304,4255]]},{name:"georgian",blocks:[[4256,4351]]},{name:"cjk",blocks:[[12288,12543],[19968,40879],[65280,65376]]},{name:"hangul",blocks:[[44032,55215]]}];wh=[];d1.forEach(i=>i.blocks.forEach(e=>wh.push(...e)));ca=80,bS=function(e,t){return"M95,"+(622+e+t)+` + `,this.textEditNode.setAttribute("contenteditable",!0),this.textEditNode.addEventListener("keyup",g=>{g.stopPropagation()}),this.textEditNode.addEventListener("click",g=>{g.stopPropagation()}),(this.mindMap.opt.customInnerElsAppendTo||document.body).appendChild(this.textEditNode));let{node:e,range:t}=this.activeOuterFrame,r=this.getStyle(this.getNodeRangeFirstNode(e,t)),[n,s,a,o]=r.textFillPadding,{defaultOuterFrameText:l,nodeTextEditZIndex:h}=this.mindMap.opt,d=this.mindMap.view.scale,c=this.getText(this.getNodeRangeFirstNode(e,t)),f=(c||l).split(/\n/gim);this.textEditNode.style.padding=`${n}px ${s}px ${a}px ${o}px`,this.textEditNode.style.fontFamily=r.fontFamily,this.textEditNode.style.fontSize=r.fontSize*d+"px",this.textEditNode.style.fontWeight=r.fontWeight,this.textEditNode.style.fontStyle=r.fontStyle,this.textEditNode.style.lineHeight=f.length>1?r.lineHeight:"normal",this.textEditNode.style.zIndex=h,this.textEditNode.innerHTML=f.join("
    "),this.textEditNode.style.display="block",this.updateTextEditBoxPos(i),this.setIsShowTextEdit(!0),c===""||c===l?ha(this.textEditNode):la(this.textEditNode)}function mS(i){this.showTextEdit=i,i?this.mindMap.keyCommand.stopCheckInSvg():this.mindMap.keyCommand.recoveryCheckInSvg()}function pS(){if(!this.textEditNode)return;(this.mindMap.opt.customInnerElsAppendTo||document.body).removeChild(this.textEditNode)}function gS(){this.hideEditTextBox()}function xS(i){let e=i.node.getBoundingClientRect();this.textEditNode&&(this.textEditNode.style.minWidth=`${e.width}px`,this.textEditNode.style.minHeight=`${e.height}px`,this.textEditNode.style.left=`${e.left}px`,this.textEditNode.style.top=`${e.top}px`)}function yS(){if(!this.showTextEdit)return;let{el:i,textNode:e,node:t,range:r}=this.activeOuterFrame,n=sa(this.textEditNode.innerHTML);n=n===this.mindMap.opt.defaultOuterFrameText?"":n,this.updateActiveOuterFrame({text:n}),this.textEditNode.style.display="none",this.textEditNode.innerHTML="",this.setIsShowTextEdit(!1),this.renderText(n,i,e,t,r),this.mindMap.emit("hide_text_edit")}function vS(i,e,t,r,n){if(!i)return;t.clear();let s=new Ve;t.add(s);let a=this.getStyle(this.getNodeRangeFirstNode(r,n)),[o,l,h,d]=a.textFillPadding,c=i.replace(/\n$/g,"").split(/\n/gim),f=new Le;c.forEach((k,L)=>{k===""&&(k="\uFEFF");let I=new _e().text(k);I.y(a.fontSize*a.lineHeight*L),this.styleText(I,a),f.add(I)}),t.add(f);let{width:m,height:g}=t.bbox(),x=m+o+h,y=g+l+d;s.size(x,y).x(0).dy(0),this.styleTextShape(s,a);let b=0;switch(a.textAlign){case"left":b=e.x();break;case"center":b=e.x()+e.width()/2-x/2;break;case"right":b=e.x()+e.width()-x;break;default:break}let E=e.y()-y;s.x(b),s.y(E),f.x(b+o),f.y(E+l)}function bS(i,e){i.fill({color:e.textFill}).radius(e.textFillRadius)}function wS(i,e){i.fill({color:e.color}).css({"font-family":e.fontFamily,"font-size":e.fontSize+"px","font-weight":e.fontWeight,"font-style":e.fontStyle})}function MS(i){let e=i.getData("outerFrame");return e&&e.text?e.text:""}var cS,s1,q6=T(()=>{wt();pe();cS="outer-frame-text-edit-warp";s1={getText:MS,createText:uS,styleTextShape:bS,styleText:wS,onScale:gS,showEditTextBox:fS,setIsShowTextEdit:mS,removeTextEditEl:pS,hideEditTextBox:yS,updateTextEditBoxPos:xS,renderText:vS}});var H6={};tt(H6,{default:()=>TS});var o1,a1,Jo,TS,U6=T(()=>{pe();F6();q6();o1={radius:5,strokeWidth:2,strokeColor:"#0984e3",strokeDasharray:"5,5",fill:"rgba(9,132,227,0.05)",fontSize:14,fontFamily:"\u5FAE\u8F6F\u96C5\u9ED1, Microsoft YaHei",fontWeight:"normal",fontStyle:"normal",color:"#fff",lineHeight:1.2,textFill:"#0984e3",textFillRadius:5,textFillPadding:[5,5,5,5],textAlign:"left"},a1="outer-frame-text-edit-warp",Jo=class{constructor(e={}){this.mindMap=e.mindMap,this.draw=null,this.createDrawContainer(),this.isNotRenderOuterFrames=!1,this.textNodeList=[],this.outerFrameElList=[],this.activeOuterFrame=null,this.textEditNode=null,this.showTextEdit=!1,Object.keys(s1).forEach(t=>{this[t]=s1[t].bind(this)}),this.mindMap.addEditNodeClass(a1),this.bindEvent()}createDrawContainer(){this.draw=this.mindMap.draw.group(),this.draw.addClass("smm-outer-frame-container"),this.draw.back(),this.draw.forward()}bindEvent(){this.renderOuterFrames=this.renderOuterFrames.bind(this),this.mindMap.on("node_tree_render_end",this.renderOuterFrames),this.mindMap.on("data_change",this.renderOuterFrames),this.clearActiveOuterFrame=this.clearActiveOuterFrame.bind(this),this.mindMap.on("draw_click",this.clearActiveOuterFrame),this.mindMap.on("node_click",this.clearActiveOuterFrame),this.mindMap.on("scale",this.onScale),this.onBeforeDestroy=this.onBeforeDestroy.bind(this),this.mindMap.on("beforeDestroy",this.onBeforeDestroy),this.addOuterFrame=this.addOuterFrame.bind(this),this.mindMap.command.add("ADD_OUTER_FRAME",this.addOuterFrame),this.removeActiveOuterFrame=this.removeActiveOuterFrame.bind(this),this.mindMap.keyCommand.addShortcut("Del|Backspace",this.removeActiveOuterFrame)}unBindEvent(){this.mindMap.off("node_tree_render_end",this.renderOuterFrames),this.mindMap.off("data_change",this.renderOuterFrames),this.mindMap.off("draw_click",this.clearActiveOuterFrame),this.mindMap.off("node_click",this.clearActiveOuterFrame),this.mindMap.off("scale",this.onScale),this.mindMap.off("beforeDestroy",this.onBeforeDestroy),this.mindMap.command.remove("ADD_OUTER_FRAME",this.addOuterFrame),this.mindMap.keyCommand.removeShortcut("Del|Backspace",this.removeActiveOuterFrame)}onBeforeDestroy(){this.hideEditTextBox(),this.removeTextEditEl()}addOuterFrame(e,t={}){e=Ot(e);let r=this.mindMap.renderer.activeNodeList;if(r.length<=0&&e.length<=0)return;let n=e.length>0?e:r;n=n.filter(a=>!a.isRoot&&!a.isGeneralization),B6(n).forEach(({node:a,range:o})=>{let l=a.children.slice(o[0],o[1]+1),h=Tt();l.forEach(d=>{let c=d.getData("outerFrame");c?c={...c,...t,groupId:h}:c={...t,groupId:h},this.mindMap.execCommand("SET_NODE_DATA",d,{outerFrame:c})})})}getActiveOuterFrame(){return this.activeOuterFrame?{...this.activeOuterFrame}:null}removeActiveOuterFrame(){if(!this.activeOuterFrame)return;let{node:e,range:t}=this.activeOuterFrame;this.getRangeNodeList(e,t).forEach(r=>{this.mindMap.execCommand("SET_NODE_DATA",r,{outerFrame:null})}),this.mindMap.emit("outer_frame_delete")}removeActiveOuterFrameText(){this.updateActiveOuterFrame({text:""})}updateActiveOuterFrame(e={}){if(!this.activeOuterFrame)return;this.isNotRenderOuterFrames=!0;let{el:t,node:r,range:n}=this.activeOuterFrame,s="";this.getRangeNodeList(r,n).forEach(a=>{let l={...a.getData("outerFrame"),...e};s=l.strokeDasharray,this.mindMap.execCommand("SET_NODE_DATA",a,{outerFrame:l})}),t.cacheStyle={dasharray:s},this.updateOuterFrameStyle()}updateOuterFrameStyle(){let{el:e,node:t,range:r,textNode:n}=this.activeOuterFrame,s=this.getNodeRangeFirstNode(t,r),a=this.getStyle(s);this.styleOuterFrame(e,{...a,strokeDasharray:"none"});let o=this.getText(s);this.renderText(o,e,n,t,r)}getRangeNodeList(e,t){return e.children.slice(t[0],t[1]+1).filter(r=>r.getData("outerFrame"))}getNodeRangeFirstNode(e,t){return e.children[t[0]]}renderOuterFrames(){if(this.isNotRenderOuterFrames){this.isNotRenderOuterFrames=!1;return}this.clearActiveOuterFrame(),this.clearTextNodes(),this.clearOuterFrameElList();let e=this.mindMap.renderer.root;if(!e)return;let t=this.mindMap.draw.transform(),{outerFramePaddingX:r,outerFramePaddingY:n}=this.mindMap.opt;ue(e,null,s=>{if(!s)return;let a=P6(s);a&&a.length>0&&a.forEach(({nodeList:o,range:l})=>{if(l[0]===-1||l[1]===-1)return;let{left:h,top:d,width:c,height:f}=X2(o);if(!Number.isFinite(h)||!Number.isFinite(d)||!Number.isFinite(c)||!Number.isFinite(f))return;let m=this.createOuterFrameEl((h-r-this.mindMap.elRect.left-t.translateX)/t.scaleX,(d-n-this.mindMap.elRect.top-t.translateY)/t.scaleY,(c+r*2)/t.scaleX,(f+n*2)/t.scaleY,this.getStyle(o[0])),g=this.createText(m,s,l);this.textNodeList.push(g),this.renderText(this.getText(o[0]),m,g,s,l),m.on("click",x=>{x.stopPropagation(),this.setActiveOuterFrame(m,s,l,g)})})},()=>{},!0,0)}setActiveOuterFrame(e,t,r,n){this.mindMap.execCommand("CLEAR_ACTIVE_NODE"),this.clearActiveOuterFrame(),this.activeOuterFrame={el:e,node:t,range:r,textNode:n},e.stroke({dasharray:"none"}),this.getText(this.getNodeRangeFirstNode(t,r))||this.renderText(this.mindMap.opt.defaultOuterFrameText,e,n,t,r),this.mindMap.emit("outer_frame_active",e,t,r)}clearActiveOuterFrame(){if(!this.activeOuterFrame)return;let{el:e,textNode:t,node:r,range:n}=this.activeOuterFrame;e.stroke({dasharray:e.cacheStyle.dasharray||o1.strokeDasharray}),this.hideEditTextBox(),this.getText(this.getNodeRangeFirstNode(r,n))||t.clear(),this.activeOuterFrame=null,this.mindMap.emit("outer_frame_deactivate")}getStyle(e){return{...o1,...e.getData("outerFrame")||{}}}createOuterFrameEl(e,t,r,n,s={}){let a=this.draw.rect().size(r,n).x(e).y(t);return this.styleOuterFrame(a,s),a.cacheStyle={dasharray:s.strokeDasharray},this.outerFrameElList.push(a),a}styleOuterFrame(e,t){e.radius(t.radius).stroke({width:t.strokeWidth,color:t.strokeColor,dasharray:t.strokeDasharray}).fill({color:t.fill})}clearTextNodes(){this.textNodeList.forEach(e=>{e.remove()})}clearOuterFrameElList(){this.outerFrameElList.forEach(e=>{e.remove()}),this.outerFrameElList=[],this.activeOuterFrame=null}beforePluginRemove(){this.mindMap.deleteEditNodeClass(a1),this.unBindEvent()}beforePluginDestroy(){this.mindMap.deleteEditNodeClass(a1),this.unBindEvent()}};Jo.instanceName="outerFrame";Jo.defaultStyle=o1;TS=Jo});var $6={};tt($6,{default:()=>NS});var mh,NS,j6=T(()=>{pe();Ge();mh=class{constructor(e){this.mindMap=e.mindMap,this.scrollbarWrapSize={width:0,height:0},this.chartHeight=0,this.chartWidth=0,this.reset(),this.bindEvent()}reset(){this.currentScrollType="",this.isMousedown=!1,this.mousedownPos={x:0,y:0},this.mousedownScrollbarPos=0}bindEvent(){this.onMousemove=this.onMousemove.bind(this),this.onMouseup=this.onMouseup.bind(this),this.updateScrollbar=this.updateScrollbar.bind(this),this.updateScrollbar=Ai(this.updateScrollbar,16,this),this.mindMap.on("mousemove",this.onMousemove),this.mindMap.on("mouseup",this.onMouseup),this.mindMap.on("node_tree_render_end",this.updateScrollbar),this.mindMap.on("view_data_change",this.updateScrollbar),this.mindMap.on("resize",this.updateScrollbar)}unBindEvent(){this.mindMap.off("mousemove",this.onMousemove),this.mindMap.off("mouseup",this.onMouseup),this.mindMap.off("node_tree_render_end",this.updateScrollbar),this.mindMap.off("view_data_change",this.updateScrollbar),this.mindMap.off("resize",this.updateScrollbar)}updateScrollbar(){if(this.isMousedown)return;let e=this.calculationScrollbar();this.emitEvent(e)}emitEvent(e){this.mindMap.emit("scrollbar_change",e)}setScrollBarWrapSize(e,t){this.scrollbarWrapSize.width=e,this.scrollbarWrapSize.height=t}calculationScrollbar(){let e=this.mindMap.draw.rbox(),t=this.mindMap.elRect;e.x-=t.left,e.y-=t.top;let r=this.mindMap.height,n=r/2,s=e.height+n*2;this.chartHeight=s;let a=e.y-n,o=Math.min(r/s*100,100),l=-a/s*100;l<0&&(l=0),l>100-o&&(l=100-o);let h=this.mindMap.width,d=h/2,c=e.width+d*2;this.chartWidth=c;let f=e.x-d,m=Math.min(h/c*100,100),g=-f/c*100;return g<0&&(g=0),g>100-m&&(g=100-m),{vertical:{top:l,height:o},horizontal:{left:g,width:m}}}onMousedown(e,t){e.preventDefault(),e.stopPropagation(),this.currentScrollType=t,this.isMousedown=!0,this.mousedownPos={x:e.clientX,y:e.clientY};let r=window.getComputedStyle(e.target);t===C.SCROLL_BAR_DIR.VERTICAL?this.mousedownScrollbarPos=Number.parseFloat(r.top):this.mousedownScrollbarPos=Number.parseFloat(r.left)}onMousemove(e){if(this.isMousedown)if(e.preventDefault(),e.stopPropagation(),this.currentScrollType===C.SCROLL_BAR_DIR.VERTICAL){let t=e.clientY-this.mousedownPos.y+this.mousedownScrollbarPos;this.updateMindMapView(C.SCROLL_BAR_DIR.VERTICAL,t)}else{let t=e.clientX-this.mousedownPos.x+this.mousedownScrollbarPos;this.updateMindMapView(C.SCROLL_BAR_DIR.HORIZONTAL,t)}}onMouseup(){this.isMousedown=!1,this.reset()}updateMindMapView(e,t){let r=this.calculationScrollbar(),n=this.mindMap.draw.transform(),s=this.mindMap.draw.rbox(),a=this.mindMap.renderer.root.group.rbox(),o=this.mindMap.renderer.layout.getRootCenterOffset(a.width,a.height);if(e===C.SCROLL_BAR_DIR.VERTICAL){let l=t;l<=0&&(l=0);let h=(100-r.vertical.height)/100*this.scrollbarWrapSize.height;l>=h&&(l=h);let d=l/this.scrollbarWrapSize.height*100,c=-d/100*this.chartHeight,f=a.y-s.y,m=this.mindMap.height/2,g=c+f-m*n.scaleY+m-o.y*n.scaleY+(this.mindMap.height-this.mindMap.initHeight)/2*n.scaleY;this.mindMap.view.translateYTo(g),this.emitEvent({horizontal:r.horizontal,vertical:{top:d,height:r.vertical.height}})}else{let l=t;l<=0&&(l=0);let h=(100-r.horizontal.width)/100*this.scrollbarWrapSize.width;l>=h&&(l=h);let d=l/this.scrollbarWrapSize.width*100,c=-d/100*this.chartWidth,f=a.x-s.x,m=this.mindMap.width/2,g=c+f-m*n.scaleX+m-o.x*n.scaleX+(this.mindMap.width-this.mindMap.initWidth)/2*n.scaleX;this.mindMap.view.translateXTo(g),this.emitEvent({vertical:r.vertical,horizontal:{left:d,width:r.horizontal.width}})}}onClick(e,t){let r=0;t===C.SCROLL_BAR_DIR.VERTICAL?r=e.clientY-e.currentTarget.getBoundingClientRect().top:r=e.clientX-e.currentTarget.getBoundingClientRect().left,this.updateMindMapView(t,r)}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};mh.instanceName="scrollbar";NS=mh});var G6={};tt(G6,{default:()=>ES});var ph,ES,W6=T(()=>{pe();ph=class{constructor(e){this.mindMap=e.mindMap,this.isMousedown=!1,this.mousedownPos={x:0,y:0},this.startViewPos={x:0,y:0},this.currentState=null}calculationMiniMap(e,t){let{svg:r,rect:n,origWidth:s,origHeight:a,scaleX:o,scaleY:l}=this.mindMap.getSvgData({ignoreWatermark:!0}),h=this.mindMap.elRect;n.x-=h.left,n.x2-=h.left,n.y-=h.top,n.y2-=h.top;let d=e/t,c=0,f=0;d>n.ratio?(f=t,c=n.ratio*f):(c=e,f=c/n.ratio);let m=c/n.width,g=(e-c)/2,x=(t-f)/2,y=n.width*o,b=n.height*l,E=(y-n.width)/2,k=(b-n.height)/2,L=n.x-E,I=n.x2+E,O=n.y-k,q=n.y2+k,F={left:0,top:0,right:0,bottom:0};F.left=Math.max(0,-L/y*c)+g,F.right=Math.max(0,(I-s)/y*c)+g,F.top=Math.max(0,-O/b*f)+x,F.bottom=Math.max(0,(q-a)/b*f)+x,F.top>x+f&&(F.top=x+f),F.left>g+c&&(F.left=g+c),Object.keys(F).forEach(ne=>{F[ne]=F[ne]+"px"}),this.removeNodeContent(r);let W=r.svg();return this.currentState={viewBoxStyle:{...F},miniMapBoxScale:m,miniMapBoxLeft:g,miniMapBoxTop:x},{getImgUrl:async ne=>{let re=await this.mindMap.doExport.fixSvgStrAndToBlob(W);ne(re)},svgHTML:W,viewBoxStyle:F,miniMapBoxScale:m,miniMapBoxLeft:g,miniMapBoxTop:x}}removeNodeContent(e){if(e.hasClass("smm-node")){let r=e.findOne(".smm-node-shape"),n=r.attr("fill");(Uo(n)||Eu(n))&&r.attr("fill",Z0(this.mindMap.themeConfig)),e.clear(),e.add(r);return}let t=e.children();t&&t.length>0&&t.forEach(r=>{this.removeNodeContent(r)})}onMousedown(e){this.isMousedown=!0,this.mousedownPos={x:e.clientX,y:e.clientY};let t=this.mindMap.view.getTransformData();this.startViewPos={x:t.state.x,y:t.state.y}}onMousemove(e,t=5){if(!this.isMousedown||this.isViewBoxMousedown)return;let r=e.clientX-this.mousedownPos.x,n=e.clientY-this.mousedownPos.y;this.mindMap.view.translateXTo(r*t+this.startViewPos.x),this.mindMap.view.translateYTo(n*t+this.startViewPos.y)}onMouseup(){this.isMousedown=!1,this.isViewBoxMousedown=!1}onViewBoxMousedown(e){this.isViewBoxMousedown=!0,this.mousedownPos={x:e.clientX,y:e.clientY};let t=this.mindMap.view.getTransformData();this.startViewPos={x:t.state.x,y:t.state.y}}onViewBoxMousemove(e){if(!this.isViewBoxMousedown||!this.currentState||this.isMousedown)return;let t=e.clientX-this.mousedownPos.x,r=e.clientY-this.mousedownPos.y,{viewBoxStyle:n,miniMapBoxScale:s,miniMapBoxLeft:a,miniMapBoxTop:o}=this.currentState,l=Math.max(a,Number.parseFloat(n.left)+t),h=Math.max(a,Number.parseFloat(n.right)-t),d=Math.max(o,Number.parseFloat(n.top)+r),c=Math.max(o,Number.parseFloat(n.bottom)-r);this.mindMap.emit("mini_map_view_box_position_change",{left:l+"px",right:h+"px",top:d+"px",bottom:c+"px"}),this.mindMap.view.translateXTo(-t/s+this.startViewPos.x),this.mindMap.view.translateYTo(-r/s+this.startViewPos.y)}};ph.instanceName="miniMap";ES=ph});var V6={};tt(V6,{default:()=>SS});var gh,SS,Y6=T(()=>{pe();gh=class{constructor({mindMap:e}){this.mindMap=e,this.isInPainter=!1,this.painterNode=null,this.bindEvent()}bindEvent(){this.painterOneNode=this.painterOneNode.bind(this),this.onEndPainter=this.onEndPainter.bind(this),this.mindMap.on("node_click",this.painterOneNode),this.mindMap.on("draw_click",this.onEndPainter)}unBindEvent(){this.mindMap.off("node_click",this.painterOneNode),this.mindMap.off("draw_click",this.onEndPainter)}startPainter(){if(this.mindMap.opt.readonly)return;let e=this.mindMap.renderer.activeNodeList;e.length<=0||(this.painterNode=e[0],this.isInPainter=!0,this.mindMap.emit("painter_start"))}endPainter(){this.painterNode=null,this.isInPainter=!1}onEndPainter(){this.isInPainter&&(this.endPainter(),this.mindMap.emit("painter_end"))}painterOneNode(e){if(!e||!this.isInPainter||!this.painterNode||!e||e.uid===this.painterNode.uid)return;let t={};this.mindMap.opt.onlyPainterNodeCustomStyles||(t={...this.painterNode.effectiveStyles});let r=this.painterNode.getData();Object.keys(r).forEach(n=>{hs(n)&&(t[n]=r[n])}),this.mindMap.renderer._handleRemoveCustomStyles(e.getData()),e.setStyles(t)}beforePluginRemove(){this.unBindEvent()}beforePluginDestroy(){this.unBindEvent()}};gh.instanceName="painter";SS=gh});function IS(i){return String(i).replace(LS,e=>_S[e])}function OS(i){if(i.default)return i.default;var e=i.type,t=Array.isArray(e)?e[0]:e;if(typeof t!="string")return t.enum[0];switch(t){case"boolean":return!1;case"string":return"";case"number":return 0;case"object":return{}}}function $S(i){for(var e=0;e=n[0]&&i<=n[1])return t.name}return null}function A4(i){for(var e=0;e=Ch[e]&&i<=Ch[e+1])return!0;return!1}function eA(i,e){lr[i]=e}function R1(i,e,t){if(!lr[e])throw new Error("Font metrics not found for font: "+e+".");var r=i.charCodeAt(0),n=lr[e][r];if(!n&&i[0]in K6&&(r=K6[i[0]].charCodeAt(0),n=lr[e][r]),!n&&t==="text"&&A4(r)&&(n=lr[e][77]),n)return{depth:n[0],height:n[1],italic:n[2],skew:n[3],width:n[4]}}function tA(i){var e;if(i>=5?e=0:i>=3?e=1:e=2,!l1[e]){var t=l1[e]={cssEmPerMu:xh.quad[e]/18};for(var r in xh)xh.hasOwnProperty(r)&&(t[r]=xh[r][e])}return l1[e]}function J6(i){if(i instanceof ii)return i;throw new Error("Expected symbolNode but got "+String(i)+".")}function aA(i){if(i instanceof ps)return i;throw new Error("Expected span but got "+String(i)+".")}function u(i,e,t,r,n,s){Be[i][n]={font:e,group:t,replace:r},s&&r&&(Be[i][r]=Be[i][n])}function Z(i){for(var{type:e,names:t,props:r,handler:n,htmlBuilder:s,mathmlBuilder:a}=i,o={type:e,numArgs:r.numArgs,argTypes:r.argTypes,allowedInArgument:!!r.allowedInArgument,allowedInText:!!r.allowedInText,allowedInMath:r.allowedInMath===void 0?!0:r.allowedInMath,numOptionalArgs:r.numOptionalArgs||0,infix:!!r.infix,primitive:!!r.primitive,handler:n},l=0;l0&&(s.push(Nh(a,e)),a=[]),s.push(r[o]));a.length>0&&s.push(Nh(a,e));var h;t?(h=Nh(mt(t,e,!0)),h.classes=["tag"],s.push(h)):n&&s.push(n);var d=zr(["katex-html"],s);if(d.setAttribute("aria-hidden","true"),h){var c=h.children[0];c.style.height=V(d.height+d.depth),d.depth&&(c.style.verticalAlign=V(-d.depth))}return d}function F4(i){return new ms(i)}function c1(i){if(!i)return!1;if(i.type==="mi"&&i.children.length===1){var e=i.children[0];return e instanceof Ci&&e.text==="."}else if(i.type==="mo"&&i.children.length===1&&i.getAttribute("separator")==="true"&&i.getAttribute("lspace")==="0em"&&i.getAttribute("rspace")==="0em"){var t=i.children[0];return t instanceof Ci&&t.text===","}else return!1}function n4(i,e,t,r,n){var s=ri(i,t),a;s.length===1&&s[0]instanceof Bt&&["mrow","mtable"].includes(s[0].type)?a=s[0]:a=new $.MathNode("mrow",s);var o=new $.MathNode("annotation",[new $.TextNode(e)]);o.setAttribute("encoding","application/x-tex");var l=new $.MathNode("semantics",[a,o]),h=new $.MathNode("math",[l]);h.setAttribute("xmlns","http://www.w3.org/1998/Math/MathML"),r&&h.setAttribute("display","block");var d=n?"katex":"katex-mathml";return z.makeSpan([d],[h])}function me(i,e){if(!i||i.type!==e)throw new Error("Expected node of type "+e+", but got "+(i?"node of type "+i.type:String(i)));return i}function P1(i){var e=qh(i);if(!e)throw new Error("Expected node of symbol group type, but got "+(i?"node of type "+i.type:String(i)));return e}function qh(i){return i&&(i.type==="atom"||lA.hasOwnProperty(i.type))?i:null}function $4(i,e){var t=mt(i.body,e,!0);return PA([i.mclass],t,e)}function j4(i,e){var t,r=ri(i.body,e);return i.mclass==="minner"?t=new $.MathNode("mpadded",r):i.mclass==="mord"?i.isCharacterBox?(t=r[0],t.type="mi"):t=new $.MathNode("mi",r):(i.isCharacterBox?(t=r[0],t.type="mo"):t=new $.MathNode("mo",r),i.mclass==="mbin"?(t.attributes.lspace="0.22em",t.attributes.rspace="0.22em"):i.mclass==="mpunct"?(t.attributes.lspace="0em",t.attributes.rspace="0.17em"):i.mclass==="mopen"||i.mclass==="mclose"?(t.attributes.lspace="0em",t.attributes.rspace="0em"):i.mclass==="minner"&&(t.attributes.lspace="0.0556em",t.attributes.width="+0.1111em")),t}function HA(i,e,t){var r=FA[i];switch(r){case"\\\\cdrightarrow":case"\\\\cdleftarrow":return t.callFunction(r,[e[0]],[e[1]]);case"\\uparrow":case"\\downarrow":{var n=t.callFunction("\\\\cdleft",[e[0]],[]),s={type:"atom",text:r,mode:"math",family:"rel"},a=t.callFunction("\\Big",[s],[]),o=t.callFunction("\\\\cdright",[e[1]],[]),l={type:"ordgroup",mode:"math",body:[n,a,o]};return t.callFunction("\\\\cdparent",[l],[])}case"\\\\cdlongequal":return t.callFunction("\\\\cdlongequal",[],[]);case"\\Vert":{var h={type:"textord",text:"\\Vert",mode:"math"};return t.callFunction("\\Big",[h],[])}default:return{type:"textord",text:" ",mode:"math"}}}function UA(i){var e=[];for(i.gullet.beginGroup(),i.gullet.macros.set("\\cr","\\\\\\relax"),i.gullet.beginGroup();;){e.push(i.parseExpression(!1,"\\\\")),i.gullet.endGroup(),i.gullet.beginGroup();var t=i.fetch().text;if(t==="&"||t==="\\\\")i.consume();else if(t==="\\end"){e[e.length-1].length===0&&e.pop();break}else throw new G("Expected \\\\ or \\cr or \\end",i.nextToken)}for(var r=[],n=[r],s=0;s-1))if("<>AV".indexOf(h)>-1)for(var c=0;c<2;c++){for(var f=!0,m=l+1;mAV=|." after @',a[l]);var g=HA(h,d,i),x={type:"styling",body:[g],mode:"math",style:"display"};r.push(x),o=s4()}s%2===0?r.push(o):r.shift(),r=[],n.push(r)}i.gullet.endGroup(),i.gullet.endGroup();var y=new Array(n[0].length).fill({type:"align",align:"c",pregap:.25,postgap:.25});return{type:"array",mode:"math",body:n,arraystretch:1,addJot:!0,rowGaps:[null],cols:y,colSeparationType:"CD",hLinesBeforeRow:new Array(n.length+1).fill([])}}function Uh(i,e){var t=qh(i);if(t&&tk.includes(t.text))return t;throw t?new G("Invalid delimiter '"+t.text+"' after '"+e.funcName+"'",i):new G("Invalid delimiter type '"+i.type+"'",i)}function l4(i){if(!i.body)throw new Error("Bug: The leftright ParseNode wasn't fully parsed.")}function dr(i){for(var{type:e,names:t,props:r,handler:n,htmlBuilder:s,mathmlBuilder:a}=i,o={type:e,numArgs:r.numArgs||0,allowedInText:!1,numOptionalArgs:0,handler:n},l=0;l1||!d)&&x.pop(),b.length{ti=class i{constructor(e,t,r){this.lexer=void 0,this.start=void 0,this.end=void 0,this.lexer=e,this.start=t,this.end=r}static range(e,t){return t?!e||!e.loc||!t.loc||e.loc.lexer!==t.loc.lexer?null:new i(e.loc.lexer,e.loc.start,t.loc.end):e&&e.loc}},mi=class i{constructor(e,t){this.text=void 0,this.loc=void 0,this.noexpand=void 0,this.treatAsRelax=void 0,this.text=e,this.loc=t}range(e,t){return new i(t,ti.range(this,e))}},G=class i{constructor(e,t){this.name=void 0,this.position=void 0,this.length=void 0,this.rawMessage=void 0;var r="KaTeX parse error: "+e,n,s,a=t&&t.loc;if(a&&a.start<=a.end){var o=a.lexer.input;n=a.start,s=a.end,n===o.length?r+=" at end of input: ":r+=" at position "+(n+1)+": ";var l=o.slice(n,s).replace(/[^]/g,"$&\u0332"),h;n>15?h="\u2026"+o.slice(n-15,n):h=o.slice(0,n);var d;s+15":">","<":"<",'"':""","'":"'"},LS=/[&><"']/g;S4=function i(e){return e.type==="ordgroup"||e.type==="color"?e.body.length===1?i(e.body[0]):e:e.type==="font"?i(e.body):e},zS=function(e){var t=S4(e);return t.type==="mathord"||t.type==="textord"||t.type==="atom"},RS=function(e){if(!e)throw new Error("Expected non-null, but got "+String(e));return e},DS=function(e){var t=/^[\x00-\x20]*([^\\/#?]*?)(:|�*58|�*3a|&colon)/i.exec(e);return t?t[2]!==":"||!/^[a-zA-Z][a-zA-Z0-9+\-.]*$/.test(t[1])?null:t[1].toLowerCase():"_relative"},Ee={deflt:AS,escape:IS,hyphenate:CS,getBaseElem:S4,isCharacterBox:zS,protocolFromUrl:DS},kh={displayMode:{type:"boolean",description:"Render math in display mode, which puts the math in display style (so \\int and \\sum are large, for example), and centers the math on the page on its own line.",cli:"-d, --display-mode"},output:{type:{enum:["htmlAndMathml","html","mathml"]},description:"Determines the markup language of the output.",cli:"-F, --format "},leqno:{type:"boolean",description:"Render display math in leqno style (left-justified tags)."},fleqn:{type:"boolean",description:"Render display math flush left."},throwOnError:{type:"boolean",default:!0,cli:"-t, --no-throw-on-error",cliDescription:"Render errors (in the color given by --error-color) instead of throwing a ParseError exception when encountering an error."},errorColor:{type:"string",default:"#cc0000",cli:"-c, --error-color ",cliDescription:"A color string given in the format 'rgb' or 'rrggbb' (no #). This option determines the color of errors rendered by the -t option.",cliProcessor:i=>"#"+i},macros:{type:"object",cli:"-m, --macro ",cliDescription:"Define custom macro of the form '\\foo:expansion' (use multiple -m arguments for multiple macros).",cliDefault:[],cliProcessor:(i,e)=>(e.push(i),e)},minRuleThickness:{type:"number",description:"Specifies a minimum thickness, in ems, for fraction lines, `\\sqrt` top lines, `{array}` vertical lines, `\\hline`, `\\hdashline`, `\\underline`, `\\overline`, and the borders of `\\fbox`, `\\boxed`, and `\\fcolorbox`.",processor:i=>Math.max(0,i),cli:"--min-rule-thickness ",cliProcessor:parseFloat},colorIsTextColor:{type:"boolean",description:"Makes \\color behave like LaTeX's 2-argument \\textcolor, instead of LaTeX's one-argument \\color mode change.",cli:"-b, --color-is-text-color"},strict:{type:[{enum:["warn","ignore","error"]},"boolean","function"],description:"Turn on strict / LaTeX faithfulness mode, which throws an error if the input uses features that are not supported by LaTeX.",cli:"-S, --strict",cliDefault:!1},trust:{type:["boolean","function"],description:"Trust the input, enabling all HTML features such as \\url.",cli:"-T, --trust"},maxSize:{type:"number",default:1/0,description:"If non-zero, all user-specified sizes, e.g. in \\rule{500em}{500em}, will be capped to maxSize ems. Otherwise, elements and spaces can be arbitrarily large",processor:i=>Math.max(0,i),cli:"-s, --max-size ",cliProcessor:parseInt},maxExpand:{type:"number",default:1e3,description:"Limit the number of macro expansions to the specified number, to prevent e.g. infinite macro loops. If set to Infinity, the macro expander will try to fully expand as in LaTeX.",processor:i=>Math.max(0,i),cli:"-e, --max-expand ",cliProcessor:i=>i==="Infinity"?1/0:parseInt(i)},globalGroup:{type:"boolean",cli:!1}};nl=class{constructor(e){this.displayMode=void 0,this.output=void 0,this.leqno=void 0,this.fleqn=void 0,this.throwOnError=void 0,this.errorColor=void 0,this.macros=void 0,this.minRuleThickness=void 0,this.colorIsTextColor=void 0,this.strict=void 0,this.trust=void 0,this.maxSize=void 0,this.maxExpand=void 0,this.globalGroup=void 0,e=e||{};for(var t in kh)if(kh.hasOwnProperty(t)){var r=kh[t];this[t]=e[t]!==void 0?r.processor?r.processor(e[t]):e[t]:OS(r)}}reportNonstrict(e,t,r){var n=this.strict;if(typeof n=="function"&&(n=n(e,t,r)),!(!n||n==="ignore")){if(n===!0||n==="error")throw new G("LaTeX-incompatible input and strict mode is set to 'error': "+(t+" ["+e+"]"),r);n==="warn"?typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+(t+" ["+e+"]")):typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to "+("unrecognized '"+n+"': "+t+" ["+e+"]"))}}useStrictBehavior(e,t,r){var n=this.strict;if(typeof n=="function")try{n=n(e,t,r)}catch{n="error"}return!n||n==="ignore"?!1:n===!0||n==="error"?!0:n==="warn"?(typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+(t+" ["+e+"]")),!1):(typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to "+("unrecognized '"+n+"': "+t+" ["+e+"]")),!1)}isTrusted(e){if(e.url&&!e.protocol){var t=Ee.protocolFromUrl(e.url);if(t==null)return!1;e.protocol=t}var r=typeof this.trust=="function"?this.trust(e):this.trust;return!!r}},ar=class{constructor(e,t,r){this.id=void 0,this.size=void 0,this.cramped=void 0,this.id=e,this.size=t,this.cramped=r}sup(){return or[BS[this.id]]}sub(){return or[PS[this.id]]}fracNum(){return or[FS[this.id]]}fracDen(){return or[qS[this.id]]}cramp(){return or[HS[this.id]]}text(){return or[US[this.id]]}isTight(){return this.size>=2}},z1=0,_h=1,fa=2,Lr=3,sl=4,ki=5,ma=6,Pt=7,or=[new ar(z1,0,!1),new ar(_h,0,!0),new ar(fa,1,!1),new ar(Lr,1,!0),new ar(sl,2,!1),new ar(ki,2,!0),new ar(ma,3,!1),new ar(Pt,3,!0)],BS=[sl,ki,sl,ki,ma,Pt,ma,Pt],PS=[ki,ki,ki,ki,Pt,Pt,Pt,Pt],FS=[fa,Lr,sl,ki,ma,Pt,ma,Pt],qS=[Lr,Lr,ki,ki,Pt,Pt,Pt,Pt],HS=[_h,_h,Lr,Lr,ki,ki,Pt,Pt],US=[z1,_h,fa,Lr,fa,Lr,fa,Lr],ae={DISPLAY:or[z1],TEXT:or[fa],SCRIPT:or[sl],SCRIPTSCRIPT:or[ma]},w1=[{name:"latin",blocks:[[256,591],[768,879]]},{name:"cyrillic",blocks:[[1024,1279]]},{name:"armenian",blocks:[[1328,1423]]},{name:"brahmic",blocks:[[2304,4255]]},{name:"georgian",blocks:[[4256,4351]]},{name:"cjk",blocks:[[12288,12543],[19968,40879],[65280,65376]]},{name:"hangul",blocks:[[44032,55215]]}];Ch=[];w1.forEach(i=>i.blocks.forEach(e=>Ch.push(...e)));ua=80,jS=function(e,t){return"M95,"+(622+e+t)+` c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14 c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54 c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10 @@ -90,7 +90,7 @@ c5.3,-9.3,12,-14,20,-14 H400000v`+(40+e)+`H845.2724 s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7 c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z -M`+(834+e)+" "+t+"h400000v"+(40+e)+"h-400000z"},wS=function(e,t){return"M263,"+(601+e+t)+`c0.7,0,18,39.7,52,119 +M`+(834+e)+" "+t+"h400000v"+(40+e)+"h-400000z"},GS=function(e,t){return"M263,"+(601+e+t)+`c0.7,0,18,39.7,52,119 c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120 c340,-704.7,510.7,-1060.3,512,-1067 l`+e/2.084+" -"+e+` @@ -100,7 +100,7 @@ s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5, c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1 s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26 c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z -M`+(1001+e)+" "+t+"h400000v"+(40+e)+"h-400000z"},MS=function(e,t){return"M983 "+(10+e+t)+` +M`+(1001+e)+" "+t+"h400000v"+(40+e)+"h-400000z"},WS=function(e,t){return"M983 "+(10+e+t)+` l`+e/3.13+" -"+e+` c4,-6.7,10,-10,18,-10 H400000v`+(40+e)+` H1013.1s-83.4,268,-264.1,840c-180.7,572,-277,876.3,-289,913c-4.7,4.7,-12.7,7,-24,7 @@ -109,7 +109,7 @@ c-10,12,-21,25,-33,39s-32,39,-32,39c-6,-5.3,-15,-14,-27,-26s25,-30,25,-30 c26.7,-32.7,52,-63,76,-91s52,-60,52,-60s208,722,208,722 c56,-175.3,126.3,-397.3,211,-666c84.7,-268.7,153.8,-488.2,207.5,-658.5 c53.7,-170.3,84.5,-266.8,92.5,-289.5z -M`+(1001+e)+" "+t+"h400000v"+(40+e)+"h-400000z"},TS=function(e,t){return"M424,"+(2398+e+t)+` +M`+(1001+e)+" "+t+"h400000v"+(40+e)+"h-400000z"},VS=function(e,t){return"M424,"+(2398+e+t)+` c-1.3,-0.7,-38.5,-172,-111.5,-514c-73,-342,-109.8,-513.3,-110.5,-514 c0,-2,-10.7,14.3,-32,49c-4.7,7.3,-9.8,15.7,-15.5,25c-5.7,9.3,-9.8,16,-12.5,20 s-5,7,-5,7c-4,-3.3,-8.3,-7.7,-13,-13s-13,-13,-13,-13s76,-122,76,-122s77,-121,77,-121 @@ -119,18 +119,18 @@ v`+(40+e)+`H1014.6 s-87.3,378.7,-272.6,1166c-185.3,787.3,-279.3,1182.3,-282,1185 c-2,6,-10,9,-24,9 c-8,0,-12,-0.7,-12,-2z M`+(1001+e)+" "+t+` -h400000v`+(40+e)+"h-400000z"},NS=function(e,t){return"M473,"+(2713+e+t)+` +h400000v`+(40+e)+"h-400000z"},YS=function(e,t){return"M473,"+(2713+e+t)+` c339.3,-1799.3,509.3,-2700,510,-2702 l`+e/5.298+" -"+e+` c3.3,-7.3,9.3,-11,18,-11 H400000v`+(40+e)+`H1017.7 s-90.5,478,-276.2,1466c-185.7,988,-279.5,1483,-281.5,1485c-2,6,-10,9,-24,9 c-8,0,-12,-0.7,-12,-2c0,-1.3,-5.3,-32,-16,-92c-50.7,-293.3,-119.7,-693.3,-207,-1200 c0,-1.3,-5.3,8.7,-16,30c-10.7,21.3,-21.3,42.7,-32,64s-16,33,-16,33s-26,-26,-26,-26 s76,-153,76,-153s77,-151,77,-151c0.7,0.7,35.7,202,105,604c67.3,400.7,102,602.7,104, -606zM`+(1001+e)+" "+t+"h400000v"+(40+e)+"H1017.7z"},ES=function(e){var t=e/2;return"M400000 "+e+" H0 L"+t+" 0 l65 45 L145 "+(e-80)+" H400000z"},SS=function(e,t,r){var n=r-54-t-e;return"M702 "+(e+t)+"H400000"+(40+e)+` +606zM`+(1001+e)+" "+t+"h400000v"+(40+e)+"H1017.7z"},XS=function(e){var t=e/2;return"M400000 "+e+" H0 L"+t+" 0 l65 45 L145 "+(e-80)+" H400000z"},KS=function(e,t,r){var n=r-54-t-e;return"M702 "+(e+t)+"H400000"+(40+e)+` H742v`+n+`l-4 4-4 4c-.667.7 -2 1.5-4 2.5s-4.167 1.833-6.5 2.5-5.5 1-9.5 1 h-12l-28-84c-16.667-52-96.667 -294.333-240-727l-212 -643 -85 170 c-4-3.333-8.333-7.667-13 -13l-13-13l77-155 77-156c66 199.333 139 419.667 -219 661 l218 661zM702 `+t+"H400000v"+(40+e)+"H742z"},AS=function(e,t,r){t=1e3*t;var n="";switch(e){case"sqrtMain":n=bS(t,ca);break;case"sqrtSize1":n=wS(t,ca);break;case"sqrtSize2":n=MS(t,ca);break;case"sqrtSize3":n=TS(t,ca);break;case"sqrtSize4":n=NS(t,ca);break;case"sqrtTall":n=SS(t,ca,r)}return n},kS=function(e,t){switch(e){case"\u239C":return"M291 0 H417 V"+t+" H291z M291 0 H417 V"+t+" H291z";case"\u2223":return"M145 0 H188 V"+t+" H145z M145 0 H188 V"+t+" H145z";case"\u2225":return"M145 0 H188 V"+t+" H145z M145 0 H188 V"+t+" H145z"+("M367 0 H410 V"+t+" H367z M367 0 H410 V"+t+" H367z");case"\u239F":return"M457 0 H583 V"+t+" H457z M457 0 H583 V"+t+" H457z";case"\u23A2":return"M319 0 H403 V"+t+" H319z M319 0 H403 V"+t+" H319z";case"\u23A5":return"M263 0 H347 V"+t+" H263z M263 0 H347 V"+t+" H263z";case"\u23AA":return"M384 0 H504 V"+t+" H384z M384 0 H504 V"+t+" H384z";case"\u23D0":return"M312 0 H355 V"+t+" H312z M312 0 H355 V"+t+" H312z";case"\u2016":return"M257 0 H300 V"+t+" H257z M257 0 H300 V"+t+" H257z"+("M478 0 H521 V"+t+" H478z M478 0 H521 V"+t+" H478z");default:return""}},D6={doubleleftarrow:`M262 157 +219 661 l218 661zM702 `+t+"H400000v"+(40+e)+"H742z"},ZS=function(e,t,r){t=1e3*t;var n="";switch(e){case"sqrtMain":n=jS(t,ua);break;case"sqrtSize1":n=GS(t,ua);break;case"sqrtSize2":n=WS(t,ua);break;case"sqrtSize3":n=VS(t,ua);break;case"sqrtSize4":n=YS(t,ua);break;case"sqrtTall":n=KS(t,ua,r)}return n},QS=function(e,t){switch(e){case"\u239C":return"M291 0 H417 V"+t+" H291z M291 0 H417 V"+t+" H291z";case"\u2223":return"M145 0 H188 V"+t+" H145z M145 0 H188 V"+t+" H145z";case"\u2225":return"M145 0 H188 V"+t+" H145z M145 0 H188 V"+t+" H145z"+("M367 0 H410 V"+t+" H367z M367 0 H410 V"+t+" H367z");case"\u239F":return"M457 0 H583 V"+t+" H457z M457 0 H583 V"+t+" H457z";case"\u23A2":return"M319 0 H403 V"+t+" H319z M319 0 H403 V"+t+" H319z";case"\u23A5":return"M263 0 H347 V"+t+" H263z M263 0 H347 V"+t+" H263z";case"\u23AA":return"M384 0 H504 V"+t+" H384z M384 0 H504 V"+t+" H384z";case"\u23D0":return"M312 0 H355 V"+t+" H312z M312 0 H355 V"+t+" H312z";case"\u2016":return"M257 0 H300 V"+t+" H257z M257 0 H300 V"+t+" H257z"+("M478 0 H521 V"+t+" H478z M478 0 H521 V"+t+" H478z");default:return""}},X6={doubleleftarrow:`M262 157 l10-10c34-36 62.7-77 86-123 3.3-8 5-13.3 5-16 0-5.3-6.7-8-20-8-7.3 0-12.2.5-14.5 1.5-2.3 1-4.8 4.5-7.5 10.5-49.3 97.3-121.7 169.3-217 216-28 14-57.3 25-88 33-6.7 2-11 3.8-13 5.5-2 1.7-3 4.2-3 7.5s1 5.8 3 7.5 @@ -305,7 +305,7 @@ M93 435 v40 H400000 v-40z M500 241 v40 H400000 v-40z M500 241 v40 H400000 v-40z` c4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199, -231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6 c-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z -M500 241 v40 H399408 v-40z M500 435 v40 H400000 v-40z`},CS=function(e,t){switch(e){case"lbrack":return"M403 1759 V84 H666 V0 H319 V1759 v"+t+` v1759 h347 v-84 +M500 241 v40 H399408 v-40z M500 435 v40 H400000 v-40z`},JS=function(e,t){switch(e){case"lbrack":return"M403 1759 V84 H666 V0 H319 V1759 v"+t+` v1759 h347 v-84 H403z M403 1759 V0 H319 V1759 v`+t+" v1759 h84z";case"rbrack":return"M347 1759 V0 H0 V84 H263 V1759 v"+t+` v1759 H0 v84 H347z M347 1759 V0 H263 V1759 v`+t+" v1759 h84z";case"vert":return"M145 15 v585 v"+t+` v585 c2.667,10,9.667,15,21,15 c10,0,16.667,-5,20,-15 v-585 v`+-t+` v-585 c-2.667,-10,-9.667,-15,-21,-15 @@ -333,61 +333,61 @@ c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6 c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17 c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558 l0,-`+(t+144)+`c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7, --470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z`;default:throw new Error("Unknown stretchy delimiter.")}},ms=class{constructor(e){this.children=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.children=e,this.classes=[],this.height=0,this.depth=0,this.maxFontSize=0,this.style={}}hasClass(e){return this.classes.includes(e)}toNode(){for(var e=document.createDocumentFragment(),t=0;tt.toText();return this.children.map(e).join("")}},dr={"AMS-Regular":{32:[0,0,0,0,.25],65:[0,.68889,0,0,.72222],66:[0,.68889,0,0,.66667],67:[0,.68889,0,0,.72222],68:[0,.68889,0,0,.72222],69:[0,.68889,0,0,.66667],70:[0,.68889,0,0,.61111],71:[0,.68889,0,0,.77778],72:[0,.68889,0,0,.77778],73:[0,.68889,0,0,.38889],74:[.16667,.68889,0,0,.5],75:[0,.68889,0,0,.77778],76:[0,.68889,0,0,.66667],77:[0,.68889,0,0,.94445],78:[0,.68889,0,0,.72222],79:[.16667,.68889,0,0,.77778],80:[0,.68889,0,0,.61111],81:[.16667,.68889,0,0,.77778],82:[0,.68889,0,0,.72222],83:[0,.68889,0,0,.55556],84:[0,.68889,0,0,.66667],85:[0,.68889,0,0,.72222],86:[0,.68889,0,0,.72222],87:[0,.68889,0,0,1],88:[0,.68889,0,0,.72222],89:[0,.68889,0,0,.72222],90:[0,.68889,0,0,.66667],107:[0,.68889,0,0,.55556],160:[0,0,0,0,.25],165:[0,.675,.025,0,.75],174:[.15559,.69224,0,0,.94666],240:[0,.68889,0,0,.55556],295:[0,.68889,0,0,.54028],710:[0,.825,0,0,2.33334],732:[0,.9,0,0,2.33334],770:[0,.825,0,0,2.33334],771:[0,.9,0,0,2.33334],989:[.08167,.58167,0,0,.77778],1008:[0,.43056,.04028,0,.66667],8245:[0,.54986,0,0,.275],8463:[0,.68889,0,0,.54028],8487:[0,.68889,0,0,.72222],8498:[0,.68889,0,0,.55556],8502:[0,.68889,0,0,.66667],8503:[0,.68889,0,0,.44445],8504:[0,.68889,0,0,.66667],8513:[0,.68889,0,0,.63889],8592:[-.03598,.46402,0,0,.5],8594:[-.03598,.46402,0,0,.5],8602:[-.13313,.36687,0,0,1],8603:[-.13313,.36687,0,0,1],8606:[.01354,.52239,0,0,1],8608:[.01354,.52239,0,0,1],8610:[.01354,.52239,0,0,1.11111],8611:[.01354,.52239,0,0,1.11111],8619:[0,.54986,0,0,1],8620:[0,.54986,0,0,1],8621:[-.13313,.37788,0,0,1.38889],8622:[-.13313,.36687,0,0,1],8624:[0,.69224,0,0,.5],8625:[0,.69224,0,0,.5],8630:[0,.43056,0,0,1],8631:[0,.43056,0,0,1],8634:[.08198,.58198,0,0,.77778],8635:[.08198,.58198,0,0,.77778],8638:[.19444,.69224,0,0,.41667],8639:[.19444,.69224,0,0,.41667],8642:[.19444,.69224,0,0,.41667],8643:[.19444,.69224,0,0,.41667],8644:[.1808,.675,0,0,1],8646:[.1808,.675,0,0,1],8647:[.1808,.675,0,0,1],8648:[.19444,.69224,0,0,.83334],8649:[.1808,.675,0,0,1],8650:[.19444,.69224,0,0,.83334],8651:[.01354,.52239,0,0,1],8652:[.01354,.52239,0,0,1],8653:[-.13313,.36687,0,0,1],8654:[-.13313,.36687,0,0,1],8655:[-.13313,.36687,0,0,1],8666:[.13667,.63667,0,0,1],8667:[.13667,.63667,0,0,1],8669:[-.13313,.37788,0,0,1],8672:[-.064,.437,0,0,1.334],8674:[-.064,.437,0,0,1.334],8705:[0,.825,0,0,.5],8708:[0,.68889,0,0,.55556],8709:[.08167,.58167,0,0,.77778],8717:[0,.43056,0,0,.42917],8722:[-.03598,.46402,0,0,.5],8724:[.08198,.69224,0,0,.77778],8726:[.08167,.58167,0,0,.77778],8733:[0,.69224,0,0,.77778],8736:[0,.69224,0,0,.72222],8737:[0,.69224,0,0,.72222],8738:[.03517,.52239,0,0,.72222],8739:[.08167,.58167,0,0,.22222],8740:[.25142,.74111,0,0,.27778],8741:[.08167,.58167,0,0,.38889],8742:[.25142,.74111,0,0,.5],8756:[0,.69224,0,0,.66667],8757:[0,.69224,0,0,.66667],8764:[-.13313,.36687,0,0,.77778],8765:[-.13313,.37788,0,0,.77778],8769:[-.13313,.36687,0,0,.77778],8770:[-.03625,.46375,0,0,.77778],8774:[.30274,.79383,0,0,.77778],8776:[-.01688,.48312,0,0,.77778],8778:[.08167,.58167,0,0,.77778],8782:[.06062,.54986,0,0,.77778],8783:[.06062,.54986,0,0,.77778],8785:[.08198,.58198,0,0,.77778],8786:[.08198,.58198,0,0,.77778],8787:[.08198,.58198,0,0,.77778],8790:[0,.69224,0,0,.77778],8791:[.22958,.72958,0,0,.77778],8796:[.08198,.91667,0,0,.77778],8806:[.25583,.75583,0,0,.77778],8807:[.25583,.75583,0,0,.77778],8808:[.25142,.75726,0,0,.77778],8809:[.25142,.75726,0,0,.77778],8812:[.25583,.75583,0,0,.5],8814:[.20576,.70576,0,0,.77778],8815:[.20576,.70576,0,0,.77778],8816:[.30274,.79383,0,0,.77778],8817:[.30274,.79383,0,0,.77778],8818:[.22958,.72958,0,0,.77778],8819:[.22958,.72958,0,0,.77778],8822:[.1808,.675,0,0,.77778],8823:[.1808,.675,0,0,.77778],8828:[.13667,.63667,0,0,.77778],8829:[.13667,.63667,0,0,.77778],8830:[.22958,.72958,0,0,.77778],8831:[.22958,.72958,0,0,.77778],8832:[.20576,.70576,0,0,.77778],8833:[.20576,.70576,0,0,.77778],8840:[.30274,.79383,0,0,.77778],8841:[.30274,.79383,0,0,.77778],8842:[.13597,.63597,0,0,.77778],8843:[.13597,.63597,0,0,.77778],8847:[.03517,.54986,0,0,.77778],8848:[.03517,.54986,0,0,.77778],8858:[.08198,.58198,0,0,.77778],8859:[.08198,.58198,0,0,.77778],8861:[.08198,.58198,0,0,.77778],8862:[0,.675,0,0,.77778],8863:[0,.675,0,0,.77778],8864:[0,.675,0,0,.77778],8865:[0,.675,0,0,.77778],8872:[0,.69224,0,0,.61111],8873:[0,.69224,0,0,.72222],8874:[0,.69224,0,0,.88889],8876:[0,.68889,0,0,.61111],8877:[0,.68889,0,0,.61111],8878:[0,.68889,0,0,.72222],8879:[0,.68889,0,0,.72222],8882:[.03517,.54986,0,0,.77778],8883:[.03517,.54986,0,0,.77778],8884:[.13667,.63667,0,0,.77778],8885:[.13667,.63667,0,0,.77778],8888:[0,.54986,0,0,1.11111],8890:[.19444,.43056,0,0,.55556],8891:[.19444,.69224,0,0,.61111],8892:[.19444,.69224,0,0,.61111],8901:[0,.54986,0,0,.27778],8903:[.08167,.58167,0,0,.77778],8905:[.08167,.58167,0,0,.77778],8906:[.08167,.58167,0,0,.77778],8907:[0,.69224,0,0,.77778],8908:[0,.69224,0,0,.77778],8909:[-.03598,.46402,0,0,.77778],8910:[0,.54986,0,0,.76042],8911:[0,.54986,0,0,.76042],8912:[.03517,.54986,0,0,.77778],8913:[.03517,.54986,0,0,.77778],8914:[0,.54986,0,0,.66667],8915:[0,.54986,0,0,.66667],8916:[0,.69224,0,0,.66667],8918:[.0391,.5391,0,0,.77778],8919:[.0391,.5391,0,0,.77778],8920:[.03517,.54986,0,0,1.33334],8921:[.03517,.54986,0,0,1.33334],8922:[.38569,.88569,0,0,.77778],8923:[.38569,.88569,0,0,.77778],8926:[.13667,.63667,0,0,.77778],8927:[.13667,.63667,0,0,.77778],8928:[.30274,.79383,0,0,.77778],8929:[.30274,.79383,0,0,.77778],8934:[.23222,.74111,0,0,.77778],8935:[.23222,.74111,0,0,.77778],8936:[.23222,.74111,0,0,.77778],8937:[.23222,.74111,0,0,.77778],8938:[.20576,.70576,0,0,.77778],8939:[.20576,.70576,0,0,.77778],8940:[.30274,.79383,0,0,.77778],8941:[.30274,.79383,0,0,.77778],8994:[.19444,.69224,0,0,.77778],8995:[.19444,.69224,0,0,.77778],9416:[.15559,.69224,0,0,.90222],9484:[0,.69224,0,0,.5],9488:[0,.69224,0,0,.5],9492:[0,.37788,0,0,.5],9496:[0,.37788,0,0,.5],9585:[.19444,.68889,0,0,.88889],9586:[.19444,.74111,0,0,.88889],9632:[0,.675,0,0,.77778],9633:[0,.675,0,0,.77778],9650:[0,.54986,0,0,.72222],9651:[0,.54986,0,0,.72222],9654:[.03517,.54986,0,0,.77778],9660:[0,.54986,0,0,.72222],9661:[0,.54986,0,0,.72222],9664:[.03517,.54986,0,0,.77778],9674:[.11111,.69224,0,0,.66667],9733:[.19444,.69224,0,0,.94445],10003:[0,.69224,0,0,.83334],10016:[0,.69224,0,0,.83334],10731:[.11111,.69224,0,0,.66667],10846:[.19444,.75583,0,0,.61111],10877:[.13667,.63667,0,0,.77778],10878:[.13667,.63667,0,0,.77778],10885:[.25583,.75583,0,0,.77778],10886:[.25583,.75583,0,0,.77778],10887:[.13597,.63597,0,0,.77778],10888:[.13597,.63597,0,0,.77778],10889:[.26167,.75726,0,0,.77778],10890:[.26167,.75726,0,0,.77778],10891:[.48256,.98256,0,0,.77778],10892:[.48256,.98256,0,0,.77778],10901:[.13667,.63667,0,0,.77778],10902:[.13667,.63667,0,0,.77778],10933:[.25142,.75726,0,0,.77778],10934:[.25142,.75726,0,0,.77778],10935:[.26167,.75726,0,0,.77778],10936:[.26167,.75726,0,0,.77778],10937:[.26167,.75726,0,0,.77778],10938:[.26167,.75726,0,0,.77778],10949:[.25583,.75583,0,0,.77778],10950:[.25583,.75583,0,0,.77778],10955:[.28481,.79383,0,0,.77778],10956:[.28481,.79383,0,0,.77778],57350:[.08167,.58167,0,0,.22222],57351:[.08167,.58167,0,0,.38889],57352:[.08167,.58167,0,0,.77778],57353:[0,.43056,.04028,0,.66667],57356:[.25142,.75726,0,0,.77778],57357:[.25142,.75726,0,0,.77778],57358:[.41951,.91951,0,0,.77778],57359:[.30274,.79383,0,0,.77778],57360:[.30274,.79383,0,0,.77778],57361:[.41951,.91951,0,0,.77778],57366:[.25142,.75726,0,0,.77778],57367:[.25142,.75726,0,0,.77778],57368:[.25142,.75726,0,0,.77778],57369:[.25142,.75726,0,0,.77778],57370:[.13597,.63597,0,0,.77778],57371:[.13597,.63597,0,0,.77778]},"Caligraphic-Regular":{32:[0,0,0,0,.25],65:[0,.68333,0,.19445,.79847],66:[0,.68333,.03041,.13889,.65681],67:[0,.68333,.05834,.13889,.52653],68:[0,.68333,.02778,.08334,.77139],69:[0,.68333,.08944,.11111,.52778],70:[0,.68333,.09931,.11111,.71875],71:[.09722,.68333,.0593,.11111,.59487],72:[0,.68333,.00965,.11111,.84452],73:[0,.68333,.07382,0,.54452],74:[.09722,.68333,.18472,.16667,.67778],75:[0,.68333,.01445,.05556,.76195],76:[0,.68333,0,.13889,.68972],77:[0,.68333,0,.13889,1.2009],78:[0,.68333,.14736,.08334,.82049],79:[0,.68333,.02778,.11111,.79611],80:[0,.68333,.08222,.08334,.69556],81:[.09722,.68333,0,.11111,.81667],82:[0,.68333,0,.08334,.8475],83:[0,.68333,.075,.13889,.60556],84:[0,.68333,.25417,0,.54464],85:[0,.68333,.09931,.08334,.62583],86:[0,.68333,.08222,0,.61278],87:[0,.68333,.08222,.08334,.98778],88:[0,.68333,.14643,.13889,.7133],89:[.09722,.68333,.08222,.08334,.66834],90:[0,.68333,.07944,.13889,.72473],160:[0,0,0,0,.25]},"Fraktur-Regular":{32:[0,0,0,0,.25],33:[0,.69141,0,0,.29574],34:[0,.69141,0,0,.21471],38:[0,.69141,0,0,.73786],39:[0,.69141,0,0,.21201],40:[.24982,.74947,0,0,.38865],41:[.24982,.74947,0,0,.38865],42:[0,.62119,0,0,.27764],43:[.08319,.58283,0,0,.75623],44:[0,.10803,0,0,.27764],45:[.08319,.58283,0,0,.75623],46:[0,.10803,0,0,.27764],47:[.24982,.74947,0,0,.50181],48:[0,.47534,0,0,.50181],49:[0,.47534,0,0,.50181],50:[0,.47534,0,0,.50181],51:[.18906,.47534,0,0,.50181],52:[.18906,.47534,0,0,.50181],53:[.18906,.47534,0,0,.50181],54:[0,.69141,0,0,.50181],55:[.18906,.47534,0,0,.50181],56:[0,.69141,0,0,.50181],57:[.18906,.47534,0,0,.50181],58:[0,.47534,0,0,.21606],59:[.12604,.47534,0,0,.21606],61:[-.13099,.36866,0,0,.75623],63:[0,.69141,0,0,.36245],65:[0,.69141,0,0,.7176],66:[0,.69141,0,0,.88397],67:[0,.69141,0,0,.61254],68:[0,.69141,0,0,.83158],69:[0,.69141,0,0,.66278],70:[.12604,.69141,0,0,.61119],71:[0,.69141,0,0,.78539],72:[.06302,.69141,0,0,.7203],73:[0,.69141,0,0,.55448],74:[.12604,.69141,0,0,.55231],75:[0,.69141,0,0,.66845],76:[0,.69141,0,0,.66602],77:[0,.69141,0,0,1.04953],78:[0,.69141,0,0,.83212],79:[0,.69141,0,0,.82699],80:[.18906,.69141,0,0,.82753],81:[.03781,.69141,0,0,.82699],82:[0,.69141,0,0,.82807],83:[0,.69141,0,0,.82861],84:[0,.69141,0,0,.66899],85:[0,.69141,0,0,.64576],86:[0,.69141,0,0,.83131],87:[0,.69141,0,0,1.04602],88:[0,.69141,0,0,.71922],89:[.18906,.69141,0,0,.83293],90:[.12604,.69141,0,0,.60201],91:[.24982,.74947,0,0,.27764],93:[.24982,.74947,0,0,.27764],94:[0,.69141,0,0,.49965],97:[0,.47534,0,0,.50046],98:[0,.69141,0,0,.51315],99:[0,.47534,0,0,.38946],100:[0,.62119,0,0,.49857],101:[0,.47534,0,0,.40053],102:[.18906,.69141,0,0,.32626],103:[.18906,.47534,0,0,.5037],104:[.18906,.69141,0,0,.52126],105:[0,.69141,0,0,.27899],106:[0,.69141,0,0,.28088],107:[0,.69141,0,0,.38946],108:[0,.69141,0,0,.27953],109:[0,.47534,0,0,.76676],110:[0,.47534,0,0,.52666],111:[0,.47534,0,0,.48885],112:[.18906,.52396,0,0,.50046],113:[.18906,.47534,0,0,.48912],114:[0,.47534,0,0,.38919],115:[0,.47534,0,0,.44266],116:[0,.62119,0,0,.33301],117:[0,.47534,0,0,.5172],118:[0,.52396,0,0,.5118],119:[0,.52396,0,0,.77351],120:[.18906,.47534,0,0,.38865],121:[.18906,.47534,0,0,.49884],122:[.18906,.47534,0,0,.39054],160:[0,0,0,0,.25],8216:[0,.69141,0,0,.21471],8217:[0,.69141,0,0,.21471],58112:[0,.62119,0,0,.49749],58113:[0,.62119,0,0,.4983],58114:[.18906,.69141,0,0,.33328],58115:[.18906,.69141,0,0,.32923],58116:[.18906,.47534,0,0,.50343],58117:[0,.69141,0,0,.33301],58118:[0,.62119,0,0,.33409],58119:[0,.47534,0,0,.50073]},"Main-Bold":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.35],34:[0,.69444,0,0,.60278],35:[.19444,.69444,0,0,.95833],36:[.05556,.75,0,0,.575],37:[.05556,.75,0,0,.95833],38:[0,.69444,0,0,.89444],39:[0,.69444,0,0,.31944],40:[.25,.75,0,0,.44722],41:[.25,.75,0,0,.44722],42:[0,.75,0,0,.575],43:[.13333,.63333,0,0,.89444],44:[.19444,.15556,0,0,.31944],45:[0,.44444,0,0,.38333],46:[0,.15556,0,0,.31944],47:[.25,.75,0,0,.575],48:[0,.64444,0,0,.575],49:[0,.64444,0,0,.575],50:[0,.64444,0,0,.575],51:[0,.64444,0,0,.575],52:[0,.64444,0,0,.575],53:[0,.64444,0,0,.575],54:[0,.64444,0,0,.575],55:[0,.64444,0,0,.575],56:[0,.64444,0,0,.575],57:[0,.64444,0,0,.575],58:[0,.44444,0,0,.31944],59:[.19444,.44444,0,0,.31944],60:[.08556,.58556,0,0,.89444],61:[-.10889,.39111,0,0,.89444],62:[.08556,.58556,0,0,.89444],63:[0,.69444,0,0,.54305],64:[0,.69444,0,0,.89444],65:[0,.68611,0,0,.86944],66:[0,.68611,0,0,.81805],67:[0,.68611,0,0,.83055],68:[0,.68611,0,0,.88194],69:[0,.68611,0,0,.75555],70:[0,.68611,0,0,.72361],71:[0,.68611,0,0,.90416],72:[0,.68611,0,0,.9],73:[0,.68611,0,0,.43611],74:[0,.68611,0,0,.59444],75:[0,.68611,0,0,.90138],76:[0,.68611,0,0,.69166],77:[0,.68611,0,0,1.09166],78:[0,.68611,0,0,.9],79:[0,.68611,0,0,.86388],80:[0,.68611,0,0,.78611],81:[.19444,.68611,0,0,.86388],82:[0,.68611,0,0,.8625],83:[0,.68611,0,0,.63889],84:[0,.68611,0,0,.8],85:[0,.68611,0,0,.88472],86:[0,.68611,.01597,0,.86944],87:[0,.68611,.01597,0,1.18888],88:[0,.68611,0,0,.86944],89:[0,.68611,.02875,0,.86944],90:[0,.68611,0,0,.70277],91:[.25,.75,0,0,.31944],92:[.25,.75,0,0,.575],93:[.25,.75,0,0,.31944],94:[0,.69444,0,0,.575],95:[.31,.13444,.03194,0,.575],97:[0,.44444,0,0,.55902],98:[0,.69444,0,0,.63889],99:[0,.44444,0,0,.51111],100:[0,.69444,0,0,.63889],101:[0,.44444,0,0,.52708],102:[0,.69444,.10903,0,.35139],103:[.19444,.44444,.01597,0,.575],104:[0,.69444,0,0,.63889],105:[0,.69444,0,0,.31944],106:[.19444,.69444,0,0,.35139],107:[0,.69444,0,0,.60694],108:[0,.69444,0,0,.31944],109:[0,.44444,0,0,.95833],110:[0,.44444,0,0,.63889],111:[0,.44444,0,0,.575],112:[.19444,.44444,0,0,.63889],113:[.19444,.44444,0,0,.60694],114:[0,.44444,0,0,.47361],115:[0,.44444,0,0,.45361],116:[0,.63492,0,0,.44722],117:[0,.44444,0,0,.63889],118:[0,.44444,.01597,0,.60694],119:[0,.44444,.01597,0,.83055],120:[0,.44444,0,0,.60694],121:[.19444,.44444,.01597,0,.60694],122:[0,.44444,0,0,.51111],123:[.25,.75,0,0,.575],124:[.25,.75,0,0,.31944],125:[.25,.75,0,0,.575],126:[.35,.34444,0,0,.575],160:[0,0,0,0,.25],163:[0,.69444,0,0,.86853],168:[0,.69444,0,0,.575],172:[0,.44444,0,0,.76666],176:[0,.69444,0,0,.86944],177:[.13333,.63333,0,0,.89444],184:[.17014,0,0,0,.51111],198:[0,.68611,0,0,1.04166],215:[.13333,.63333,0,0,.89444],216:[.04861,.73472,0,0,.89444],223:[0,.69444,0,0,.59722],230:[0,.44444,0,0,.83055],247:[.13333,.63333,0,0,.89444],248:[.09722,.54167,0,0,.575],305:[0,.44444,0,0,.31944],338:[0,.68611,0,0,1.16944],339:[0,.44444,0,0,.89444],567:[.19444,.44444,0,0,.35139],710:[0,.69444,0,0,.575],711:[0,.63194,0,0,.575],713:[0,.59611,0,0,.575],714:[0,.69444,0,0,.575],715:[0,.69444,0,0,.575],728:[0,.69444,0,0,.575],729:[0,.69444,0,0,.31944],730:[0,.69444,0,0,.86944],732:[0,.69444,0,0,.575],733:[0,.69444,0,0,.575],915:[0,.68611,0,0,.69166],916:[0,.68611,0,0,.95833],920:[0,.68611,0,0,.89444],923:[0,.68611,0,0,.80555],926:[0,.68611,0,0,.76666],928:[0,.68611,0,0,.9],931:[0,.68611,0,0,.83055],933:[0,.68611,0,0,.89444],934:[0,.68611,0,0,.83055],936:[0,.68611,0,0,.89444],937:[0,.68611,0,0,.83055],8211:[0,.44444,.03194,0,.575],8212:[0,.44444,.03194,0,1.14999],8216:[0,.69444,0,0,.31944],8217:[0,.69444,0,0,.31944],8220:[0,.69444,0,0,.60278],8221:[0,.69444,0,0,.60278],8224:[.19444,.69444,0,0,.51111],8225:[.19444,.69444,0,0,.51111],8242:[0,.55556,0,0,.34444],8407:[0,.72444,.15486,0,.575],8463:[0,.69444,0,0,.66759],8465:[0,.69444,0,0,.83055],8467:[0,.69444,0,0,.47361],8472:[.19444,.44444,0,0,.74027],8476:[0,.69444,0,0,.83055],8501:[0,.69444,0,0,.70277],8592:[-.10889,.39111,0,0,1.14999],8593:[.19444,.69444,0,0,.575],8594:[-.10889,.39111,0,0,1.14999],8595:[.19444,.69444,0,0,.575],8596:[-.10889,.39111,0,0,1.14999],8597:[.25,.75,0,0,.575],8598:[.19444,.69444,0,0,1.14999],8599:[.19444,.69444,0,0,1.14999],8600:[.19444,.69444,0,0,1.14999],8601:[.19444,.69444,0,0,1.14999],8636:[-.10889,.39111,0,0,1.14999],8637:[-.10889,.39111,0,0,1.14999],8640:[-.10889,.39111,0,0,1.14999],8641:[-.10889,.39111,0,0,1.14999],8656:[-.10889,.39111,0,0,1.14999],8657:[.19444,.69444,0,0,.70277],8658:[-.10889,.39111,0,0,1.14999],8659:[.19444,.69444,0,0,.70277],8660:[-.10889,.39111,0,0,1.14999],8661:[.25,.75,0,0,.70277],8704:[0,.69444,0,0,.63889],8706:[0,.69444,.06389,0,.62847],8707:[0,.69444,0,0,.63889],8709:[.05556,.75,0,0,.575],8711:[0,.68611,0,0,.95833],8712:[.08556,.58556,0,0,.76666],8715:[.08556,.58556,0,0,.76666],8722:[.13333,.63333,0,0,.89444],8723:[.13333,.63333,0,0,.89444],8725:[.25,.75,0,0,.575],8726:[.25,.75,0,0,.575],8727:[-.02778,.47222,0,0,.575],8728:[-.02639,.47361,0,0,.575],8729:[-.02639,.47361,0,0,.575],8730:[.18,.82,0,0,.95833],8733:[0,.44444,0,0,.89444],8734:[0,.44444,0,0,1.14999],8736:[0,.69224,0,0,.72222],8739:[.25,.75,0,0,.31944],8741:[.25,.75,0,0,.575],8743:[0,.55556,0,0,.76666],8744:[0,.55556,0,0,.76666],8745:[0,.55556,0,0,.76666],8746:[0,.55556,0,0,.76666],8747:[.19444,.69444,.12778,0,.56875],8764:[-.10889,.39111,0,0,.89444],8768:[.19444,.69444,0,0,.31944],8771:[.00222,.50222,0,0,.89444],8773:[.027,.638,0,0,.894],8776:[.02444,.52444,0,0,.89444],8781:[.00222,.50222,0,0,.89444],8801:[.00222,.50222,0,0,.89444],8804:[.19667,.69667,0,0,.89444],8805:[.19667,.69667,0,0,.89444],8810:[.08556,.58556,0,0,1.14999],8811:[.08556,.58556,0,0,1.14999],8826:[.08556,.58556,0,0,.89444],8827:[.08556,.58556,0,0,.89444],8834:[.08556,.58556,0,0,.89444],8835:[.08556,.58556,0,0,.89444],8838:[.19667,.69667,0,0,.89444],8839:[.19667,.69667,0,0,.89444],8846:[0,.55556,0,0,.76666],8849:[.19667,.69667,0,0,.89444],8850:[.19667,.69667,0,0,.89444],8851:[0,.55556,0,0,.76666],8852:[0,.55556,0,0,.76666],8853:[.13333,.63333,0,0,.89444],8854:[.13333,.63333,0,0,.89444],8855:[.13333,.63333,0,0,.89444],8856:[.13333,.63333,0,0,.89444],8857:[.13333,.63333,0,0,.89444],8866:[0,.69444,0,0,.70277],8867:[0,.69444,0,0,.70277],8868:[0,.69444,0,0,.89444],8869:[0,.69444,0,0,.89444],8900:[-.02639,.47361,0,0,.575],8901:[-.02639,.47361,0,0,.31944],8902:[-.02778,.47222,0,0,.575],8968:[.25,.75,0,0,.51111],8969:[.25,.75,0,0,.51111],8970:[.25,.75,0,0,.51111],8971:[.25,.75,0,0,.51111],8994:[-.13889,.36111,0,0,1.14999],8995:[-.13889,.36111,0,0,1.14999],9651:[.19444,.69444,0,0,1.02222],9657:[-.02778,.47222,0,0,.575],9661:[.19444,.69444,0,0,1.02222],9667:[-.02778,.47222,0,0,.575],9711:[.19444,.69444,0,0,1.14999],9824:[.12963,.69444,0,0,.89444],9825:[.12963,.69444,0,0,.89444],9826:[.12963,.69444,0,0,.89444],9827:[.12963,.69444,0,0,.89444],9837:[0,.75,0,0,.44722],9838:[.19444,.69444,0,0,.44722],9839:[.19444,.69444,0,0,.44722],10216:[.25,.75,0,0,.44722],10217:[.25,.75,0,0,.44722],10815:[0,.68611,0,0,.9],10927:[.19667,.69667,0,0,.89444],10928:[.19667,.69667,0,0,.89444],57376:[.19444,.69444,0,0,0]},"Main-BoldItalic":{32:[0,0,0,0,.25],33:[0,.69444,.11417,0,.38611],34:[0,.69444,.07939,0,.62055],35:[.19444,.69444,.06833,0,.94444],37:[.05556,.75,.12861,0,.94444],38:[0,.69444,.08528,0,.88555],39:[0,.69444,.12945,0,.35555],40:[.25,.75,.15806,0,.47333],41:[.25,.75,.03306,0,.47333],42:[0,.75,.14333,0,.59111],43:[.10333,.60333,.03306,0,.88555],44:[.19444,.14722,0,0,.35555],45:[0,.44444,.02611,0,.41444],46:[0,.14722,0,0,.35555],47:[.25,.75,.15806,0,.59111],48:[0,.64444,.13167,0,.59111],49:[0,.64444,.13167,0,.59111],50:[0,.64444,.13167,0,.59111],51:[0,.64444,.13167,0,.59111],52:[.19444,.64444,.13167,0,.59111],53:[0,.64444,.13167,0,.59111],54:[0,.64444,.13167,0,.59111],55:[.19444,.64444,.13167,0,.59111],56:[0,.64444,.13167,0,.59111],57:[0,.64444,.13167,0,.59111],58:[0,.44444,.06695,0,.35555],59:[.19444,.44444,.06695,0,.35555],61:[-.10889,.39111,.06833,0,.88555],63:[0,.69444,.11472,0,.59111],64:[0,.69444,.09208,0,.88555],65:[0,.68611,0,0,.86555],66:[0,.68611,.0992,0,.81666],67:[0,.68611,.14208,0,.82666],68:[0,.68611,.09062,0,.87555],69:[0,.68611,.11431,0,.75666],70:[0,.68611,.12903,0,.72722],71:[0,.68611,.07347,0,.89527],72:[0,.68611,.17208,0,.8961],73:[0,.68611,.15681,0,.47166],74:[0,.68611,.145,0,.61055],75:[0,.68611,.14208,0,.89499],76:[0,.68611,0,0,.69777],77:[0,.68611,.17208,0,1.07277],78:[0,.68611,.17208,0,.8961],79:[0,.68611,.09062,0,.85499],80:[0,.68611,.0992,0,.78721],81:[.19444,.68611,.09062,0,.85499],82:[0,.68611,.02559,0,.85944],83:[0,.68611,.11264,0,.64999],84:[0,.68611,.12903,0,.7961],85:[0,.68611,.17208,0,.88083],86:[0,.68611,.18625,0,.86555],87:[0,.68611,.18625,0,1.15999],88:[0,.68611,.15681,0,.86555],89:[0,.68611,.19803,0,.86555],90:[0,.68611,.14208,0,.70888],91:[.25,.75,.1875,0,.35611],93:[.25,.75,.09972,0,.35611],94:[0,.69444,.06709,0,.59111],95:[.31,.13444,.09811,0,.59111],97:[0,.44444,.09426,0,.59111],98:[0,.69444,.07861,0,.53222],99:[0,.44444,.05222,0,.53222],100:[0,.69444,.10861,0,.59111],101:[0,.44444,.085,0,.53222],102:[.19444,.69444,.21778,0,.4],103:[.19444,.44444,.105,0,.53222],104:[0,.69444,.09426,0,.59111],105:[0,.69326,.11387,0,.35555],106:[.19444,.69326,.1672,0,.35555],107:[0,.69444,.11111,0,.53222],108:[0,.69444,.10861,0,.29666],109:[0,.44444,.09426,0,.94444],110:[0,.44444,.09426,0,.64999],111:[0,.44444,.07861,0,.59111],112:[.19444,.44444,.07861,0,.59111],113:[.19444,.44444,.105,0,.53222],114:[0,.44444,.11111,0,.50167],115:[0,.44444,.08167,0,.48694],116:[0,.63492,.09639,0,.385],117:[0,.44444,.09426,0,.62055],118:[0,.44444,.11111,0,.53222],119:[0,.44444,.11111,0,.76777],120:[0,.44444,.12583,0,.56055],121:[.19444,.44444,.105,0,.56166],122:[0,.44444,.13889,0,.49055],126:[.35,.34444,.11472,0,.59111],160:[0,0,0,0,.25],168:[0,.69444,.11473,0,.59111],176:[0,.69444,0,0,.94888],184:[.17014,0,0,0,.53222],198:[0,.68611,.11431,0,1.02277],216:[.04861,.73472,.09062,0,.88555],223:[.19444,.69444,.09736,0,.665],230:[0,.44444,.085,0,.82666],248:[.09722,.54167,.09458,0,.59111],305:[0,.44444,.09426,0,.35555],338:[0,.68611,.11431,0,1.14054],339:[0,.44444,.085,0,.82666],567:[.19444,.44444,.04611,0,.385],710:[0,.69444,.06709,0,.59111],711:[0,.63194,.08271,0,.59111],713:[0,.59444,.10444,0,.59111],714:[0,.69444,.08528,0,.59111],715:[0,.69444,0,0,.59111],728:[0,.69444,.10333,0,.59111],729:[0,.69444,.12945,0,.35555],730:[0,.69444,0,0,.94888],732:[0,.69444,.11472,0,.59111],733:[0,.69444,.11472,0,.59111],915:[0,.68611,.12903,0,.69777],916:[0,.68611,0,0,.94444],920:[0,.68611,.09062,0,.88555],923:[0,.68611,0,0,.80666],926:[0,.68611,.15092,0,.76777],928:[0,.68611,.17208,0,.8961],931:[0,.68611,.11431,0,.82666],933:[0,.68611,.10778,0,.88555],934:[0,.68611,.05632,0,.82666],936:[0,.68611,.10778,0,.88555],937:[0,.68611,.0992,0,.82666],8211:[0,.44444,.09811,0,.59111],8212:[0,.44444,.09811,0,1.18221],8216:[0,.69444,.12945,0,.35555],8217:[0,.69444,.12945,0,.35555],8220:[0,.69444,.16772,0,.62055],8221:[0,.69444,.07939,0,.62055]},"Main-Italic":{32:[0,0,0,0,.25],33:[0,.69444,.12417,0,.30667],34:[0,.69444,.06961,0,.51444],35:[.19444,.69444,.06616,0,.81777],37:[.05556,.75,.13639,0,.81777],38:[0,.69444,.09694,0,.76666],39:[0,.69444,.12417,0,.30667],40:[.25,.75,.16194,0,.40889],41:[.25,.75,.03694,0,.40889],42:[0,.75,.14917,0,.51111],43:[.05667,.56167,.03694,0,.76666],44:[.19444,.10556,0,0,.30667],45:[0,.43056,.02826,0,.35778],46:[0,.10556,0,0,.30667],47:[.25,.75,.16194,0,.51111],48:[0,.64444,.13556,0,.51111],49:[0,.64444,.13556,0,.51111],50:[0,.64444,.13556,0,.51111],51:[0,.64444,.13556,0,.51111],52:[.19444,.64444,.13556,0,.51111],53:[0,.64444,.13556,0,.51111],54:[0,.64444,.13556,0,.51111],55:[.19444,.64444,.13556,0,.51111],56:[0,.64444,.13556,0,.51111],57:[0,.64444,.13556,0,.51111],58:[0,.43056,.0582,0,.30667],59:[.19444,.43056,.0582,0,.30667],61:[-.13313,.36687,.06616,0,.76666],63:[0,.69444,.1225,0,.51111],64:[0,.69444,.09597,0,.76666],65:[0,.68333,0,0,.74333],66:[0,.68333,.10257,0,.70389],67:[0,.68333,.14528,0,.71555],68:[0,.68333,.09403,0,.755],69:[0,.68333,.12028,0,.67833],70:[0,.68333,.13305,0,.65277],71:[0,.68333,.08722,0,.77361],72:[0,.68333,.16389,0,.74333],73:[0,.68333,.15806,0,.38555],74:[0,.68333,.14028,0,.525],75:[0,.68333,.14528,0,.76888],76:[0,.68333,0,0,.62722],77:[0,.68333,.16389,0,.89666],78:[0,.68333,.16389,0,.74333],79:[0,.68333,.09403,0,.76666],80:[0,.68333,.10257,0,.67833],81:[.19444,.68333,.09403,0,.76666],82:[0,.68333,.03868,0,.72944],83:[0,.68333,.11972,0,.56222],84:[0,.68333,.13305,0,.71555],85:[0,.68333,.16389,0,.74333],86:[0,.68333,.18361,0,.74333],87:[0,.68333,.18361,0,.99888],88:[0,.68333,.15806,0,.74333],89:[0,.68333,.19383,0,.74333],90:[0,.68333,.14528,0,.61333],91:[.25,.75,.1875,0,.30667],93:[.25,.75,.10528,0,.30667],94:[0,.69444,.06646,0,.51111],95:[.31,.12056,.09208,0,.51111],97:[0,.43056,.07671,0,.51111],98:[0,.69444,.06312,0,.46],99:[0,.43056,.05653,0,.46],100:[0,.69444,.10333,0,.51111],101:[0,.43056,.07514,0,.46],102:[.19444,.69444,.21194,0,.30667],103:[.19444,.43056,.08847,0,.46],104:[0,.69444,.07671,0,.51111],105:[0,.65536,.1019,0,.30667],106:[.19444,.65536,.14467,0,.30667],107:[0,.69444,.10764,0,.46],108:[0,.69444,.10333,0,.25555],109:[0,.43056,.07671,0,.81777],110:[0,.43056,.07671,0,.56222],111:[0,.43056,.06312,0,.51111],112:[.19444,.43056,.06312,0,.51111],113:[.19444,.43056,.08847,0,.46],114:[0,.43056,.10764,0,.42166],115:[0,.43056,.08208,0,.40889],116:[0,.61508,.09486,0,.33222],117:[0,.43056,.07671,0,.53666],118:[0,.43056,.10764,0,.46],119:[0,.43056,.10764,0,.66444],120:[0,.43056,.12042,0,.46389],121:[.19444,.43056,.08847,0,.48555],122:[0,.43056,.12292,0,.40889],126:[.35,.31786,.11585,0,.51111],160:[0,0,0,0,.25],168:[0,.66786,.10474,0,.51111],176:[0,.69444,0,0,.83129],184:[.17014,0,0,0,.46],198:[0,.68333,.12028,0,.88277],216:[.04861,.73194,.09403,0,.76666],223:[.19444,.69444,.10514,0,.53666],230:[0,.43056,.07514,0,.71555],248:[.09722,.52778,.09194,0,.51111],338:[0,.68333,.12028,0,.98499],339:[0,.43056,.07514,0,.71555],710:[0,.69444,.06646,0,.51111],711:[0,.62847,.08295,0,.51111],713:[0,.56167,.10333,0,.51111],714:[0,.69444,.09694,0,.51111],715:[0,.69444,0,0,.51111],728:[0,.69444,.10806,0,.51111],729:[0,.66786,.11752,0,.30667],730:[0,.69444,0,0,.83129],732:[0,.66786,.11585,0,.51111],733:[0,.69444,.1225,0,.51111],915:[0,.68333,.13305,0,.62722],916:[0,.68333,0,0,.81777],920:[0,.68333,.09403,0,.76666],923:[0,.68333,0,0,.69222],926:[0,.68333,.15294,0,.66444],928:[0,.68333,.16389,0,.74333],931:[0,.68333,.12028,0,.71555],933:[0,.68333,.11111,0,.76666],934:[0,.68333,.05986,0,.71555],936:[0,.68333,.11111,0,.76666],937:[0,.68333,.10257,0,.71555],8211:[0,.43056,.09208,0,.51111],8212:[0,.43056,.09208,0,1.02222],8216:[0,.69444,.12417,0,.30667],8217:[0,.69444,.12417,0,.30667],8220:[0,.69444,.1685,0,.51444],8221:[0,.69444,.06961,0,.51444],8463:[0,.68889,0,0,.54028]},"Main-Regular":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.27778],34:[0,.69444,0,0,.5],35:[.19444,.69444,0,0,.83334],36:[.05556,.75,0,0,.5],37:[.05556,.75,0,0,.83334],38:[0,.69444,0,0,.77778],39:[0,.69444,0,0,.27778],40:[.25,.75,0,0,.38889],41:[.25,.75,0,0,.38889],42:[0,.75,0,0,.5],43:[.08333,.58333,0,0,.77778],44:[.19444,.10556,0,0,.27778],45:[0,.43056,0,0,.33333],46:[0,.10556,0,0,.27778],47:[.25,.75,0,0,.5],48:[0,.64444,0,0,.5],49:[0,.64444,0,0,.5],50:[0,.64444,0,0,.5],51:[0,.64444,0,0,.5],52:[0,.64444,0,0,.5],53:[0,.64444,0,0,.5],54:[0,.64444,0,0,.5],55:[0,.64444,0,0,.5],56:[0,.64444,0,0,.5],57:[0,.64444,0,0,.5],58:[0,.43056,0,0,.27778],59:[.19444,.43056,0,0,.27778],60:[.0391,.5391,0,0,.77778],61:[-.13313,.36687,0,0,.77778],62:[.0391,.5391,0,0,.77778],63:[0,.69444,0,0,.47222],64:[0,.69444,0,0,.77778],65:[0,.68333,0,0,.75],66:[0,.68333,0,0,.70834],67:[0,.68333,0,0,.72222],68:[0,.68333,0,0,.76389],69:[0,.68333,0,0,.68056],70:[0,.68333,0,0,.65278],71:[0,.68333,0,0,.78472],72:[0,.68333,0,0,.75],73:[0,.68333,0,0,.36111],74:[0,.68333,0,0,.51389],75:[0,.68333,0,0,.77778],76:[0,.68333,0,0,.625],77:[0,.68333,0,0,.91667],78:[0,.68333,0,0,.75],79:[0,.68333,0,0,.77778],80:[0,.68333,0,0,.68056],81:[.19444,.68333,0,0,.77778],82:[0,.68333,0,0,.73611],83:[0,.68333,0,0,.55556],84:[0,.68333,0,0,.72222],85:[0,.68333,0,0,.75],86:[0,.68333,.01389,0,.75],87:[0,.68333,.01389,0,1.02778],88:[0,.68333,0,0,.75],89:[0,.68333,.025,0,.75],90:[0,.68333,0,0,.61111],91:[.25,.75,0,0,.27778],92:[.25,.75,0,0,.5],93:[.25,.75,0,0,.27778],94:[0,.69444,0,0,.5],95:[.31,.12056,.02778,0,.5],97:[0,.43056,0,0,.5],98:[0,.69444,0,0,.55556],99:[0,.43056,0,0,.44445],100:[0,.69444,0,0,.55556],101:[0,.43056,0,0,.44445],102:[0,.69444,.07778,0,.30556],103:[.19444,.43056,.01389,0,.5],104:[0,.69444,0,0,.55556],105:[0,.66786,0,0,.27778],106:[.19444,.66786,0,0,.30556],107:[0,.69444,0,0,.52778],108:[0,.69444,0,0,.27778],109:[0,.43056,0,0,.83334],110:[0,.43056,0,0,.55556],111:[0,.43056,0,0,.5],112:[.19444,.43056,0,0,.55556],113:[.19444,.43056,0,0,.52778],114:[0,.43056,0,0,.39167],115:[0,.43056,0,0,.39445],116:[0,.61508,0,0,.38889],117:[0,.43056,0,0,.55556],118:[0,.43056,.01389,0,.52778],119:[0,.43056,.01389,0,.72222],120:[0,.43056,0,0,.52778],121:[.19444,.43056,.01389,0,.52778],122:[0,.43056,0,0,.44445],123:[.25,.75,0,0,.5],124:[.25,.75,0,0,.27778],125:[.25,.75,0,0,.5],126:[.35,.31786,0,0,.5],160:[0,0,0,0,.25],163:[0,.69444,0,0,.76909],167:[.19444,.69444,0,0,.44445],168:[0,.66786,0,0,.5],172:[0,.43056,0,0,.66667],176:[0,.69444,0,0,.75],177:[.08333,.58333,0,0,.77778],182:[.19444,.69444,0,0,.61111],184:[.17014,0,0,0,.44445],198:[0,.68333,0,0,.90278],215:[.08333,.58333,0,0,.77778],216:[.04861,.73194,0,0,.77778],223:[0,.69444,0,0,.5],230:[0,.43056,0,0,.72222],247:[.08333,.58333,0,0,.77778],248:[.09722,.52778,0,0,.5],305:[0,.43056,0,0,.27778],338:[0,.68333,0,0,1.01389],339:[0,.43056,0,0,.77778],567:[.19444,.43056,0,0,.30556],710:[0,.69444,0,0,.5],711:[0,.62847,0,0,.5],713:[0,.56778,0,0,.5],714:[0,.69444,0,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,0,0,.5],729:[0,.66786,0,0,.27778],730:[0,.69444,0,0,.75],732:[0,.66786,0,0,.5],733:[0,.69444,0,0,.5],915:[0,.68333,0,0,.625],916:[0,.68333,0,0,.83334],920:[0,.68333,0,0,.77778],923:[0,.68333,0,0,.69445],926:[0,.68333,0,0,.66667],928:[0,.68333,0,0,.75],931:[0,.68333,0,0,.72222],933:[0,.68333,0,0,.77778],934:[0,.68333,0,0,.72222],936:[0,.68333,0,0,.77778],937:[0,.68333,0,0,.72222],8211:[0,.43056,.02778,0,.5],8212:[0,.43056,.02778,0,1],8216:[0,.69444,0,0,.27778],8217:[0,.69444,0,0,.27778],8220:[0,.69444,0,0,.5],8221:[0,.69444,0,0,.5],8224:[.19444,.69444,0,0,.44445],8225:[.19444,.69444,0,0,.44445],8230:[0,.123,0,0,1.172],8242:[0,.55556,0,0,.275],8407:[0,.71444,.15382,0,.5],8463:[0,.68889,0,0,.54028],8465:[0,.69444,0,0,.72222],8467:[0,.69444,0,.11111,.41667],8472:[.19444,.43056,0,.11111,.63646],8476:[0,.69444,0,0,.72222],8501:[0,.69444,0,0,.61111],8592:[-.13313,.36687,0,0,1],8593:[.19444,.69444,0,0,.5],8594:[-.13313,.36687,0,0,1],8595:[.19444,.69444,0,0,.5],8596:[-.13313,.36687,0,0,1],8597:[.25,.75,0,0,.5],8598:[.19444,.69444,0,0,1],8599:[.19444,.69444,0,0,1],8600:[.19444,.69444,0,0,1],8601:[.19444,.69444,0,0,1],8614:[.011,.511,0,0,1],8617:[.011,.511,0,0,1.126],8618:[.011,.511,0,0,1.126],8636:[-.13313,.36687,0,0,1],8637:[-.13313,.36687,0,0,1],8640:[-.13313,.36687,0,0,1],8641:[-.13313,.36687,0,0,1],8652:[.011,.671,0,0,1],8656:[-.13313,.36687,0,0,1],8657:[.19444,.69444,0,0,.61111],8658:[-.13313,.36687,0,0,1],8659:[.19444,.69444,0,0,.61111],8660:[-.13313,.36687,0,0,1],8661:[.25,.75,0,0,.61111],8704:[0,.69444,0,0,.55556],8706:[0,.69444,.05556,.08334,.5309],8707:[0,.69444,0,0,.55556],8709:[.05556,.75,0,0,.5],8711:[0,.68333,0,0,.83334],8712:[.0391,.5391,0,0,.66667],8715:[.0391,.5391,0,0,.66667],8722:[.08333,.58333,0,0,.77778],8723:[.08333,.58333,0,0,.77778],8725:[.25,.75,0,0,.5],8726:[.25,.75,0,0,.5],8727:[-.03472,.46528,0,0,.5],8728:[-.05555,.44445,0,0,.5],8729:[-.05555,.44445,0,0,.5],8730:[.2,.8,0,0,.83334],8733:[0,.43056,0,0,.77778],8734:[0,.43056,0,0,1],8736:[0,.69224,0,0,.72222],8739:[.25,.75,0,0,.27778],8741:[.25,.75,0,0,.5],8743:[0,.55556,0,0,.66667],8744:[0,.55556,0,0,.66667],8745:[0,.55556,0,0,.66667],8746:[0,.55556,0,0,.66667],8747:[.19444,.69444,.11111,0,.41667],8764:[-.13313,.36687,0,0,.77778],8768:[.19444,.69444,0,0,.27778],8771:[-.03625,.46375,0,0,.77778],8773:[-.022,.589,0,0,.778],8776:[-.01688,.48312,0,0,.77778],8781:[-.03625,.46375,0,0,.77778],8784:[-.133,.673,0,0,.778],8801:[-.03625,.46375,0,0,.77778],8804:[.13597,.63597,0,0,.77778],8805:[.13597,.63597,0,0,.77778],8810:[.0391,.5391,0,0,1],8811:[.0391,.5391,0,0,1],8826:[.0391,.5391,0,0,.77778],8827:[.0391,.5391,0,0,.77778],8834:[.0391,.5391,0,0,.77778],8835:[.0391,.5391,0,0,.77778],8838:[.13597,.63597,0,0,.77778],8839:[.13597,.63597,0,0,.77778],8846:[0,.55556,0,0,.66667],8849:[.13597,.63597,0,0,.77778],8850:[.13597,.63597,0,0,.77778],8851:[0,.55556,0,0,.66667],8852:[0,.55556,0,0,.66667],8853:[.08333,.58333,0,0,.77778],8854:[.08333,.58333,0,0,.77778],8855:[.08333,.58333,0,0,.77778],8856:[.08333,.58333,0,0,.77778],8857:[.08333,.58333,0,0,.77778],8866:[0,.69444,0,0,.61111],8867:[0,.69444,0,0,.61111],8868:[0,.69444,0,0,.77778],8869:[0,.69444,0,0,.77778],8872:[.249,.75,0,0,.867],8900:[-.05555,.44445,0,0,.5],8901:[-.05555,.44445,0,0,.27778],8902:[-.03472,.46528,0,0,.5],8904:[.005,.505,0,0,.9],8942:[.03,.903,0,0,.278],8943:[-.19,.313,0,0,1.172],8945:[-.1,.823,0,0,1.282],8968:[.25,.75,0,0,.44445],8969:[.25,.75,0,0,.44445],8970:[.25,.75,0,0,.44445],8971:[.25,.75,0,0,.44445],8994:[-.14236,.35764,0,0,1],8995:[-.14236,.35764,0,0,1],9136:[.244,.744,0,0,.412],9137:[.244,.745,0,0,.412],9651:[.19444,.69444,0,0,.88889],9657:[-.03472,.46528,0,0,.5],9661:[.19444,.69444,0,0,.88889],9667:[-.03472,.46528,0,0,.5],9711:[.19444,.69444,0,0,1],9824:[.12963,.69444,0,0,.77778],9825:[.12963,.69444,0,0,.77778],9826:[.12963,.69444,0,0,.77778],9827:[.12963,.69444,0,0,.77778],9837:[0,.75,0,0,.38889],9838:[.19444,.69444,0,0,.38889],9839:[.19444,.69444,0,0,.38889],10216:[.25,.75,0,0,.38889],10217:[.25,.75,0,0,.38889],10222:[.244,.744,0,0,.412],10223:[.244,.745,0,0,.412],10229:[.011,.511,0,0,1.609],10230:[.011,.511,0,0,1.638],10231:[.011,.511,0,0,1.859],10232:[.024,.525,0,0,1.609],10233:[.024,.525,0,0,1.638],10234:[.024,.525,0,0,1.858],10236:[.011,.511,0,0,1.638],10815:[0,.68333,0,0,.75],10927:[.13597,.63597,0,0,.77778],10928:[.13597,.63597,0,0,.77778],57376:[.19444,.69444,0,0,0]},"Math-BoldItalic":{32:[0,0,0,0,.25],48:[0,.44444,0,0,.575],49:[0,.44444,0,0,.575],50:[0,.44444,0,0,.575],51:[.19444,.44444,0,0,.575],52:[.19444,.44444,0,0,.575],53:[.19444,.44444,0,0,.575],54:[0,.64444,0,0,.575],55:[.19444,.44444,0,0,.575],56:[0,.64444,0,0,.575],57:[.19444,.44444,0,0,.575],65:[0,.68611,0,0,.86944],66:[0,.68611,.04835,0,.8664],67:[0,.68611,.06979,0,.81694],68:[0,.68611,.03194,0,.93812],69:[0,.68611,.05451,0,.81007],70:[0,.68611,.15972,0,.68889],71:[0,.68611,0,0,.88673],72:[0,.68611,.08229,0,.98229],73:[0,.68611,.07778,0,.51111],74:[0,.68611,.10069,0,.63125],75:[0,.68611,.06979,0,.97118],76:[0,.68611,0,0,.75555],77:[0,.68611,.11424,0,1.14201],78:[0,.68611,.11424,0,.95034],79:[0,.68611,.03194,0,.83666],80:[0,.68611,.15972,0,.72309],81:[.19444,.68611,0,0,.86861],82:[0,.68611,.00421,0,.87235],83:[0,.68611,.05382,0,.69271],84:[0,.68611,.15972,0,.63663],85:[0,.68611,.11424,0,.80027],86:[0,.68611,.25555,0,.67778],87:[0,.68611,.15972,0,1.09305],88:[0,.68611,.07778,0,.94722],89:[0,.68611,.25555,0,.67458],90:[0,.68611,.06979,0,.77257],97:[0,.44444,0,0,.63287],98:[0,.69444,0,0,.52083],99:[0,.44444,0,0,.51342],100:[0,.69444,0,0,.60972],101:[0,.44444,0,0,.55361],102:[.19444,.69444,.11042,0,.56806],103:[.19444,.44444,.03704,0,.5449],104:[0,.69444,0,0,.66759],105:[0,.69326,0,0,.4048],106:[.19444,.69326,.0622,0,.47083],107:[0,.69444,.01852,0,.6037],108:[0,.69444,.0088,0,.34815],109:[0,.44444,0,0,1.0324],110:[0,.44444,0,0,.71296],111:[0,.44444,0,0,.58472],112:[.19444,.44444,0,0,.60092],113:[.19444,.44444,.03704,0,.54213],114:[0,.44444,.03194,0,.5287],115:[0,.44444,0,0,.53125],116:[0,.63492,0,0,.41528],117:[0,.44444,0,0,.68102],118:[0,.44444,.03704,0,.56666],119:[0,.44444,.02778,0,.83148],120:[0,.44444,0,0,.65903],121:[.19444,.44444,.03704,0,.59028],122:[0,.44444,.04213,0,.55509],160:[0,0,0,0,.25],915:[0,.68611,.15972,0,.65694],916:[0,.68611,0,0,.95833],920:[0,.68611,.03194,0,.86722],923:[0,.68611,0,0,.80555],926:[0,.68611,.07458,0,.84125],928:[0,.68611,.08229,0,.98229],931:[0,.68611,.05451,0,.88507],933:[0,.68611,.15972,0,.67083],934:[0,.68611,0,0,.76666],936:[0,.68611,.11653,0,.71402],937:[0,.68611,.04835,0,.8789],945:[0,.44444,0,0,.76064],946:[.19444,.69444,.03403,0,.65972],947:[.19444,.44444,.06389,0,.59003],948:[0,.69444,.03819,0,.52222],949:[0,.44444,0,0,.52882],950:[.19444,.69444,.06215,0,.50833],951:[.19444,.44444,.03704,0,.6],952:[0,.69444,.03194,0,.5618],953:[0,.44444,0,0,.41204],954:[0,.44444,0,0,.66759],955:[0,.69444,0,0,.67083],956:[.19444,.44444,0,0,.70787],957:[0,.44444,.06898,0,.57685],958:[.19444,.69444,.03021,0,.50833],959:[0,.44444,0,0,.58472],960:[0,.44444,.03704,0,.68241],961:[.19444,.44444,0,0,.6118],962:[.09722,.44444,.07917,0,.42361],963:[0,.44444,.03704,0,.68588],964:[0,.44444,.13472,0,.52083],965:[0,.44444,.03704,0,.63055],966:[.19444,.44444,0,0,.74722],967:[.19444,.44444,0,0,.71805],968:[.19444,.69444,.03704,0,.75833],969:[0,.44444,.03704,0,.71782],977:[0,.69444,0,0,.69155],981:[.19444,.69444,0,0,.7125],982:[0,.44444,.03194,0,.975],1009:[.19444,.44444,0,0,.6118],1013:[0,.44444,0,0,.48333],57649:[0,.44444,0,0,.39352],57911:[.19444,.44444,0,0,.43889]},"Math-Italic":{32:[0,0,0,0,.25],48:[0,.43056,0,0,.5],49:[0,.43056,0,0,.5],50:[0,.43056,0,0,.5],51:[.19444,.43056,0,0,.5],52:[.19444,.43056,0,0,.5],53:[.19444,.43056,0,0,.5],54:[0,.64444,0,0,.5],55:[.19444,.43056,0,0,.5],56:[0,.64444,0,0,.5],57:[.19444,.43056,0,0,.5],65:[0,.68333,0,.13889,.75],66:[0,.68333,.05017,.08334,.75851],67:[0,.68333,.07153,.08334,.71472],68:[0,.68333,.02778,.05556,.82792],69:[0,.68333,.05764,.08334,.7382],70:[0,.68333,.13889,.08334,.64306],71:[0,.68333,0,.08334,.78625],72:[0,.68333,.08125,.05556,.83125],73:[0,.68333,.07847,.11111,.43958],74:[0,.68333,.09618,.16667,.55451],75:[0,.68333,.07153,.05556,.84931],76:[0,.68333,0,.02778,.68056],77:[0,.68333,.10903,.08334,.97014],78:[0,.68333,.10903,.08334,.80347],79:[0,.68333,.02778,.08334,.76278],80:[0,.68333,.13889,.08334,.64201],81:[.19444,.68333,0,.08334,.79056],82:[0,.68333,.00773,.08334,.75929],83:[0,.68333,.05764,.08334,.6132],84:[0,.68333,.13889,.08334,.58438],85:[0,.68333,.10903,.02778,.68278],86:[0,.68333,.22222,0,.58333],87:[0,.68333,.13889,0,.94445],88:[0,.68333,.07847,.08334,.82847],89:[0,.68333,.22222,0,.58056],90:[0,.68333,.07153,.08334,.68264],97:[0,.43056,0,0,.52859],98:[0,.69444,0,0,.42917],99:[0,.43056,0,.05556,.43276],100:[0,.69444,0,.16667,.52049],101:[0,.43056,0,.05556,.46563],102:[.19444,.69444,.10764,.16667,.48959],103:[.19444,.43056,.03588,.02778,.47697],104:[0,.69444,0,0,.57616],105:[0,.65952,0,0,.34451],106:[.19444,.65952,.05724,0,.41181],107:[0,.69444,.03148,0,.5206],108:[0,.69444,.01968,.08334,.29838],109:[0,.43056,0,0,.87801],110:[0,.43056,0,0,.60023],111:[0,.43056,0,.05556,.48472],112:[.19444,.43056,0,.08334,.50313],113:[.19444,.43056,.03588,.08334,.44641],114:[0,.43056,.02778,.05556,.45116],115:[0,.43056,0,.05556,.46875],116:[0,.61508,0,.08334,.36111],117:[0,.43056,0,.02778,.57246],118:[0,.43056,.03588,.02778,.48472],119:[0,.43056,.02691,.08334,.71592],120:[0,.43056,0,.02778,.57153],121:[.19444,.43056,.03588,.05556,.49028],122:[0,.43056,.04398,.05556,.46505],160:[0,0,0,0,.25],915:[0,.68333,.13889,.08334,.61528],916:[0,.68333,0,.16667,.83334],920:[0,.68333,.02778,.08334,.76278],923:[0,.68333,0,.16667,.69445],926:[0,.68333,.07569,.08334,.74236],928:[0,.68333,.08125,.05556,.83125],931:[0,.68333,.05764,.08334,.77986],933:[0,.68333,.13889,.05556,.58333],934:[0,.68333,0,.08334,.66667],936:[0,.68333,.11,.05556,.61222],937:[0,.68333,.05017,.08334,.7724],945:[0,.43056,.0037,.02778,.6397],946:[.19444,.69444,.05278,.08334,.56563],947:[.19444,.43056,.05556,0,.51773],948:[0,.69444,.03785,.05556,.44444],949:[0,.43056,0,.08334,.46632],950:[.19444,.69444,.07378,.08334,.4375],951:[.19444,.43056,.03588,.05556,.49653],952:[0,.69444,.02778,.08334,.46944],953:[0,.43056,0,.05556,.35394],954:[0,.43056,0,0,.57616],955:[0,.69444,0,0,.58334],956:[.19444,.43056,0,.02778,.60255],957:[0,.43056,.06366,.02778,.49398],958:[.19444,.69444,.04601,.11111,.4375],959:[0,.43056,0,.05556,.48472],960:[0,.43056,.03588,0,.57003],961:[.19444,.43056,0,.08334,.51702],962:[.09722,.43056,.07986,.08334,.36285],963:[0,.43056,.03588,0,.57141],964:[0,.43056,.1132,.02778,.43715],965:[0,.43056,.03588,.02778,.54028],966:[.19444,.43056,0,.08334,.65417],967:[.19444,.43056,0,.05556,.62569],968:[.19444,.69444,.03588,.11111,.65139],969:[0,.43056,.03588,0,.62245],977:[0,.69444,0,.08334,.59144],981:[.19444,.69444,0,.08334,.59583],982:[0,.43056,.02778,0,.82813],1009:[.19444,.43056,0,.08334,.51702],1013:[0,.43056,0,.05556,.4059],57649:[0,.43056,0,.02778,.32246],57911:[.19444,.43056,0,.08334,.38403]},"SansSerif-Bold":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.36667],34:[0,.69444,0,0,.55834],35:[.19444,.69444,0,0,.91667],36:[.05556,.75,0,0,.55],37:[.05556,.75,0,0,1.02912],38:[0,.69444,0,0,.83056],39:[0,.69444,0,0,.30556],40:[.25,.75,0,0,.42778],41:[.25,.75,0,0,.42778],42:[0,.75,0,0,.55],43:[.11667,.61667,0,0,.85556],44:[.10556,.13056,0,0,.30556],45:[0,.45833,0,0,.36667],46:[0,.13056,0,0,.30556],47:[.25,.75,0,0,.55],48:[0,.69444,0,0,.55],49:[0,.69444,0,0,.55],50:[0,.69444,0,0,.55],51:[0,.69444,0,0,.55],52:[0,.69444,0,0,.55],53:[0,.69444,0,0,.55],54:[0,.69444,0,0,.55],55:[0,.69444,0,0,.55],56:[0,.69444,0,0,.55],57:[0,.69444,0,0,.55],58:[0,.45833,0,0,.30556],59:[.10556,.45833,0,0,.30556],61:[-.09375,.40625,0,0,.85556],63:[0,.69444,0,0,.51945],64:[0,.69444,0,0,.73334],65:[0,.69444,0,0,.73334],66:[0,.69444,0,0,.73334],67:[0,.69444,0,0,.70278],68:[0,.69444,0,0,.79445],69:[0,.69444,0,0,.64167],70:[0,.69444,0,0,.61111],71:[0,.69444,0,0,.73334],72:[0,.69444,0,0,.79445],73:[0,.69444,0,0,.33056],74:[0,.69444,0,0,.51945],75:[0,.69444,0,0,.76389],76:[0,.69444,0,0,.58056],77:[0,.69444,0,0,.97778],78:[0,.69444,0,0,.79445],79:[0,.69444,0,0,.79445],80:[0,.69444,0,0,.70278],81:[.10556,.69444,0,0,.79445],82:[0,.69444,0,0,.70278],83:[0,.69444,0,0,.61111],84:[0,.69444,0,0,.73334],85:[0,.69444,0,0,.76389],86:[0,.69444,.01528,0,.73334],87:[0,.69444,.01528,0,1.03889],88:[0,.69444,0,0,.73334],89:[0,.69444,.0275,0,.73334],90:[0,.69444,0,0,.67223],91:[.25,.75,0,0,.34306],93:[.25,.75,0,0,.34306],94:[0,.69444,0,0,.55],95:[.35,.10833,.03056,0,.55],97:[0,.45833,0,0,.525],98:[0,.69444,0,0,.56111],99:[0,.45833,0,0,.48889],100:[0,.69444,0,0,.56111],101:[0,.45833,0,0,.51111],102:[0,.69444,.07639,0,.33611],103:[.19444,.45833,.01528,0,.55],104:[0,.69444,0,0,.56111],105:[0,.69444,0,0,.25556],106:[.19444,.69444,0,0,.28611],107:[0,.69444,0,0,.53056],108:[0,.69444,0,0,.25556],109:[0,.45833,0,0,.86667],110:[0,.45833,0,0,.56111],111:[0,.45833,0,0,.55],112:[.19444,.45833,0,0,.56111],113:[.19444,.45833,0,0,.56111],114:[0,.45833,.01528,0,.37222],115:[0,.45833,0,0,.42167],116:[0,.58929,0,0,.40417],117:[0,.45833,0,0,.56111],118:[0,.45833,.01528,0,.5],119:[0,.45833,.01528,0,.74445],120:[0,.45833,0,0,.5],121:[.19444,.45833,.01528,0,.5],122:[0,.45833,0,0,.47639],126:[.35,.34444,0,0,.55],160:[0,0,0,0,.25],168:[0,.69444,0,0,.55],176:[0,.69444,0,0,.73334],180:[0,.69444,0,0,.55],184:[.17014,0,0,0,.48889],305:[0,.45833,0,0,.25556],567:[.19444,.45833,0,0,.28611],710:[0,.69444,0,0,.55],711:[0,.63542,0,0,.55],713:[0,.63778,0,0,.55],728:[0,.69444,0,0,.55],729:[0,.69444,0,0,.30556],730:[0,.69444,0,0,.73334],732:[0,.69444,0,0,.55],733:[0,.69444,0,0,.55],915:[0,.69444,0,0,.58056],916:[0,.69444,0,0,.91667],920:[0,.69444,0,0,.85556],923:[0,.69444,0,0,.67223],926:[0,.69444,0,0,.73334],928:[0,.69444,0,0,.79445],931:[0,.69444,0,0,.79445],933:[0,.69444,0,0,.85556],934:[0,.69444,0,0,.79445],936:[0,.69444,0,0,.85556],937:[0,.69444,0,0,.79445],8211:[0,.45833,.03056,0,.55],8212:[0,.45833,.03056,0,1.10001],8216:[0,.69444,0,0,.30556],8217:[0,.69444,0,0,.30556],8220:[0,.69444,0,0,.55834],8221:[0,.69444,0,0,.55834]},"SansSerif-Italic":{32:[0,0,0,0,.25],33:[0,.69444,.05733,0,.31945],34:[0,.69444,.00316,0,.5],35:[.19444,.69444,.05087,0,.83334],36:[.05556,.75,.11156,0,.5],37:[.05556,.75,.03126,0,.83334],38:[0,.69444,.03058,0,.75834],39:[0,.69444,.07816,0,.27778],40:[.25,.75,.13164,0,.38889],41:[.25,.75,.02536,0,.38889],42:[0,.75,.11775,0,.5],43:[.08333,.58333,.02536,0,.77778],44:[.125,.08333,0,0,.27778],45:[0,.44444,.01946,0,.33333],46:[0,.08333,0,0,.27778],47:[.25,.75,.13164,0,.5],48:[0,.65556,.11156,0,.5],49:[0,.65556,.11156,0,.5],50:[0,.65556,.11156,0,.5],51:[0,.65556,.11156,0,.5],52:[0,.65556,.11156,0,.5],53:[0,.65556,.11156,0,.5],54:[0,.65556,.11156,0,.5],55:[0,.65556,.11156,0,.5],56:[0,.65556,.11156,0,.5],57:[0,.65556,.11156,0,.5],58:[0,.44444,.02502,0,.27778],59:[.125,.44444,.02502,0,.27778],61:[-.13,.37,.05087,0,.77778],63:[0,.69444,.11809,0,.47222],64:[0,.69444,.07555,0,.66667],65:[0,.69444,0,0,.66667],66:[0,.69444,.08293,0,.66667],67:[0,.69444,.11983,0,.63889],68:[0,.69444,.07555,0,.72223],69:[0,.69444,.11983,0,.59722],70:[0,.69444,.13372,0,.56945],71:[0,.69444,.11983,0,.66667],72:[0,.69444,.08094,0,.70834],73:[0,.69444,.13372,0,.27778],74:[0,.69444,.08094,0,.47222],75:[0,.69444,.11983,0,.69445],76:[0,.69444,0,0,.54167],77:[0,.69444,.08094,0,.875],78:[0,.69444,.08094,0,.70834],79:[0,.69444,.07555,0,.73611],80:[0,.69444,.08293,0,.63889],81:[.125,.69444,.07555,0,.73611],82:[0,.69444,.08293,0,.64584],83:[0,.69444,.09205,0,.55556],84:[0,.69444,.13372,0,.68056],85:[0,.69444,.08094,0,.6875],86:[0,.69444,.1615,0,.66667],87:[0,.69444,.1615,0,.94445],88:[0,.69444,.13372,0,.66667],89:[0,.69444,.17261,0,.66667],90:[0,.69444,.11983,0,.61111],91:[.25,.75,.15942,0,.28889],93:[.25,.75,.08719,0,.28889],94:[0,.69444,.0799,0,.5],95:[.35,.09444,.08616,0,.5],97:[0,.44444,.00981,0,.48056],98:[0,.69444,.03057,0,.51667],99:[0,.44444,.08336,0,.44445],100:[0,.69444,.09483,0,.51667],101:[0,.44444,.06778,0,.44445],102:[0,.69444,.21705,0,.30556],103:[.19444,.44444,.10836,0,.5],104:[0,.69444,.01778,0,.51667],105:[0,.67937,.09718,0,.23889],106:[.19444,.67937,.09162,0,.26667],107:[0,.69444,.08336,0,.48889],108:[0,.69444,.09483,0,.23889],109:[0,.44444,.01778,0,.79445],110:[0,.44444,.01778,0,.51667],111:[0,.44444,.06613,0,.5],112:[.19444,.44444,.0389,0,.51667],113:[.19444,.44444,.04169,0,.51667],114:[0,.44444,.10836,0,.34167],115:[0,.44444,.0778,0,.38333],116:[0,.57143,.07225,0,.36111],117:[0,.44444,.04169,0,.51667],118:[0,.44444,.10836,0,.46111],119:[0,.44444,.10836,0,.68334],120:[0,.44444,.09169,0,.46111],121:[.19444,.44444,.10836,0,.46111],122:[0,.44444,.08752,0,.43472],126:[.35,.32659,.08826,0,.5],160:[0,0,0,0,.25],168:[0,.67937,.06385,0,.5],176:[0,.69444,0,0,.73752],184:[.17014,0,0,0,.44445],305:[0,.44444,.04169,0,.23889],567:[.19444,.44444,.04169,0,.26667],710:[0,.69444,.0799,0,.5],711:[0,.63194,.08432,0,.5],713:[0,.60889,.08776,0,.5],714:[0,.69444,.09205,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,.09483,0,.5],729:[0,.67937,.07774,0,.27778],730:[0,.69444,0,0,.73752],732:[0,.67659,.08826,0,.5],733:[0,.69444,.09205,0,.5],915:[0,.69444,.13372,0,.54167],916:[0,.69444,0,0,.83334],920:[0,.69444,.07555,0,.77778],923:[0,.69444,0,0,.61111],926:[0,.69444,.12816,0,.66667],928:[0,.69444,.08094,0,.70834],931:[0,.69444,.11983,0,.72222],933:[0,.69444,.09031,0,.77778],934:[0,.69444,.04603,0,.72222],936:[0,.69444,.09031,0,.77778],937:[0,.69444,.08293,0,.72222],8211:[0,.44444,.08616,0,.5],8212:[0,.44444,.08616,0,1],8216:[0,.69444,.07816,0,.27778],8217:[0,.69444,.07816,0,.27778],8220:[0,.69444,.14205,0,.5],8221:[0,.69444,.00316,0,.5]},"SansSerif-Regular":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.31945],34:[0,.69444,0,0,.5],35:[.19444,.69444,0,0,.83334],36:[.05556,.75,0,0,.5],37:[.05556,.75,0,0,.83334],38:[0,.69444,0,0,.75834],39:[0,.69444,0,0,.27778],40:[.25,.75,0,0,.38889],41:[.25,.75,0,0,.38889],42:[0,.75,0,0,.5],43:[.08333,.58333,0,0,.77778],44:[.125,.08333,0,0,.27778],45:[0,.44444,0,0,.33333],46:[0,.08333,0,0,.27778],47:[.25,.75,0,0,.5],48:[0,.65556,0,0,.5],49:[0,.65556,0,0,.5],50:[0,.65556,0,0,.5],51:[0,.65556,0,0,.5],52:[0,.65556,0,0,.5],53:[0,.65556,0,0,.5],54:[0,.65556,0,0,.5],55:[0,.65556,0,0,.5],56:[0,.65556,0,0,.5],57:[0,.65556,0,0,.5],58:[0,.44444,0,0,.27778],59:[.125,.44444,0,0,.27778],61:[-.13,.37,0,0,.77778],63:[0,.69444,0,0,.47222],64:[0,.69444,0,0,.66667],65:[0,.69444,0,0,.66667],66:[0,.69444,0,0,.66667],67:[0,.69444,0,0,.63889],68:[0,.69444,0,0,.72223],69:[0,.69444,0,0,.59722],70:[0,.69444,0,0,.56945],71:[0,.69444,0,0,.66667],72:[0,.69444,0,0,.70834],73:[0,.69444,0,0,.27778],74:[0,.69444,0,0,.47222],75:[0,.69444,0,0,.69445],76:[0,.69444,0,0,.54167],77:[0,.69444,0,0,.875],78:[0,.69444,0,0,.70834],79:[0,.69444,0,0,.73611],80:[0,.69444,0,0,.63889],81:[.125,.69444,0,0,.73611],82:[0,.69444,0,0,.64584],83:[0,.69444,0,0,.55556],84:[0,.69444,0,0,.68056],85:[0,.69444,0,0,.6875],86:[0,.69444,.01389,0,.66667],87:[0,.69444,.01389,0,.94445],88:[0,.69444,0,0,.66667],89:[0,.69444,.025,0,.66667],90:[0,.69444,0,0,.61111],91:[.25,.75,0,0,.28889],93:[.25,.75,0,0,.28889],94:[0,.69444,0,0,.5],95:[.35,.09444,.02778,0,.5],97:[0,.44444,0,0,.48056],98:[0,.69444,0,0,.51667],99:[0,.44444,0,0,.44445],100:[0,.69444,0,0,.51667],101:[0,.44444,0,0,.44445],102:[0,.69444,.06944,0,.30556],103:[.19444,.44444,.01389,0,.5],104:[0,.69444,0,0,.51667],105:[0,.67937,0,0,.23889],106:[.19444,.67937,0,0,.26667],107:[0,.69444,0,0,.48889],108:[0,.69444,0,0,.23889],109:[0,.44444,0,0,.79445],110:[0,.44444,0,0,.51667],111:[0,.44444,0,0,.5],112:[.19444,.44444,0,0,.51667],113:[.19444,.44444,0,0,.51667],114:[0,.44444,.01389,0,.34167],115:[0,.44444,0,0,.38333],116:[0,.57143,0,0,.36111],117:[0,.44444,0,0,.51667],118:[0,.44444,.01389,0,.46111],119:[0,.44444,.01389,0,.68334],120:[0,.44444,0,0,.46111],121:[.19444,.44444,.01389,0,.46111],122:[0,.44444,0,0,.43472],126:[.35,.32659,0,0,.5],160:[0,0,0,0,.25],168:[0,.67937,0,0,.5],176:[0,.69444,0,0,.66667],184:[.17014,0,0,0,.44445],305:[0,.44444,0,0,.23889],567:[.19444,.44444,0,0,.26667],710:[0,.69444,0,0,.5],711:[0,.63194,0,0,.5],713:[0,.60889,0,0,.5],714:[0,.69444,0,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,0,0,.5],729:[0,.67937,0,0,.27778],730:[0,.69444,0,0,.66667],732:[0,.67659,0,0,.5],733:[0,.69444,0,0,.5],915:[0,.69444,0,0,.54167],916:[0,.69444,0,0,.83334],920:[0,.69444,0,0,.77778],923:[0,.69444,0,0,.61111],926:[0,.69444,0,0,.66667],928:[0,.69444,0,0,.70834],931:[0,.69444,0,0,.72222],933:[0,.69444,0,0,.77778],934:[0,.69444,0,0,.72222],936:[0,.69444,0,0,.77778],937:[0,.69444,0,0,.72222],8211:[0,.44444,.02778,0,.5],8212:[0,.44444,.02778,0,1],8216:[0,.69444,0,0,.27778],8217:[0,.69444,0,0,.27778],8220:[0,.69444,0,0,.5],8221:[0,.69444,0,0,.5]},"Script-Regular":{32:[0,0,0,0,.25],65:[0,.7,.22925,0,.80253],66:[0,.7,.04087,0,.90757],67:[0,.7,.1689,0,.66619],68:[0,.7,.09371,0,.77443],69:[0,.7,.18583,0,.56162],70:[0,.7,.13634,0,.89544],71:[0,.7,.17322,0,.60961],72:[0,.7,.29694,0,.96919],73:[0,.7,.19189,0,.80907],74:[.27778,.7,.19189,0,1.05159],75:[0,.7,.31259,0,.91364],76:[0,.7,.19189,0,.87373],77:[0,.7,.15981,0,1.08031],78:[0,.7,.3525,0,.9015],79:[0,.7,.08078,0,.73787],80:[0,.7,.08078,0,1.01262],81:[0,.7,.03305,0,.88282],82:[0,.7,.06259,0,.85],83:[0,.7,.19189,0,.86767],84:[0,.7,.29087,0,.74697],85:[0,.7,.25815,0,.79996],86:[0,.7,.27523,0,.62204],87:[0,.7,.27523,0,.80532],88:[0,.7,.26006,0,.94445],89:[0,.7,.2939,0,.70961],90:[0,.7,.24037,0,.8212],160:[0,0,0,0,.25]},"Size1-Regular":{32:[0,0,0,0,.25],40:[.35001,.85,0,0,.45834],41:[.35001,.85,0,0,.45834],47:[.35001,.85,0,0,.57778],91:[.35001,.85,0,0,.41667],92:[.35001,.85,0,0,.57778],93:[.35001,.85,0,0,.41667],123:[.35001,.85,0,0,.58334],125:[.35001,.85,0,0,.58334],160:[0,0,0,0,.25],710:[0,.72222,0,0,.55556],732:[0,.72222,0,0,.55556],770:[0,.72222,0,0,.55556],771:[0,.72222,0,0,.55556],8214:[-99e-5,.601,0,0,.77778],8593:[1e-5,.6,0,0,.66667],8595:[1e-5,.6,0,0,.66667],8657:[1e-5,.6,0,0,.77778],8659:[1e-5,.6,0,0,.77778],8719:[.25001,.75,0,0,.94445],8720:[.25001,.75,0,0,.94445],8721:[.25001,.75,0,0,1.05556],8730:[.35001,.85,0,0,1],8739:[-.00599,.606,0,0,.33333],8741:[-.00599,.606,0,0,.55556],8747:[.30612,.805,.19445,0,.47222],8748:[.306,.805,.19445,0,.47222],8749:[.306,.805,.19445,0,.47222],8750:[.30612,.805,.19445,0,.47222],8896:[.25001,.75,0,0,.83334],8897:[.25001,.75,0,0,.83334],8898:[.25001,.75,0,0,.83334],8899:[.25001,.75,0,0,.83334],8968:[.35001,.85,0,0,.47222],8969:[.35001,.85,0,0,.47222],8970:[.35001,.85,0,0,.47222],8971:[.35001,.85,0,0,.47222],9168:[-99e-5,.601,0,0,.66667],10216:[.35001,.85,0,0,.47222],10217:[.35001,.85,0,0,.47222],10752:[.25001,.75,0,0,1.11111],10753:[.25001,.75,0,0,1.11111],10754:[.25001,.75,0,0,1.11111],10756:[.25001,.75,0,0,.83334],10758:[.25001,.75,0,0,.83334]},"Size2-Regular":{32:[0,0,0,0,.25],40:[.65002,1.15,0,0,.59722],41:[.65002,1.15,0,0,.59722],47:[.65002,1.15,0,0,.81111],91:[.65002,1.15,0,0,.47222],92:[.65002,1.15,0,0,.81111],93:[.65002,1.15,0,0,.47222],123:[.65002,1.15,0,0,.66667],125:[.65002,1.15,0,0,.66667],160:[0,0,0,0,.25],710:[0,.75,0,0,1],732:[0,.75,0,0,1],770:[0,.75,0,0,1],771:[0,.75,0,0,1],8719:[.55001,1.05,0,0,1.27778],8720:[.55001,1.05,0,0,1.27778],8721:[.55001,1.05,0,0,1.44445],8730:[.65002,1.15,0,0,1],8747:[.86225,1.36,.44445,0,.55556],8748:[.862,1.36,.44445,0,.55556],8749:[.862,1.36,.44445,0,.55556],8750:[.86225,1.36,.44445,0,.55556],8896:[.55001,1.05,0,0,1.11111],8897:[.55001,1.05,0,0,1.11111],8898:[.55001,1.05,0,0,1.11111],8899:[.55001,1.05,0,0,1.11111],8968:[.65002,1.15,0,0,.52778],8969:[.65002,1.15,0,0,.52778],8970:[.65002,1.15,0,0,.52778],8971:[.65002,1.15,0,0,.52778],10216:[.65002,1.15,0,0,.61111],10217:[.65002,1.15,0,0,.61111],10752:[.55001,1.05,0,0,1.51112],10753:[.55001,1.05,0,0,1.51112],10754:[.55001,1.05,0,0,1.51112],10756:[.55001,1.05,0,0,1.11111],10758:[.55001,1.05,0,0,1.11111]},"Size3-Regular":{32:[0,0,0,0,.25],40:[.95003,1.45,0,0,.73611],41:[.95003,1.45,0,0,.73611],47:[.95003,1.45,0,0,1.04445],91:[.95003,1.45,0,0,.52778],92:[.95003,1.45,0,0,1.04445],93:[.95003,1.45,0,0,.52778],123:[.95003,1.45,0,0,.75],125:[.95003,1.45,0,0,.75],160:[0,0,0,0,.25],710:[0,.75,0,0,1.44445],732:[0,.75,0,0,1.44445],770:[0,.75,0,0,1.44445],771:[0,.75,0,0,1.44445],8730:[.95003,1.45,0,0,1],8968:[.95003,1.45,0,0,.58334],8969:[.95003,1.45,0,0,.58334],8970:[.95003,1.45,0,0,.58334],8971:[.95003,1.45,0,0,.58334],10216:[.95003,1.45,0,0,.75],10217:[.95003,1.45,0,0,.75]},"Size4-Regular":{32:[0,0,0,0,.25],40:[1.25003,1.75,0,0,.79167],41:[1.25003,1.75,0,0,.79167],47:[1.25003,1.75,0,0,1.27778],91:[1.25003,1.75,0,0,.58334],92:[1.25003,1.75,0,0,1.27778],93:[1.25003,1.75,0,0,.58334],123:[1.25003,1.75,0,0,.80556],125:[1.25003,1.75,0,0,.80556],160:[0,0,0,0,.25],710:[0,.825,0,0,1.8889],732:[0,.825,0,0,1.8889],770:[0,.825,0,0,1.8889],771:[0,.825,0,0,1.8889],8730:[1.25003,1.75,0,0,1],8968:[1.25003,1.75,0,0,.63889],8969:[1.25003,1.75,0,0,.63889],8970:[1.25003,1.75,0,0,.63889],8971:[1.25003,1.75,0,0,.63889],9115:[.64502,1.155,0,0,.875],9116:[1e-5,.6,0,0,.875],9117:[.64502,1.155,0,0,.875],9118:[.64502,1.155,0,0,.875],9119:[1e-5,.6,0,0,.875],9120:[.64502,1.155,0,0,.875],9121:[.64502,1.155,0,0,.66667],9122:[-99e-5,.601,0,0,.66667],9123:[.64502,1.155,0,0,.66667],9124:[.64502,1.155,0,0,.66667],9125:[-99e-5,.601,0,0,.66667],9126:[.64502,1.155,0,0,.66667],9127:[1e-5,.9,0,0,.88889],9128:[.65002,1.15,0,0,.88889],9129:[.90001,0,0,0,.88889],9130:[0,.3,0,0,.88889],9131:[1e-5,.9,0,0,.88889],9132:[.65002,1.15,0,0,.88889],9133:[.90001,0,0,0,.88889],9143:[.88502,.915,0,0,1.05556],10216:[1.25003,1.75,0,0,.80556],10217:[1.25003,1.75,0,0,.80556],57344:[-.00499,.605,0,0,1.05556],57345:[-.00499,.605,0,0,1.05556],57680:[0,.12,0,0,.45],57681:[0,.12,0,0,.45],57682:[0,.12,0,0,.45],57683:[0,.12,0,0,.45]},"Typewriter-Regular":{32:[0,0,0,0,.525],33:[0,.61111,0,0,.525],34:[0,.61111,0,0,.525],35:[0,.61111,0,0,.525],36:[.08333,.69444,0,0,.525],37:[.08333,.69444,0,0,.525],38:[0,.61111,0,0,.525],39:[0,.61111,0,0,.525],40:[.08333,.69444,0,0,.525],41:[.08333,.69444,0,0,.525],42:[0,.52083,0,0,.525],43:[-.08056,.53055,0,0,.525],44:[.13889,.125,0,0,.525],45:[-.08056,.53055,0,0,.525],46:[0,.125,0,0,.525],47:[.08333,.69444,0,0,.525],48:[0,.61111,0,0,.525],49:[0,.61111,0,0,.525],50:[0,.61111,0,0,.525],51:[0,.61111,0,0,.525],52:[0,.61111,0,0,.525],53:[0,.61111,0,0,.525],54:[0,.61111,0,0,.525],55:[0,.61111,0,0,.525],56:[0,.61111,0,0,.525],57:[0,.61111,0,0,.525],58:[0,.43056,0,0,.525],59:[.13889,.43056,0,0,.525],60:[-.05556,.55556,0,0,.525],61:[-.19549,.41562,0,0,.525],62:[-.05556,.55556,0,0,.525],63:[0,.61111,0,0,.525],64:[0,.61111,0,0,.525],65:[0,.61111,0,0,.525],66:[0,.61111,0,0,.525],67:[0,.61111,0,0,.525],68:[0,.61111,0,0,.525],69:[0,.61111,0,0,.525],70:[0,.61111,0,0,.525],71:[0,.61111,0,0,.525],72:[0,.61111,0,0,.525],73:[0,.61111,0,0,.525],74:[0,.61111,0,0,.525],75:[0,.61111,0,0,.525],76:[0,.61111,0,0,.525],77:[0,.61111,0,0,.525],78:[0,.61111,0,0,.525],79:[0,.61111,0,0,.525],80:[0,.61111,0,0,.525],81:[.13889,.61111,0,0,.525],82:[0,.61111,0,0,.525],83:[0,.61111,0,0,.525],84:[0,.61111,0,0,.525],85:[0,.61111,0,0,.525],86:[0,.61111,0,0,.525],87:[0,.61111,0,0,.525],88:[0,.61111,0,0,.525],89:[0,.61111,0,0,.525],90:[0,.61111,0,0,.525],91:[.08333,.69444,0,0,.525],92:[.08333,.69444,0,0,.525],93:[.08333,.69444,0,0,.525],94:[0,.61111,0,0,.525],95:[.09514,0,0,0,.525],96:[0,.61111,0,0,.525],97:[0,.43056,0,0,.525],98:[0,.61111,0,0,.525],99:[0,.43056,0,0,.525],100:[0,.61111,0,0,.525],101:[0,.43056,0,0,.525],102:[0,.61111,0,0,.525],103:[.22222,.43056,0,0,.525],104:[0,.61111,0,0,.525],105:[0,.61111,0,0,.525],106:[.22222,.61111,0,0,.525],107:[0,.61111,0,0,.525],108:[0,.61111,0,0,.525],109:[0,.43056,0,0,.525],110:[0,.43056,0,0,.525],111:[0,.43056,0,0,.525],112:[.22222,.43056,0,0,.525],113:[.22222,.43056,0,0,.525],114:[0,.43056,0,0,.525],115:[0,.43056,0,0,.525],116:[0,.55358,0,0,.525],117:[0,.43056,0,0,.525],118:[0,.43056,0,0,.525],119:[0,.43056,0,0,.525],120:[0,.43056,0,0,.525],121:[.22222,.43056,0,0,.525],122:[0,.43056,0,0,.525],123:[.08333,.69444,0,0,.525],124:[.08333,.69444,0,0,.525],125:[.08333,.69444,0,0,.525],126:[0,.61111,0,0,.525],127:[0,.61111,0,0,.525],160:[0,0,0,0,.525],176:[0,.61111,0,0,.525],184:[.19445,0,0,0,.525],305:[0,.43056,0,0,.525],567:[.22222,.43056,0,0,.525],711:[0,.56597,0,0,.525],713:[0,.56555,0,0,.525],714:[0,.61111,0,0,.525],715:[0,.61111,0,0,.525],728:[0,.61111,0,0,.525],730:[0,.61111,0,0,.525],770:[0,.61111,0,0,.525],771:[0,.61111,0,0,.525],776:[0,.61111,0,0,.525],915:[0,.61111,0,0,.525],916:[0,.61111,0,0,.525],920:[0,.61111,0,0,.525],923:[0,.61111,0,0,.525],926:[0,.61111,0,0,.525],928:[0,.61111,0,0,.525],931:[0,.61111,0,0,.525],933:[0,.61111,0,0,.525],934:[0,.61111,0,0,.525],936:[0,.61111,0,0,.525],937:[0,.61111,0,0,.525],8216:[0,.61111,0,0,.525],8217:[0,.61111,0,0,.525],8242:[0,.61111,0,0,.525],9251:[.11111,.21944,0,0,.525]}},hh={slant:[.25,.25,.25],space:[0,0,0],stretch:[0,0,0],shrink:[0,0,0],xHeight:[.431,.431,.431],quad:[1,1.171,1.472],extraSpace:[0,0,0],num1:[.677,.732,.925],num2:[.394,.384,.387],num3:[.444,.471,.504],denom1:[.686,.752,1.025],denom2:[.345,.344,.532],sup1:[.413,.503,.504],sup2:[.363,.431,.404],sup3:[.289,.286,.294],sub1:[.15,.143,.2],sub2:[.247,.286,.4],supDrop:[.386,.353,.494],subDrop:[.05,.071,.1],delim1:[2.39,1.7,1.98],delim2:[1.01,1.157,1.42],axisHeight:[.25,.25,.25],defaultRuleThickness:[.04,.049,.049],bigOpSpacing1:[.111,.111,.111],bigOpSpacing2:[.166,.166,.166],bigOpSpacing3:[.2,.2,.2],bigOpSpacing4:[.6,.611,.611],bigOpSpacing5:[.1,.143,.143],sqrtRuleThickness:[.04,.04,.04],ptPerEm:[10,10,10],doubleRuleSep:[.2,.2,.2],arrayRuleWidth:[.04,.04,.04],fboxsep:[.3,.3,.3],fboxrule:[.04,.04,.04]},O6={\u00C5:"A",\u00D0:"D",\u00DE:"o",\u00E5:"a",\u00F0:"d",\u00FE:"o",\u0410:"A",\u0411:"B",\u0412:"B",\u0413:"F",\u0414:"A",\u0415:"E",\u0416:"K",\u0417:"3",\u0418:"N",\u0419:"N",\u041A:"K",\u041B:"N",\u041C:"M",\u041D:"H",\u041E:"O",\u041F:"N",\u0420:"P",\u0421:"C",\u0422:"T",\u0423:"y",\u0424:"O",\u0425:"X",\u0426:"U",\u0427:"h",\u0428:"W",\u0429:"W",\u042A:"B",\u042B:"X",\u042C:"B",\u042D:"3",\u042E:"X",\u042F:"R",\u0430:"a",\u0431:"b",\u0432:"a",\u0433:"r",\u0434:"y",\u0435:"e",\u0436:"m",\u0437:"e",\u0438:"n",\u0439:"n",\u043A:"n",\u043B:"n",\u043C:"m",\u043D:"n",\u043E:"o",\u043F:"n",\u0440:"p",\u0441:"c",\u0442:"o",\u0443:"y",\u0444:"b",\u0445:"x",\u0446:"n",\u0447:"n",\u0448:"w",\u0449:"w",\u044A:"a",\u044B:"m",\u044C:"a",\u044D:"e",\u044E:"m",\u044F:"r"};Zu={};IS=[[1,1,1],[2,1,1],[3,1,1],[4,2,1],[5,2,1],[6,3,1],[7,4,2],[8,6,3],[9,7,6],[10,8,7],[11,10,9]],B6=[.5,.6,.7,.8,.9,1,1.2,1.44,1.728,2.074,2.488],P6=function(e,t){return t.size<2?e:IS[e-1][t.size-1]},Th=class i{constructor(e){this.style=void 0,this.color=void 0,this.size=void 0,this.textSize=void 0,this.phantom=void 0,this.font=void 0,this.fontFamily=void 0,this.fontWeight=void 0,this.fontShape=void 0,this.sizeMultiplier=void 0,this.maxSize=void 0,this.minRuleThickness=void 0,this._fontMetrics=void 0,this.style=e.style,this.color=e.color,this.size=e.size||i.BASESIZE,this.textSize=e.textSize||this.size,this.phantom=!!e.phantom,this.font=e.font||"",this.fontFamily=e.fontFamily||"",this.fontWeight=e.fontWeight||"",this.fontShape=e.fontShape||"",this.sizeMultiplier=B6[this.size-1],this.maxSize=e.maxSize,this.minRuleThickness=e.minRuleThickness,this._fontMetrics=void 0}extend(e){var t={style:this.style,size:this.size,textSize:this.textSize,color:this.color,phantom:this.phantom,font:this.font,fontFamily:this.fontFamily,fontWeight:this.fontWeight,fontShape:this.fontShape,maxSize:this.maxSize,minRuleThickness:this.minRuleThickness};for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);return new i(t)}havingStyle(e){return this.style===e?this:this.extend({style:e,size:P6(this.textSize,e)})}havingCrampedStyle(){return this.havingStyle(this.style.cramp())}havingSize(e){return this.size===e&&this.textSize===e?this:this.extend({style:this.style.text(),size:e,textSize:e,sizeMultiplier:B6[e-1]})}havingBaseStyle(e){e=e||this.style.text();var t=P6(i.BASESIZE,e);return this.size===t&&this.textSize===i.BASESIZE&&this.style===e?this:this.extend({style:e,size:t})}havingBaseSizing(){var e;switch(this.style.id){case 4:case 5:e=3;break;case 6:case 7:e=1;break;default:e=6}return this.extend({style:this.style.text(),size:e})}withColor(e){return this.extend({color:e})}withPhantom(){return this.extend({phantom:!0})}withFont(e){return this.extend({font:e})}withTextFontFamily(e){return this.extend({fontFamily:e,font:""})}withTextFontWeight(e){return this.extend({fontWeight:e,font:""})}withTextFontShape(e){return this.extend({fontShape:e,font:""})}sizingClasses(e){return e.size!==this.size?["sizing","reset-size"+e.size,"size"+this.size]:[]}baseSizingClasses(){return this.size!==i.BASESIZE?["sizing","reset-size"+this.size,"size"+i.BASESIZE]:[]}fontMetrics(){return this._fontMetrics||(this._fontMetrics=LS(this.size)),this._fontMetrics}getColor(){return this.phantom?"transparent":this.color}};Th.BASESIZE=6;c1={pt:1,mm:7227/2540,cm:7227/254,in:72.27,bp:803/800,pc:12,dd:1238/1157,cc:14856/1157,nd:685/642,nc:1370/107,sp:1/65536,px:803/800},zS={ex:!0,em:!0,mu:!0},m4=function(e){return typeof e!="string"&&(e=e.unit),e in c1||e in zS||e==="ex"},Xe=function(e,t){var r;if(e.unit in c1)r=c1[e.unit]/t.fontMetrics().ptPerEm/t.sizeMultiplier;else if(e.unit==="mu")r=t.fontMetrics().cssEmPerMu;else{var n;if(t.style.isTight()?n=t.havingStyle(t.style.text()):n=t,e.unit==="ex")r=n.fontMetrics().xHeight;else if(e.unit==="em")r=n.fontMetrics().quad;else throw new G("Invalid unit: '"+e.unit+"'");n!==t&&(r*=n.sizeMultiplier/t.sizeMultiplier)}return Math.min(e.number*r,t.maxSize)},V=function(e){return+e.toFixed(4)+"em"},yn=function(e){return e.filter(t=>t).join(" ")},p4=function(e,t,r){if(this.classes=e||[],this.attributes={},this.height=0,this.depth=0,this.maxFontSize=0,this.style=r||{},t){t.style.isTight()&&this.classes.push("mtight");var n=t.getColor();n&&(this.style.color=n)}},g4=function(e){var t=document.createElement(e);t.className=yn(this.classes);for(var r in this.style)this.style.hasOwnProperty(r)&&(t.style[r]=this.style[r]);for(var n in this.attributes)this.attributes.hasOwnProperty(n)&&t.setAttribute(n,this.attributes[n]);for(var s=0;s/=\x00-\x1f]/,x4=function(e){var t="<"+e;this.classes.length&&(t+=' class="'+Ne.escape(yn(this.classes))+'"');var r="";for(var n in this.style)this.style.hasOwnProperty(n)&&(r+=Ne.hyphenate(n)+":"+this.style[n]+";");r&&(t+=' style="'+Ne.escape(r)+'"');for(var s in this.attributes)if(this.attributes.hasOwnProperty(s)){if(RS.test(s))throw new G("Invalid attribute name '"+s+"'");t+=" "+s+'="'+Ne.escape(this.attributes[s])+'"'}t+=">";for(var a=0;a",t},ps=class{constructor(e,t,r,n){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.width=void 0,this.maxFontSize=void 0,this.style=void 0,p4.call(this,e,r,n),this.children=t||[]}setAttribute(e,t){this.attributes[e]=t}hasClass(e){return this.classes.includes(e)}toNode(){return g4.call(this,"span")}toMarkup(){return x4.call(this,"span")}},el=class{constructor(e,t,r,n){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,p4.call(this,t,n),this.children=r||[],this.setAttribute("href",e)}setAttribute(e,t){this.attributes[e]=t}hasClass(e){return this.classes.includes(e)}toNode(){return g4.call(this,"a")}toMarkup(){return x4.call(this,"a")}},u1=class{constructor(e,t,r){this.src=void 0,this.alt=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.alt=t,this.src=e,this.classes=["mord"],this.style=r}hasClass(e){return this.classes.includes(e)}toNode(){var e=document.createElement("img");e.src=this.src,e.alt=this.alt,e.className="mord";for(var t in this.style)this.style.hasOwnProperty(t)&&(e.style[t]=this.style[t]);return e}toMarkup(){var e=''+Ne.escape(this.alt)+'0&&(t=document.createElement("span"),t.style.marginRight=V(this.italic)),this.classes.length>0&&(t=t||document.createElement("span"),t.className=yn(this.classes));for(var r in this.style)this.style.hasOwnProperty(r)&&(t=t||document.createElement("span"),t.style[r]=this.style[r]);return t?(t.appendChild(e),t):e}toMarkup(){var e=!1,t="0&&(r+="margin-right:"+this.italic+"em;");for(var n in this.style)this.style.hasOwnProperty(n)&&(r+=Ne.hyphenate(n)+":"+this.style[n]+";");r&&(e=!0,t+=' style="'+Ne.escape(r)+'"');var s=Ne.escape(this.text);return e?(t+=">",t+=s,t+="",t):s}},Xi=class{constructor(e,t){this.children=void 0,this.attributes=void 0,this.children=e||[],this.attributes=t||{}}toNode(){var e="http://www.w3.org/2000/svg",t=document.createElementNS(e,"svg");for(var r in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,r)&&t.setAttribute(r,this.attributes[r]);for(var n=0;n':''}},tl=class{constructor(e){this.attributes=void 0,this.attributes=e||{}}toNode(){var e="http://www.w3.org/2000/svg",t=document.createElementNS(e,"line");for(var r in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,r)&&t.setAttribute(r,this.attributes[r]);return t}toMarkup(){var e="","\\gt",!0);u(p,v,E,"\u2208","\\in",!0);u(p,v,E,"\uE020","\\@not");u(p,v,E,"\u2282","\\subset",!0);u(p,v,E,"\u2283","\\supset",!0);u(p,v,E,"\u2286","\\subseteq",!0);u(p,v,E,"\u2287","\\supseteq",!0);u(p,N,E,"\u2288","\\nsubseteq",!0);u(p,N,E,"\u2289","\\nsupseteq",!0);u(p,v,E,"\u22A8","\\models");u(p,v,E,"\u2190","\\leftarrow",!0);u(p,v,E,"\u2264","\\le");u(p,v,E,"\u2264","\\leq",!0);u(p,v,E,"<","\\lt",!0);u(p,v,E,"\u2192","\\rightarrow",!0);u(p,v,E,"\u2192","\\to");u(p,N,E,"\u2271","\\ngeq",!0);u(p,N,E,"\u2270","\\nleq",!0);u(p,v,Dr,"\xA0","\\ ");u(p,v,Dr,"\xA0","\\space");u(p,v,Dr,"\xA0","\\nobreakspace");u(H,v,Dr,"\xA0","\\ ");u(H,v,Dr,"\xA0"," ");u(H,v,Dr,"\xA0","\\space");u(H,v,Dr,"\xA0","\\nobreakspace");u(p,v,Dr,null,"\\nobreak");u(p,v,Dr,null,"\\allowbreak");u(p,v,_h,",",",");u(p,v,_h,";",";");u(p,N,ie,"\u22BC","\\barwedge",!0);u(p,N,ie,"\u22BB","\\veebar",!0);u(p,v,ie,"\u2299","\\odot",!0);u(p,v,ie,"\u2295","\\oplus",!0);u(p,v,ie,"\u2297","\\otimes",!0);u(p,v,_,"\u2202","\\partial",!0);u(p,v,ie,"\u2298","\\oslash",!0);u(p,N,ie,"\u229A","\\circledcirc",!0);u(p,N,ie,"\u22A1","\\boxdot",!0);u(p,v,ie,"\u25B3","\\bigtriangleup");u(p,v,ie,"\u25BD","\\bigtriangledown");u(p,v,ie,"\u2020","\\dagger");u(p,v,ie,"\u22C4","\\diamond");u(p,v,ie,"\u22C6","\\star");u(p,v,ie,"\u25C3","\\triangleleft");u(p,v,ie,"\u25B9","\\triangleright");u(p,v,fi,"{","\\{");u(H,v,_,"{","\\{");u(H,v,_,"{","\\textbraceleft");u(p,v,Ft,"}","\\}");u(H,v,_,"}","\\}");u(H,v,_,"}","\\textbraceright");u(p,v,fi,"{","\\lbrace");u(p,v,Ft,"}","\\rbrace");u(p,v,fi,"[","\\lbrack",!0);u(H,v,_,"[","\\lbrack",!0);u(p,v,Ft,"]","\\rbrack",!0);u(H,v,_,"]","\\rbrack",!0);u(p,v,fi,"(","\\lparen",!0);u(p,v,Ft,")","\\rparen",!0);u(H,v,_,"<","\\textless",!0);u(H,v,_,">","\\textgreater",!0);u(p,v,fi,"\u230A","\\lfloor",!0);u(p,v,Ft,"\u230B","\\rfloor",!0);u(p,v,fi,"\u2308","\\lceil",!0);u(p,v,Ft,"\u2309","\\rceil",!0);u(p,v,_,"\\","\\backslash");u(p,v,_,"\u2223","|");u(p,v,_,"\u2223","\\vert");u(H,v,_,"|","\\textbar",!0);u(p,v,_,"\u2225","\\|");u(p,v,_,"\u2225","\\Vert");u(H,v,_,"\u2225","\\textbardbl");u(H,v,_,"~","\\textasciitilde");u(H,v,_,"\\","\\textbackslash");u(H,v,_,"^","\\textasciicircum");u(p,v,E,"\u2191","\\uparrow",!0);u(p,v,E,"\u21D1","\\Uparrow",!0);u(p,v,E,"\u2193","\\downarrow",!0);u(p,v,E,"\u21D3","\\Downarrow",!0);u(p,v,E,"\u2195","\\updownarrow",!0);u(p,v,E,"\u21D5","\\Updownarrow",!0);u(p,v,ot,"\u2210","\\coprod");u(p,v,ot,"\u22C1","\\bigvee");u(p,v,ot,"\u22C0","\\bigwedge");u(p,v,ot,"\u2A04","\\biguplus");u(p,v,ot,"\u22C2","\\bigcap");u(p,v,ot,"\u22C3","\\bigcup");u(p,v,ot,"\u222B","\\int");u(p,v,ot,"\u222B","\\intop");u(p,v,ot,"\u222C","\\iint");u(p,v,ot,"\u222D","\\iiint");u(p,v,ot,"\u220F","\\prod");u(p,v,ot,"\u2211","\\sum");u(p,v,ot,"\u2A02","\\bigotimes");u(p,v,ot,"\u2A01","\\bigoplus");u(p,v,ot,"\u2A00","\\bigodot");u(p,v,ot,"\u222E","\\oint");u(p,v,ot,"\u222F","\\oiint");u(p,v,ot,"\u2230","\\oiiint");u(p,v,ot,"\u2A06","\\bigsqcup");u(p,v,ot,"\u222B","\\smallint");u(H,v,ma,"\u2026","\\textellipsis");u(p,v,ma,"\u2026","\\mathellipsis");u(H,v,ma,"\u2026","\\ldots",!0);u(p,v,ma,"\u2026","\\ldots",!0);u(p,v,ma,"\u22EF","\\@cdots",!0);u(p,v,ma,"\u22F1","\\ddots",!0);u(p,v,_,"\u22EE","\\varvdots");u(H,v,_,"\u22EE","\\varvdots");u(p,v,je,"\u02CA","\\acute");u(p,v,je,"\u02CB","\\grave");u(p,v,je,"\xA8","\\ddot");u(p,v,je,"~","\\tilde");u(p,v,je,"\u02C9","\\bar");u(p,v,je,"\u02D8","\\breve");u(p,v,je,"\u02C7","\\check");u(p,v,je,"^","\\hat");u(p,v,je,"\u20D7","\\vec");u(p,v,je,"\u02D9","\\dot");u(p,v,je,"\u02DA","\\mathring");u(p,v,se,"\uE131","\\@imath");u(p,v,se,"\uE237","\\@jmath");u(p,v,_,"\u0131","\u0131");u(p,v,_,"\u0237","\u0237");u(H,v,_,"\u0131","\\i",!0);u(H,v,_,"\u0237","\\j",!0);u(H,v,_,"\xDF","\\ss",!0);u(H,v,_,"\xE6","\\ae",!0);u(H,v,_,"\u0153","\\oe",!0);u(H,v,_,"\xF8","\\o",!0);u(H,v,_,"\xC6","\\AE",!0);u(H,v,_,"\u0152","\\OE",!0);u(H,v,_,"\xD8","\\O",!0);u(H,v,je,"\u02CA","\\'");u(H,v,je,"\u02CB","\\`");u(H,v,je,"\u02C6","\\^");u(H,v,je,"\u02DC","\\~");u(H,v,je,"\u02C9","\\=");u(H,v,je,"\u02D8","\\u");u(H,v,je,"\u02D9","\\.");u(H,v,je,"\xB8","\\c");u(H,v,je,"\u02DA","\\r");u(H,v,je,"\u02C7","\\v");u(H,v,je,"\xA8",'\\"');u(H,v,je,"\u02DD","\\H");u(H,v,je,"\u25EF","\\textcircled");y4={"--":!0,"---":!0,"``":!0,"''":!0};u(H,v,_,"\u2013","--",!0);u(H,v,_,"\u2013","\\textendash");u(H,v,_,"\u2014","---",!0);u(H,v,_,"\u2014","\\textemdash");u(H,v,_,"\u2018","`",!0);u(H,v,_,"\u2018","\\textquoteleft");u(H,v,_,"\u2019","'",!0);u(H,v,_,"\u2019","\\textquoteright");u(H,v,_,"\u201C","``",!0);u(H,v,_,"\u201C","\\textquotedblleft");u(H,v,_,"\u201D","''",!0);u(H,v,_,"\u201D","\\textquotedblright");u(p,v,_,"\xB0","\\degree",!0);u(H,v,_,"\xB0","\\degree");u(H,v,_,"\xB0","\\textdegree",!0);u(p,v,_,"\xA3","\\pounds");u(p,v,_,"\xA3","\\mathsterling",!0);u(H,v,_,"\xA3","\\pounds");u(H,v,_,"\xA3","\\textsterling",!0);u(p,N,_,"\u2720","\\maltese");u(H,N,_,"\u2720","\\maltese");q6='0123456789/@."';for(dh=0;dh0)return Yi(s,h,n,t,a.concat(d));if(l){var c,f;if(l==="boldsymbol"){var m=HS(s,n,t,a,r);c=m.fontName,f=[m.fontClass]}else o?(c=w4[l].fontName,f=[l]):(c=ph(l,t.fontWeight,t.fontShape),f=[l,t.fontWeight,t.fontShape]);if(Lh(s,c,n).metrics)return Yi(s,c,n,t,a.concat(f));if(y4.hasOwnProperty(s)&&c.slice(0,10)==="Typewriter"){for(var g=[],x=0;x{if(yn(i.classes)!==yn(e.classes)||i.skew!==e.skew||i.maxFontSize!==e.maxFontSize)return!1;if(i.classes.length===1){var t=i.classes[0];if(t==="mbin"||t==="mord")return!1}for(var r in i.style)if(i.style.hasOwnProperty(r)&&i.style[r]!==e.style[r])return!1;for(var n in e.style)if(e.style.hasOwnProperty(n)&&i.style[n]!==e.style[n])return!1;return!0},jS=i=>{for(var e=0;et&&(t=a.height),a.depth>r&&(r=a.depth),a.maxFontSize>n&&(n=a.maxFontSize)}e.height=t,e.depth=r,e.maxFontSize=n},Qt=function(e,t,r,n){var s=new ps(e,t,r,n);return N1(s),s},v4=(i,e,t,r)=>new ps(i,e,t,r),GS=function(e,t,r){var n=Qt([e],[],t);return n.height=Math.max(r||t.fontMetrics().defaultRuleThickness,t.minRuleThickness),n.style.borderBottomWidth=V(n.height),n.maxFontSize=1,n},VS=function(e,t,r,n){var s=new el(e,t,r,n);return N1(s),s},b4=function(e){var t=new ms(e);return N1(t),t},WS=function(e,t){return e instanceof ms?Qt([],[e],t):e},YS=function(e){if(e.positionType==="individualShift"){for(var t=e.children,r=[t[0]],n=-t[0].shift-t[0].elem.depth,s=n,a=1;a{var t=Qt(["mspace"],[],e),r=Xe(i,e);return t.style.marginRight=V(r),t},ph=function(e,t,r){var n="";switch(e){case"amsrm":n="AMS";break;case"textrm":n="Main";break;case"textsf":n="SansSerif";break;case"texttt":n="Typewriter";break;default:n=e}var s;return t==="textbf"&&r==="textit"?s="BoldItalic":t==="textbf"?s="Bold":t==="textit"?s="Italic":s="Regular",n+"-"+s},w4={mathbf:{variant:"bold",fontName:"Main-Bold"},mathrm:{variant:"normal",fontName:"Main-Regular"},textit:{variant:"italic",fontName:"Main-Italic"},mathit:{variant:"italic",fontName:"Main-Italic"},mathnormal:{variant:"italic",fontName:"Math-Italic"},mathsfit:{variant:"sans-serif-italic",fontName:"SansSerif-Italic"},mathbb:{variant:"double-struck",fontName:"AMS-Regular"},mathcal:{variant:"script",fontName:"Caligraphic-Regular"},mathfrak:{variant:"fraktur",fontName:"Fraktur-Regular"},mathscr:{variant:"script",fontName:"Script-Regular"},mathsf:{variant:"sans-serif",fontName:"SansSerif-Regular"},mathtt:{variant:"monospace",fontName:"Typewriter-Regular"}},M4={vec:["vec",.471,.714],oiintSize1:["oiintSize1",.957,.499],oiintSize2:["oiintSize2",1.472,.659],oiiintSize1:["oiiintSize1",1.304,.499],oiiintSize2:["oiiintSize2",1.98,.659]},ZS=function(e,t){var[r,n,s]=M4[e],a=new cr(r),o=new Xi([a],{width:V(n),height:V(s),style:"width:"+V(n),viewBox:"0 0 "+1e3*n+" "+1e3*s,preserveAspectRatio:"xMinYMin"}),l=v4(["overlay"],[o],t);return l.height=s,l.style.height=V(s),l.style.width=V(n),l},z={fontMap:w4,makeSymbol:Yi,mathsym:qS,makeSpan:Qt,makeSvgSpan:v4,makeLineSpan:GS,makeAnchor:VS,makeFragment:b4,wrapFragment:WS,makeVList:XS,makeOrd:US,makeGlue:KS,staticSvg:ZS,svgData:M4,tryCombineChars:jS},Ye={number:3,unit:"mu"},fs={number:4,unit:"mu"},_r={number:5,unit:"mu"},QS={mord:{mop:Ye,mbin:fs,mrel:_r,minner:Ye},mop:{mord:Ye,mop:Ye,mrel:_r,minner:Ye},mbin:{mord:fs,mop:fs,mopen:fs,minner:fs},mrel:{mord:_r,mop:_r,mopen:_r,minner:_r},mopen:{},mclose:{mop:Ye,mbin:fs,mrel:_r,minner:Ye},mpunct:{mord:Ye,mop:Ye,mrel:_r,mopen:Ye,mclose:Ye,mpunct:Ye,minner:Ye},minner:{mord:Ye,mop:Ye,mbin:fs,mrel:_r,mopen:Ye,mpunct:Ye,minner:Ye}},JS={mord:{mop:Ye},mop:{mord:Ye,mop:Ye},mbin:{},mrel:{},mopen:{},mclose:{mop:Ye},mpunct:{},minner:{mop:Ye}},T4={},Eh={},Sh={};Ah=function(e){return e.type==="ordgroup"&&e.body.length===1?e.body[0]:e},rt=function(e){return e.type==="ordgroup"?e.body:[e]},zr=z.makeSpan,eA=["leftmost","mbin","mopen","mrel","mop","mpunct"],tA=["rightmost","mrel","mclose","mpunct"],iA={display:ae.DISPLAY,text:ae.TEXT,script:ae.SCRIPT,scriptscript:ae.SCRIPTSCRIPT},rA={mord:"mord",mop:"mop",mbin:"mbin",mrel:"mrel",mopen:"mopen",mclose:"mclose",mpunct:"mpunct",minner:"minner"},ct=function(e,t,r,n){n===void 0&&(n=[null,null]);for(var s=[],a=0;a{var y=x.classes[0],b=g.classes[0];y==="mbin"&&tA.includes(b)?x.classes[0]="mord":b==="mbin"&&eA.includes(y)&&(g.classes[0]="mord")},{node:c},f,m),$6(s,(g,x)=>{var y=m1(x),b=m1(g),S=y&&b?g.hasClass("mtight")?JS[y][b]:QS[y][b]:null;if(S)return z.makeGlue(S,h)},{node:c},f,m),s},$6=function i(e,t,r,n,s){n&&e.push(n);for(var a=0;af=>{e.splice(c+1,0,f),a++})(a)}n&&e.pop()},N4=function(e){return e instanceof ms||e instanceof el||e instanceof ps&&e.hasClass("enclosing")?e:null},nA=function i(e,t){var r=N4(e);if(r){var n=r.children;if(n.length){if(t==="right")return i(n[n.length-1],"right");if(t==="left")return i(n[0],"left")}}return e},m1=function(e,t){return e?(t&&(e=nA(e,t)),rA[e.classes[0]]||null):null},il=function(e,t){var r=["nulldelimiter"].concat(e.baseSizingClasses());return zr(t.concat(r))},Me=function(e,t,r){if(!e)return zr();if(Eh[e.type]){var n=Eh[e.type](e,t);if(r&&t.size!==r.size){n=zr(t.sizingClasses(r),[n],t);var s=t.sizeMultiplier/r.sizeMultiplier;n.height*=s,n.depth*=s}return n}else throw new G("Got group of unknown type: '"+e.type+"'")};Bt=class{constructor(e,t,r){this.type=void 0,this.attributes=void 0,this.children=void 0,this.classes=void 0,this.type=e,this.attributes={},this.children=t||[],this.classes=r||[]}setAttribute(e,t){this.attributes[e]=t}getAttribute(e){return this.attributes[e]}toNode(){var e=document.createElementNS("http://www.w3.org/1998/Math/MathML",this.type);for(var t in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,t)&&e.setAttribute(t,this.attributes[t]);this.classes.length>0&&(e.className=yn(this.classes));for(var r=0;r0&&(e+=' class ="'+Ne.escape(yn(this.classes))+'"'),e+=">";for(var r=0;r",e}toText(){return this.children.map(e=>e.toText()).join("")}},ki=class{constructor(e){this.text=void 0,this.text=e}toNode(){return document.createTextNode(this.text)}toMarkup(){return Ne.escape(this.toText())}toText(){return this.text}},g1=class{constructor(e){this.width=void 0,this.character=void 0,this.width=e,e>=.05555&&e<=.05556?this.character="\u200A":e>=.1666&&e<=.1667?this.character="\u2009":e>=.2222&&e<=.2223?this.character="\u2005":e>=.2777&&e<=.2778?this.character="\u2005\u200A":e>=-.05556&&e<=-.05555?this.character="\u200A\u2063":e>=-.1667&&e<=-.1666?this.character="\u2009\u2063":e>=-.2223&&e<=-.2222?this.character="\u205F\u2063":e>=-.2778&&e<=-.2777?this.character="\u2005\u2063":this.character=null}toNode(){if(this.character)return document.createTextNode(this.character);var e=document.createElementNS("http://www.w3.org/1998/Math/MathML","mspace");return e.setAttribute("width",V(this.width)),e}toMarkup(){return this.character?""+this.character+"":''}toText(){return this.character?this.character:" "}},$={MathNode:Bt,TextNode:ki,SpaceNode:g1,newDocumentFragment:E4},Ci=function(e,t,r){return Oe[t][e]&&Oe[t][e].replace&&e.charCodeAt(0)!==55349&&!(y4.hasOwnProperty(e)&&r&&(r.fontFamily&&r.fontFamily.slice(4,6)==="tt"||r.font&&r.font.slice(4,6)==="tt"))&&(e=Oe[t][e].replace),new $.TextNode(e)},E1=function(e){return e.length===1?e[0]:new $.MathNode("mrow",e)},S1=function(e,t){if(t.fontFamily==="texttt")return"monospace";if(t.fontFamily==="textsf")return t.fontShape==="textit"&&t.fontWeight==="textbf"?"sans-serif-bold-italic":t.fontShape==="textit"?"sans-serif-italic":t.fontWeight==="textbf"?"bold-sans-serif":"sans-serif";if(t.fontShape==="textit"&&t.fontWeight==="textbf")return"bold-italic";if(t.fontShape==="textit")return"italic";if(t.fontWeight==="textbf")return"bold";var r=t.font;if(!r||r==="mathnormal")return null;var n=e.mode;if(r==="mathit")return"italic";if(r==="boldsymbol")return e.type==="textord"?"bold":"bold-italic";if(r==="mathbf")return"bold";if(r==="mathbb")return"double-struck";if(r==="mathsfit")return"sans-serif-italic";if(r==="mathfrak")return"fraktur";if(r==="mathscr"||r==="mathcal")return"script";if(r==="mathsf")return"sans-serif";if(r==="mathtt")return"monospace";var s=e.text;if(["\\imath","\\jmath"].includes(s))return null;Oe[n][s]&&Oe[n][s].replace&&(s=Oe[n][s].replace);var a=z.fontMap[r].fontName;return T1(s,a,n)?z.fontMap[r].variant:null};ti=function(e,t,r){if(e.length===1){var n=ze(e[0],t);return r&&n instanceof Bt&&n.type==="mo"&&(n.setAttribute("lspace","0em"),n.setAttribute("rspace","0em")),[n]}for(var s=[],a,o=0;o=1&&(a.type==="mn"||e1(a))){var h=l.children[0];h instanceof Bt&&h.type==="mn"&&(h.children=[...a.children,...h.children],s.pop())}else if(a.type==="mi"&&a.children.length===1){var d=a.children[0];if(d instanceof ki&&d.text==="\u0338"&&(l.type==="mo"||l.type==="mi"||l.type==="mn")){var c=l.children[0];c instanceof ki&&c.text.length>0&&(c.text=c.text.slice(0,1)+"\u0338"+c.text.slice(1),s.pop())}}}s.push(l),a=l}return s},vn=function(e,t,r){return E1(ti(e,t,r))},ze=function(e,t){if(!e)return new $.MathNode("mrow");if(Sh[e.type]){var r=Sh[e.type](e,t);return r}else throw new G("Got group of unknown type: '"+e.type+"'")};S4=function(e){return new Th({style:e.displayMode?ae.DISPLAY:ae.TEXT,maxSize:e.maxSize,minRuleThickness:e.minRuleThickness})},A4=function(e,t){if(t.displayMode){var r=["katex-display"];t.leqno&&r.push("leqno"),t.fleqn&&r.push("fleqn"),e=z.makeSpan(r,[e])}return e},sA=function(e,t,r){var n=S4(r),s;if(r.output==="mathml")return j6(e,t,n,r.displayMode,!0);if(r.output==="html"){var a=p1(e,n);s=z.makeSpan(["katex"],[a])}else{var o=j6(e,t,n,r.displayMode,!1),l=p1(e,n);s=z.makeSpan(["katex"],[o,l])}return A4(s,r)},aA=function(e,t,r){var n=S4(r),s=p1(e,n),a=z.makeSpan(["katex"],[s]);return A4(a,r)},oA={widehat:"^",widecheck:"\u02C7",widetilde:"~",utilde:"~",overleftarrow:"\u2190",underleftarrow:"\u2190",xleftarrow:"\u2190",overrightarrow:"\u2192",underrightarrow:"\u2192",xrightarrow:"\u2192",underbrace:"\u23DF",overbrace:"\u23DE",overgroup:"\u23E0",undergroup:"\u23E1",overleftrightarrow:"\u2194",underleftrightarrow:"\u2194",xleftrightarrow:"\u2194",Overrightarrow:"\u21D2",xRightarrow:"\u21D2",overleftharpoon:"\u21BC",xleftharpoonup:"\u21BC",overrightharpoon:"\u21C0",xrightharpoonup:"\u21C0",xLeftarrow:"\u21D0",xLeftrightarrow:"\u21D4",xhookleftarrow:"\u21A9",xhookrightarrow:"\u21AA",xmapsto:"\u21A6",xrightharpoondown:"\u21C1",xleftharpoondown:"\u21BD",xrightleftharpoons:"\u21CC",xleftrightharpoons:"\u21CB",xtwoheadleftarrow:"\u219E",xtwoheadrightarrow:"\u21A0",xlongequal:"=",xtofrom:"\u21C4",xrightleftarrows:"\u21C4",xrightequilibrium:"\u21CC",xleftequilibrium:"\u21CB","\\cdrightarrow":"\u2192","\\cdleftarrow":"\u2190","\\cdlongequal":"="},lA=function(e){var t=new $.MathNode("mo",[new $.TextNode(oA[e.replace(/^\\/,"")])]);return t.setAttribute("stretchy","true"),t},hA={overrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],overleftarrow:[["leftarrow"],.888,522,"xMinYMin"],underrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],underleftarrow:[["leftarrow"],.888,522,"xMinYMin"],xrightarrow:[["rightarrow"],1.469,522,"xMaxYMin"],"\\cdrightarrow":[["rightarrow"],3,522,"xMaxYMin"],xleftarrow:[["leftarrow"],1.469,522,"xMinYMin"],"\\cdleftarrow":[["leftarrow"],3,522,"xMinYMin"],Overrightarrow:[["doublerightarrow"],.888,560,"xMaxYMin"],xRightarrow:[["doublerightarrow"],1.526,560,"xMaxYMin"],xLeftarrow:[["doubleleftarrow"],1.526,560,"xMinYMin"],overleftharpoon:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoonup:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoondown:[["leftharpoondown"],.888,522,"xMinYMin"],overrightharpoon:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoonup:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoondown:[["rightharpoondown"],.888,522,"xMaxYMin"],xlongequal:[["longequal"],.888,334,"xMinYMin"],"\\cdlongequal":[["longequal"],3,334,"xMinYMin"],xtwoheadleftarrow:[["twoheadleftarrow"],.888,334,"xMinYMin"],xtwoheadrightarrow:[["twoheadrightarrow"],.888,334,"xMaxYMin"],overleftrightarrow:[["leftarrow","rightarrow"],.888,522],overbrace:[["leftbrace","midbrace","rightbrace"],1.6,548],underbrace:[["leftbraceunder","midbraceunder","rightbraceunder"],1.6,548],underleftrightarrow:[["leftarrow","rightarrow"],.888,522],xleftrightarrow:[["leftarrow","rightarrow"],1.75,522],xLeftrightarrow:[["doubleleftarrow","doublerightarrow"],1.75,560],xrightleftharpoons:[["leftharpoondownplus","rightharpoonplus"],1.75,716],xleftrightharpoons:[["leftharpoonplus","rightharpoondownplus"],1.75,716],xhookleftarrow:[["leftarrow","righthook"],1.08,522],xhookrightarrow:[["lefthook","rightarrow"],1.08,522],overlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],underlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],overgroup:[["leftgroup","rightgroup"],.888,342],undergroup:[["leftgroupunder","rightgroupunder"],.888,342],xmapsto:[["leftmapsto","rightarrow"],1.5,522],xtofrom:[["leftToFrom","rightToFrom"],1.75,528],xrightleftarrows:[["baraboveleftarrow","rightarrowabovebar"],1.75,901],xrightequilibrium:[["baraboveshortleftharpoon","rightharpoonaboveshortbar"],1.75,716],xleftequilibrium:[["shortbaraboveleftharpoon","shortrightharpoonabovebar"],1.75,716]},dA=function(e){return e.type==="ordgroup"?e.body.length:1},cA=function(e,t){function r(){var o=4e5,l=e.label.slice(1);if(["widehat","widecheck","widetilde","utilde"].includes(l)){var h=e,d=dA(h.base),c,f,m;if(d>5)l==="widehat"||l==="widecheck"?(c=420,o=2364,m=.42,f=l+"4"):(c=312,o=2340,m=.34,f="tilde4");else{var g=[1,1,2,2,3,3][d];l==="widehat"||l==="widecheck"?(o=[0,1062,2364,2364,2364][g],c=[0,239,300,360,420][g],m=[0,.24,.3,.3,.36,.42][g],f=l+g):(o=[0,600,1033,2339,2340][g],c=[0,260,286,306,312][g],m=[0,.26,.286,.3,.306,.34][g],f="tilde"+g)}var x=new cr(f),y=new Xi([x],{width:"100%",height:V(m),viewBox:"0 0 "+o+" "+c,preserveAspectRatio:"none"});return{span:z.makeSvgSpan([],[y],t),minWidth:0,height:m}}else{var b=[],S=hA[l],[A,C,I]=S,O=I/1e3,q=A.length,P,W;if(q===1){var ne=S[3];P=["hide-tail"],W=[ne]}else if(q===2)P=["halfarrow-left","halfarrow-right"],W=["xMinYMin","xMaxYMin"];else if(q===3)P=["brace-left","brace-center","brace-right"],W=["xMinYMin","xMidYMin","xMaxYMin"];else throw new Error(`Correct katexImagesData or update code here to support - `+q+" children.");for(var re=0;re0&&(n.style.minWidth=V(s)),n},uA=function(e,t,r,n,s){var a,o=e.height+e.depth+r+n;if(/fbox|color|angl/.test(t)){if(a=z.makeSpan(["stretchy",t],[],s),t==="fbox"){var l=s.color&&s.getColor();l&&(a.style.borderColor=l)}}else{var h=[];/^[bx]cancel$/.test(t)&&h.push(new tl({x1:"0",y1:"0",x2:"100%",y2:"100%","stroke-width":"0.046em"})),/^x?cancel$/.test(t)&&h.push(new tl({x1:"0",y1:"100%",x2:"100%",y2:"0","stroke-width":"0.046em"}));var d=new Xi(h,{width:"100%",height:V(o)});a=z.makeSvgSpan([],[d],s)}return a.height=o,a.style.height=V(o),a},Rr={encloseSpan:uA,mathMLnode:lA,svgSpan:cA};k1=(i,e)=>{var t,r,n;i&&i.type==="supsub"?(r=me(i.base,"accent"),t=r.base,i.base=t,n=OS(Me(i,e)),i.base=r):(r=me(i,"accent"),t=r.base);var s=Me(t,e.havingCrampedStyle()),a=r.isShifty&&Ne.isCharacterBox(t),o=0;if(a){var l=Ne.getBaseElem(t),h=Me(l,e.havingCrampedStyle());o=F6(h).skew}var d=r.label==="\\c",c=d?s.height+s.depth:Math.min(s.height,e.fontMetrics().xHeight),f;if(r.isStretchy)f=Rr.svgSpan(r,e),f=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"elem",elem:f,wrapperClasses:["svg-align"],wrapperStyle:o>0?{width:"calc(100% - "+V(2*o)+")",marginLeft:V(2*o)}:void 0}]},e);else{var m,g;r.label==="\\vec"?(m=z.staticSvg("vec",e),g=z.svgData.vec[1]):(m=z.makeOrd({mode:r.mode,text:r.label},e,"textord"),m=F6(m),m.italic=0,g=m.width,d&&(c+=m.depth)),f=z.makeSpan(["accent-body"],[m]);var x=r.label==="\\textcircled";x&&(f.classes.push("accent-full"),c=s.height);var y=o;x||(y-=g/2),f.style.left=V(y),r.label==="\\textcircled"&&(f.style.top=".2em"),f=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"kern",size:-c},{type:"elem",elem:f}]},e)}var b=z.makeSpan(["mord","accent"],[f],e);return n?(n.children[0]=b,n.height=Math.max(b.height,n.height),n.classes[0]="mord",n):b},k4=(i,e)=>{var t=i.isStretchy?Rr.mathMLnode(i.label):new $.MathNode("mo",[Ci(i.label,i.mode)]),r=new $.MathNode("mover",[ze(i.base,e),t]);return r.setAttribute("accent","true"),r},fA=new RegExp(["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring"].map(i=>"\\"+i).join("|"));Z({type:"accent",names:["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring","\\widecheck","\\widehat","\\widetilde","\\overrightarrow","\\overleftarrow","\\Overrightarrow","\\overleftrightarrow","\\overgroup","\\overlinesegment","\\overleftharpoon","\\overrightharpoon"],props:{numArgs:1},handler:(i,e)=>{var t=Ah(e[0]),r=!fA.test(i.funcName),n=!r||i.funcName==="\\widehat"||i.funcName==="\\widetilde"||i.funcName==="\\widecheck";return{type:"accent",mode:i.parser.mode,label:i.funcName,isStretchy:r,isShifty:n,base:t}},htmlBuilder:k1,mathmlBuilder:k4});Z({type:"accent",names:["\\'","\\`","\\^","\\~","\\=","\\u","\\.",'\\"',"\\c","\\r","\\H","\\v","\\textcircled"],props:{numArgs:1,allowedInText:!0,allowedInMath:!0,argTypes:["primitive"]},handler:(i,e)=>{var t=e[0],r=i.parser.mode;return r==="math"&&(i.parser.settings.reportNonstrict("mathVsTextAccents","LaTeX's accent "+i.funcName+" works only in text mode"),r="text"),{type:"accent",mode:r,label:i.funcName,isStretchy:!1,isShifty:!0,base:t}},htmlBuilder:k1,mathmlBuilder:k4});Z({type:"accentUnder",names:["\\underleftarrow","\\underrightarrow","\\underleftrightarrow","\\undergroup","\\underlinesegment","\\utilde"],props:{numArgs:1},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0];return{type:"accentUnder",mode:t.mode,label:r,base:n}},htmlBuilder:(i,e)=>{var t=Me(i.base,e),r=Rr.svgSpan(i,e),n=i.label==="\\utilde"?.12:0,s=z.makeVList({positionType:"top",positionData:t.height,children:[{type:"elem",elem:r,wrapperClasses:["svg-align"]},{type:"kern",size:n},{type:"elem",elem:t}]},e);return z.makeSpan(["mord","accentunder"],[s],e)},mathmlBuilder:(i,e)=>{var t=Rr.mathMLnode(i.label),r=new $.MathNode("munder",[ze(i.base,e),t]);return r.setAttribute("accentunder","true"),r}});xh=i=>{var e=new $.MathNode("mpadded",i?[i]:[]);return e.setAttribute("width","+0.6em"),e.setAttribute("lspace","0.3em"),e};Z({type:"xArrow",names:["\\xleftarrow","\\xrightarrow","\\xLeftarrow","\\xRightarrow","\\xleftrightarrow","\\xLeftrightarrow","\\xhookleftarrow","\\xhookrightarrow","\\xmapsto","\\xrightharpoondown","\\xrightharpoonup","\\xleftharpoondown","\\xleftharpoonup","\\xrightleftharpoons","\\xleftrightharpoons","\\xlongequal","\\xtwoheadrightarrow","\\xtwoheadleftarrow","\\xtofrom","\\xrightleftarrows","\\xrightequilibrium","\\xleftequilibrium","\\\\cdrightarrow","\\\\cdleftarrow","\\\\cdlongequal"],props:{numArgs:1,numOptionalArgs:1},handler(i,e,t){var{parser:r,funcName:n}=i;return{type:"xArrow",mode:r.mode,label:n,body:e[0],below:t[0]}},htmlBuilder(i,e){var t=e.style,r=e.havingStyle(t.sup()),n=z.wrapFragment(Me(i.body,r,e),e),s=i.label.slice(0,2)==="\\x"?"x":"cd";n.classes.push(s+"-arrow-pad");var a;i.below&&(r=e.havingStyle(t.sub()),a=z.wrapFragment(Me(i.below,r,e),e),a.classes.push(s+"-arrow-pad"));var o=Rr.svgSpan(i,e),l=-e.fontMetrics().axisHeight+.5*o.height,h=-e.fontMetrics().axisHeight-.5*o.height-.111;(n.depth>.25||i.label==="\\xleftequilibrium")&&(h-=n.depth);var d;if(a){var c=-e.fontMetrics().axisHeight+a.height+.5*o.height+.111;d=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:n,shift:h},{type:"elem",elem:o,shift:l},{type:"elem",elem:a,shift:c}]},e)}else d=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:n,shift:h},{type:"elem",elem:o,shift:l}]},e);return d.children[0].children[0].children[1].classes.push("svg-align"),z.makeSpan(["mrel","x-arrow"],[d],e)},mathmlBuilder(i,e){var t=Rr.mathMLnode(i.label);t.setAttribute("minsize",i.label.charAt(0)==="x"?"1.75em":"3.0em");var r;if(i.body){var n=xh(ze(i.body,e));if(i.below){var s=xh(ze(i.below,e));r=new $.MathNode("munderover",[t,s,n])}else r=new $.MathNode("mover",[t,n])}else if(i.below){var a=xh(ze(i.below,e));r=new $.MathNode("munder",[t,a])}else r=xh(),r=new $.MathNode("mover",[t,r]);return r}});mA=z.makeSpan;Z({type:"mclass",names:["\\mathord","\\mathbin","\\mathrel","\\mathopen","\\mathclose","\\mathpunct","\\mathinner"],props:{numArgs:1,primitive:!0},handler(i,e){var{parser:t,funcName:r}=i,n=e[0];return{type:"mclass",mode:t.mode,mclass:"m"+r.slice(5),body:rt(n),isCharacterBox:Ne.isCharacterBox(n)}},htmlBuilder:C4,mathmlBuilder:_4});zh=i=>{var e=i.type==="ordgroup"&&i.body.length?i.body[0]:i;return e.type==="atom"&&(e.family==="bin"||e.family==="rel")?"m"+e.family:"mord"};Z({type:"mclass",names:["\\@binrel"],props:{numArgs:2},handler(i,e){var{parser:t}=i;return{type:"mclass",mode:t.mode,mclass:zh(e[0]),body:rt(e[1]),isCharacterBox:Ne.isCharacterBox(e[1])}}});Z({type:"mclass",names:["\\stackrel","\\overset","\\underset"],props:{numArgs:2},handler(i,e){var{parser:t,funcName:r}=i,n=e[1],s=e[0],a;r!=="\\stackrel"?a=zh(n):a="mrel";var o={type:"op",mode:n.mode,limits:!0,alwaysHandleSupSub:!0,parentIsSupSub:!1,symbol:!1,suppressBaseShift:r!=="\\stackrel",body:rt(n)},l={type:"supsub",mode:s.mode,base:o,sup:r==="\\underset"?null:s,sub:r==="\\underset"?s:null};return{type:"mclass",mode:t.mode,mclass:a,body:[l],isCharacterBox:Ne.isCharacterBox(l)}},htmlBuilder:C4,mathmlBuilder:_4});Z({type:"pmb",names:["\\pmb"],props:{numArgs:1,allowedInText:!0},handler(i,e){var{parser:t}=i;return{type:"pmb",mode:t.mode,mclass:zh(e[0]),body:rt(e[0])}},htmlBuilder(i,e){var t=ct(i.body,e,!0),r=z.makeSpan([i.mclass],t,e);return r.style.textShadow="0.02em 0.01em 0.04px",r},mathmlBuilder(i,e){var t=ti(i.body,e),r=new $.MathNode("mstyle",t);return r.setAttribute("style","text-shadow: 0.02em 0.01em 0.04px"),r}});pA={">":"\\\\cdrightarrow","<":"\\\\cdleftarrow","=":"\\\\cdlongequal",A:"\\uparrow",V:"\\downarrow","|":"\\Vert",".":"no arrow"},G6=()=>({type:"styling",body:[],mode:"math",style:"display"}),V6=i=>i.type==="textord"&&i.text==="@",gA=(i,e)=>(i.type==="mathord"||i.type==="atom")&&i.text===e;Z({type:"cdlabel",names:["\\\\cdleft","\\\\cdright"],props:{numArgs:1},handler(i,e){var{parser:t,funcName:r}=i;return{type:"cdlabel",mode:t.mode,side:r.slice(4),label:e[0]}},htmlBuilder(i,e){var t=e.havingStyle(e.style.sup()),r=z.wrapFragment(Me(i.label,t,e),e);return r.classes.push("cd-label-"+i.side),r.style.bottom=V(.8-r.depth),r.height=0,r.depth=0,r},mathmlBuilder(i,e){var t=new $.MathNode("mrow",[ze(i.label,e)]);return t=new $.MathNode("mpadded",[t]),t.setAttribute("width","0"),i.side==="left"&&t.setAttribute("lspace","-1width"),t.setAttribute("voffset","0.7em"),t=new $.MathNode("mstyle",[t]),t.setAttribute("displaystyle","false"),t.setAttribute("scriptlevel","1"),t}});Z({type:"cdlabelparent",names:["\\\\cdparent"],props:{numArgs:1},handler(i,e){var{parser:t}=i;return{type:"cdlabelparent",mode:t.mode,fragment:e[0]}},htmlBuilder(i,e){var t=z.wrapFragment(Me(i.fragment,e),e);return t.classes.push("cd-vert-arrow"),t},mathmlBuilder(i,e){return new $.MathNode("mrow",[ze(i.fragment,e)])}});Z({type:"textord",names:["\\@char"],props:{numArgs:1,allowedInText:!0},handler(i,e){for(var{parser:t}=i,r=me(e[0],"ordgroup"),n=r.body,s="",a=0;a=1114111)throw new G("\\@char with invalid code point "+s);return l<=65535?h=String.fromCharCode(l):(l-=65536,h=String.fromCharCode((l>>10)+55296,(l&1023)+56320)),{type:"textord",mode:t.mode,text:h}}});L4=(i,e)=>{var t=ct(i.body,e.withColor(i.color),!1);return z.makeFragment(t)},I4=(i,e)=>{var t=ti(i.body,e.withColor(i.color)),r=new $.MathNode("mstyle",t);return r.setAttribute("mathcolor",i.color),r};Z({type:"color",names:["\\textcolor"],props:{numArgs:2,allowedInText:!0,argTypes:["color","original"]},handler(i,e){var{parser:t}=i,r=me(e[0],"color-token").color,n=e[1];return{type:"color",mode:t.mode,color:r,body:rt(n)}},htmlBuilder:L4,mathmlBuilder:I4});Z({type:"color",names:["\\color"],props:{numArgs:1,allowedInText:!0,argTypes:["color"]},handler(i,e){var{parser:t,breakOnTokenText:r}=i,n=me(e[0],"color-token").color;t.gullet.macros.set("\\current@color",n);var s=t.parseExpression(!0,r);return{type:"color",mode:t.mode,color:n,body:s}},htmlBuilder:L4,mathmlBuilder:I4});Z({type:"cr",names:["\\\\"],props:{numArgs:0,numOptionalArgs:0,allowedInText:!0},handler(i,e,t){var{parser:r}=i,n=r.gullet.future().text==="["?r.parseSizeGroup(!0):null,s=!r.settings.displayMode||!r.settings.useStrictBehavior("newLineInDisplayMode","In LaTeX, \\\\ or \\newline does nothing in display mode");return{type:"cr",mode:r.mode,newLine:s,size:n&&me(n,"size").value}},htmlBuilder(i,e){var t=z.makeSpan(["mspace"],[],e);return i.newLine&&(t.classes.push("newline"),i.size&&(t.style.marginTop=V(Xe(i.size,e)))),t},mathmlBuilder(i,e){var t=new $.MathNode("mspace");return i.newLine&&(t.setAttribute("linebreak","newline"),i.size&&t.setAttribute("height",V(Xe(i.size,e)))),t}});x1={"\\global":"\\global","\\long":"\\\\globallong","\\\\globallong":"\\\\globallong","\\def":"\\gdef","\\gdef":"\\gdef","\\edef":"\\xdef","\\xdef":"\\xdef","\\let":"\\\\globallet","\\futurelet":"\\\\globalfuture"},z4=i=>{var e=i.text;if(/^(?:[\\{}$&#^_]|EOF)$/.test(e))throw new G("Expected a control sequence",i);return e},vA=i=>{var e=i.gullet.popToken();return e.text==="="&&(e=i.gullet.popToken(),e.text===" "&&(e=i.gullet.popToken())),e},R4=(i,e,t,r)=>{var n=i.gullet.macros.get(t.text);n==null&&(t.noexpand=!0,n={tokens:[t],numArgs:0,unexpandable:!i.gullet.isExpandable(t.text)}),i.gullet.macros.set(e,n,r)};Z({type:"internal",names:["\\global","\\long","\\\\globallong"],props:{numArgs:0,allowedInText:!0},handler(i){var{parser:e,funcName:t}=i;e.consumeSpaces();var r=e.fetch();if(x1[r.text])return(t==="\\global"||t==="\\\\globallong")&&(r.text=x1[r.text]),me(e.parseFunction(),"internal");throw new G("Invalid token after macro prefix",r)}});Z({type:"internal",names:["\\def","\\gdef","\\edef","\\xdef"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(i){var{parser:e,funcName:t}=i,r=e.gullet.popToken(),n=r.text;if(/^(?:[\\{}$&#^_]|EOF)$/.test(n))throw new G("Expected a control sequence",r);for(var s=0,a,o=[[]];e.gullet.future().text!=="{";)if(r=e.gullet.popToken(),r.text==="#"){if(e.gullet.future().text==="{"){a=e.gullet.future(),o[s].push("{");break}if(r=e.gullet.popToken(),!/^[1-9]$/.test(r.text))throw new G('Invalid argument number "'+r.text+'"');if(parseInt(r.text)!==s+1)throw new G('Argument number "'+r.text+'" out of order');s++,o.push([])}else{if(r.text==="EOF")throw new G("Expected a macro definition");o[s].push(r.text)}var{tokens:l}=e.gullet.consumeArg();return a&&l.unshift(a),(t==="\\edef"||t==="\\xdef")&&(l=e.gullet.expandTokens(l),l.reverse()),e.gullet.macros.set(n,{tokens:l,numArgs:s,delimiters:o},t===x1[t]),{type:"internal",mode:e.mode}}});Z({type:"internal",names:["\\let","\\\\globallet"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(i){var{parser:e,funcName:t}=i,r=z4(e.gullet.popToken());e.gullet.consumeSpaces();var n=vA(e);return R4(e,r,n,t==="\\\\globallet"),{type:"internal",mode:e.mode}}});Z({type:"internal",names:["\\futurelet","\\\\globalfuture"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(i){var{parser:e,funcName:t}=i,r=z4(e.gullet.popToken()),n=e.gullet.popToken(),s=e.gullet.popToken();return R4(e,r,s,t==="\\\\globalfuture"),e.gullet.pushToken(s),e.gullet.pushToken(n),{type:"internal",mode:e.mode}}});Ko=function(e,t,r){var n=Oe.math[e]&&Oe.math[e].replace,s=T1(n||e,t,r);if(!s)throw new Error("Unsupported symbol "+e+" and font size "+t+".");return s},C1=function(e,t,r,n){var s=r.havingBaseStyle(t),a=z.makeSpan(n.concat(s.sizingClasses(r)),[e],r),o=s.sizeMultiplier/r.sizeMultiplier;return a.height*=o,a.depth*=o,a.maxFontSize=s.sizeMultiplier,a},D4=function(e,t,r){var n=t.havingBaseStyle(r),s=(1-t.sizeMultiplier/n.sizeMultiplier)*t.fontMetrics().axisHeight;e.classes.push("delimcenter"),e.style.top=V(s),e.height-=s,e.depth+=s},bA=function(e,t,r,n,s,a){var o=z.makeSymbol(e,"Main-Regular",s,n),l=C1(o,t,n,a);return r&&D4(l,n,t),l},wA=function(e,t,r,n){return z.makeSymbol(e,"Size"+t+"-Regular",r,n)},O4=function(e,t,r,n,s,a){var o=wA(e,t,s,n),l=C1(z.makeSpan(["delimsizing","size"+t],[o],n),ae.TEXT,n,a);return r&&D4(l,n,ae.TEXT),l},t1=function(e,t,r){var n;t==="Size1-Regular"?n="delim-size1":n="delim-size4";var s=z.makeSpan(["delimsizinginner",n],[z.makeSpan([],[z.makeSymbol(e,t,r)])]);return{type:"elem",elem:s}},i1=function(e,t,r){var n=dr["Size4-Regular"][e.charCodeAt(0)]?dr["Size4-Regular"][e.charCodeAt(0)][4]:dr["Size1-Regular"][e.charCodeAt(0)][4],s=new cr("inner",kS(e,Math.round(1e3*t))),a=new Xi([s],{width:V(n),height:V(t),style:"width:"+V(n),viewBox:"0 0 "+1e3*n+" "+Math.round(1e3*t),preserveAspectRatio:"xMinYMin"}),o=z.makeSvgSpan([],[a],r);return o.height=t,o.style.height=V(t),o.style.width=V(n),{type:"elem",elem:o}},y1=.008,yh={type:"kern",size:-1*y1},MA=["|","\\lvert","\\rvert","\\vert"],TA=["\\|","\\lVert","\\rVert","\\Vert"],B4=function(e,t,r,n,s,a){var o,l,h,d,c="",f=0;o=h=d=e,l=null;var m="Size1-Regular";e==="\\uparrow"?h=d="\u23D0":e==="\\Uparrow"?h=d="\u2016":e==="\\downarrow"?o=h="\u23D0":e==="\\Downarrow"?o=h="\u2016":e==="\\updownarrow"?(o="\\uparrow",h="\u23D0",d="\\downarrow"):e==="\\Updownarrow"?(o="\\Uparrow",h="\u2016",d="\\Downarrow"):MA.includes(e)?(h="\u2223",c="vert",f=333):TA.includes(e)?(h="\u2225",c="doublevert",f=556):e==="["||e==="\\lbrack"?(o="\u23A1",h="\u23A2",d="\u23A3",m="Size4-Regular",c="lbrack",f=667):e==="]"||e==="\\rbrack"?(o="\u23A4",h="\u23A5",d="\u23A6",m="Size4-Regular",c="rbrack",f=667):e==="\\lfloor"||e==="\u230A"?(h=o="\u23A2",d="\u23A3",m="Size4-Regular",c="lfloor",f=667):e==="\\lceil"||e==="\u2308"?(o="\u23A1",h=d="\u23A2",m="Size4-Regular",c="lceil",f=667):e==="\\rfloor"||e==="\u230B"?(h=o="\u23A5",d="\u23A6",m="Size4-Regular",c="rfloor",f=667):e==="\\rceil"||e==="\u2309"?(o="\u23A4",h=d="\u23A5",m="Size4-Regular",c="rceil",f=667):e==="("||e==="\\lparen"?(o="\u239B",h="\u239C",d="\u239D",m="Size4-Regular",c="lparen",f=875):e===")"||e==="\\rparen"?(o="\u239E",h="\u239F",d="\u23A0",m="Size4-Regular",c="rparen",f=875):e==="\\{"||e==="\\lbrace"?(o="\u23A7",l="\u23A8",d="\u23A9",h="\u23AA",m="Size4-Regular"):e==="\\}"||e==="\\rbrace"?(o="\u23AB",l="\u23AC",d="\u23AD",h="\u23AA",m="Size4-Regular"):e==="\\lgroup"||e==="\u27EE"?(o="\u23A7",d="\u23A9",h="\u23AA",m="Size4-Regular"):e==="\\rgroup"||e==="\u27EF"?(o="\u23AB",d="\u23AD",h="\u23AA",m="Size4-Regular"):e==="\\lmoustache"||e==="\u23B0"?(o="\u23A7",d="\u23AD",h="\u23AA",m="Size4-Regular"):(e==="\\rmoustache"||e==="\u23B1")&&(o="\u23AB",d="\u23A9",h="\u23AA",m="Size4-Regular");var g=Ko(o,m,s),x=g.height+g.depth,y=Ko(h,m,s),b=y.height+y.depth,S=Ko(d,m,s),A=S.height+S.depth,C=0,I=1;if(l!==null){var O=Ko(l,m,s);C=O.height+O.depth,I=2}var q=x+A+C,P=Math.max(0,Math.ceil((t-q)/(I*b))),W=q+P*I*b,ne=n.fontMetrics().axisHeight;r&&(ne*=n.sizeMultiplier);var re=W/2-ne,oe=[];if(c.length>0){var ke=W-x-A,Q=Math.round(W*1e3),Y=CS(c,Math.round(ke*1e3)),le=new cr(c,Y),ce=(f/1e3).toFixed(3)+"em",at=(Q/1e3).toFixed(3)+"em",It=new Xi([le],{width:ce,height:at,viewBox:"0 0 "+f+" "+Q}),bt=z.makeSvgSpan([],[It],n);bt.height=Q/1e3,bt.style.width=ce,bt.style.height=at,oe.push({type:"elem",elem:bt})}else{if(oe.push(t1(d,m,s)),oe.push(yh),l===null){var qi=W-x-A+2*y1;oe.push(i1(h,qi,n))}else{var zt=(W-x-A-C)/2+2*y1;oe.push(i1(h,zt,n)),oe.push(yh),oe.push(t1(l,m,s)),oe.push(yh),oe.push(i1(h,zt,n))}oe.push(yh),oe.push(t1(o,m,s))}var di=n.havingBaseStyle(ae.TEXT),nn=z.makeVList({positionType:"bottom",positionData:re,children:oe},di);return C1(z.makeSpan(["delimsizing","mult"],[nn],di),ae.TEXT,n,a)},r1=80,n1=.08,s1=function(e,t,r,n,s){var a=AS(e,n,r),o=new cr(e,a),l=new Xi([o],{width:"400em",height:V(t),viewBox:"0 0 400000 "+r,preserveAspectRatio:"xMinYMin slice"});return z.makeSvgSpan(["hide-tail"],[l],s)},NA=function(e,t){var r=t.havingBaseSizing(),n=H4("\\surd",e*r.sizeMultiplier,q4,r),s=r.sizeMultiplier,a=Math.max(0,t.minRuleThickness-t.fontMetrics().sqrtRuleThickness),o,l=0,h=0,d=0,c;return n.type==="small"?(d=1e3+1e3*a+r1,e<1?s=1:e<1.4&&(s=.7),l=(1+a+n1)/s,h=(1+a)/s,o=s1("sqrtMain",l,d,a,t),o.style.minWidth="0.853em",c=.833/s):n.type==="large"?(d=(1e3+r1)*Zo[n.size],h=(Zo[n.size]+a)/s,l=(Zo[n.size]+a+n1)/s,o=s1("sqrtSize"+n.size,l,d,a,t),o.style.minWidth="1.02em",c=1/s):(l=e+a+n1,h=e+a,d=Math.floor(1e3*e+a)+r1,o=s1("sqrtTall",l,d,a,t),o.style.minWidth="0.742em",c=1.056),o.height=h,o.style.height=V(l),{span:o,advanceWidth:c,ruleWidth:(t.fontMetrics().sqrtRuleThickness+a)*s}},P4=["(","\\lparen",")","\\rparen","[","\\lbrack","]","\\rbrack","\\{","\\lbrace","\\}","\\rbrace","\\lfloor","\\rfloor","\u230A","\u230B","\\lceil","\\rceil","\u2308","\u2309","\\surd"],EA=["\\uparrow","\\downarrow","\\updownarrow","\\Uparrow","\\Downarrow","\\Updownarrow","|","\\|","\\vert","\\Vert","\\lvert","\\rvert","\\lVert","\\rVert","\\lgroup","\\rgroup","\u27EE","\u27EF","\\lmoustache","\\rmoustache","\u23B0","\u23B1"],F4=["<",">","\\langle","\\rangle","/","\\backslash","\\lt","\\gt"],Zo=[0,1.2,1.8,2.4,3],SA=function(e,t,r,n,s){if(e==="<"||e==="\\lt"||e==="\u27E8"?e="\\langle":(e===">"||e==="\\gt"||e==="\u27E9")&&(e="\\rangle"),P4.includes(e)||F4.includes(e))return O4(e,t,!1,r,n,s);if(EA.includes(e))return B4(e,Zo[t],!1,r,n,s);throw new G("Illegal delimiter: '"+e+"'")},AA=[{type:"small",style:ae.SCRIPTSCRIPT},{type:"small",style:ae.SCRIPT},{type:"small",style:ae.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4}],kA=[{type:"small",style:ae.SCRIPTSCRIPT},{type:"small",style:ae.SCRIPT},{type:"small",style:ae.TEXT},{type:"stack"}],q4=[{type:"small",style:ae.SCRIPTSCRIPT},{type:"small",style:ae.SCRIPT},{type:"small",style:ae.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4},{type:"stack"}],CA=function(e){if(e.type==="small")return"Main-Regular";if(e.type==="large")return"Size"+e.size+"-Regular";if(e.type==="stack")return"Size4-Regular";throw new Error("Add support for delim type '"+e.type+"' here.")},H4=function(e,t,r,n){for(var s=Math.min(2,3-n.style.size),a=s;at)return r[a]}return r[r.length-1]},U4=function(e,t,r,n,s,a){e==="<"||e==="\\lt"||e==="\u27E8"?e="\\langle":(e===">"||e==="\\gt"||e==="\u27E9")&&(e="\\rangle");var o;F4.includes(e)?o=AA:P4.includes(e)?o=q4:o=kA;var l=H4(e,t,o,n);return l.type==="small"?bA(e,l.style,r,n,s,a):l.type==="large"?O4(e,l.size,r,n,s,a):B4(e,t,r,n,s,a)},_A=function(e,t,r,n,s,a){var o=n.fontMetrics().axisHeight*n.sizeMultiplier,l=901,h=5/n.fontMetrics().ptPerEm,d=Math.max(t-o,r+o),c=Math.max(d/500*l,2*d-h);return U4(e,c,!0,n,s,a)},Ir={sqrtImage:NA,sizedDelim:SA,sizeToMaxHeight:Zo,customSizedDelim:U4,leftRightDelim:_A},W6={"\\bigl":{mclass:"mopen",size:1},"\\Bigl":{mclass:"mopen",size:2},"\\biggl":{mclass:"mopen",size:3},"\\Biggl":{mclass:"mopen",size:4},"\\bigr":{mclass:"mclose",size:1},"\\Bigr":{mclass:"mclose",size:2},"\\biggr":{mclass:"mclose",size:3},"\\Biggr":{mclass:"mclose",size:4},"\\bigm":{mclass:"mrel",size:1},"\\Bigm":{mclass:"mrel",size:2},"\\biggm":{mclass:"mrel",size:3},"\\Biggm":{mclass:"mrel",size:4},"\\big":{mclass:"mord",size:1},"\\Big":{mclass:"mord",size:2},"\\bigg":{mclass:"mord",size:3},"\\Bigg":{mclass:"mord",size:4}},LA=["(","\\lparen",")","\\rparen","[","\\lbrack","]","\\rbrack","\\{","\\lbrace","\\}","\\rbrace","\\lfloor","\\rfloor","\u230A","\u230B","\\lceil","\\rceil","\u2308","\u2309","<",">","\\langle","\u27E8","\\rangle","\u27E9","\\lt","\\gt","\\lvert","\\rvert","\\lVert","\\rVert","\\lgroup","\\rgroup","\u27EE","\u27EF","\\lmoustache","\\rmoustache","\u23B0","\u23B1","/","\\backslash","|","\\vert","\\|","\\Vert","\\uparrow","\\Uparrow","\\downarrow","\\Downarrow","\\updownarrow","\\Updownarrow","."];Z({type:"delimsizing",names:["\\bigl","\\Bigl","\\biggl","\\Biggl","\\bigr","\\Bigr","\\biggr","\\Biggr","\\bigm","\\Bigm","\\biggm","\\Biggm","\\big","\\Big","\\bigg","\\Bigg"],props:{numArgs:1,argTypes:["primitive"]},handler:(i,e)=>{var t=Rh(e[0],i);return{type:"delimsizing",mode:i.parser.mode,size:W6[i.funcName].size,mclass:W6[i.funcName].mclass,delim:t.text}},htmlBuilder:(i,e)=>i.delim==="."?z.makeSpan([i.mclass]):Ir.sizedDelim(i.delim,i.size,e,i.mode,[i.mclass]),mathmlBuilder:i=>{var e=[];i.delim!=="."&&e.push(Ci(i.delim,i.mode));var t=new $.MathNode("mo",e);i.mclass==="mopen"||i.mclass==="mclose"?t.setAttribute("fence","true"):t.setAttribute("fence","false"),t.setAttribute("stretchy","true");var r=V(Ir.sizeToMaxHeight[i.size]);return t.setAttribute("minsize",r),t.setAttribute("maxsize",r),t}});Z({type:"leftright-right",names:["\\right"],props:{numArgs:1,primitive:!0},handler:(i,e)=>{var t=i.parser.gullet.macros.get("\\current@color");if(t&&typeof t!="string")throw new G("\\current@color set to non-string in \\right");return{type:"leftright-right",mode:i.parser.mode,delim:Rh(e[0],i).text,color:t}}});Z({type:"leftright",names:["\\left"],props:{numArgs:1,primitive:!0},handler:(i,e)=>{var t=Rh(e[0],i),r=i.parser;++r.leftrightDepth;var n=r.parseExpression(!1);--r.leftrightDepth,r.expect("\\right",!1);var s=me(r.parseFunction(),"leftright-right");return{type:"leftright",mode:r.mode,body:n,left:t.text,right:s.delim,rightColor:s.color}},htmlBuilder:(i,e)=>{Y6(i);for(var t=ct(i.body,e,!0,["mopen","mclose"]),r=0,n=0,s=!1,a=0;a{Y6(i);var t=ti(i.body,e);if(i.left!=="."){var r=new $.MathNode("mo",[Ci(i.left,i.mode)]);r.setAttribute("fence","true"),t.unshift(r)}if(i.right!=="."){var n=new $.MathNode("mo",[Ci(i.right,i.mode)]);n.setAttribute("fence","true"),i.rightColor&&n.setAttribute("mathcolor",i.rightColor),t.push(n)}return E1(t)}});Z({type:"middle",names:["\\middle"],props:{numArgs:1,primitive:!0},handler:(i,e)=>{var t=Rh(e[0],i);if(!i.parser.leftrightDepth)throw new G("\\middle without preceding \\left",t);return{type:"middle",mode:i.parser.mode,delim:t.text}},htmlBuilder:(i,e)=>{var t;if(i.delim===".")t=il(e,[]);else{t=Ir.sizedDelim(i.delim,1,e,i.mode,[]);var r={delim:i.delim,options:e};t.isMiddle=r}return t},mathmlBuilder:(i,e)=>{var t=i.delim==="\\vert"||i.delim==="|"?Ci("|","text"):Ci(i.delim,i.mode),r=new $.MathNode("mo",[t]);return r.setAttribute("fence","true"),r.setAttribute("lspace","0.05em"),r.setAttribute("rspace","0.05em"),r}});_1=(i,e)=>{var t=z.wrapFragment(Me(i.body,e),e),r=i.label.slice(1),n=e.sizeMultiplier,s,a=0,o=Ne.isCharacterBox(i.body);if(r==="sout")s=z.makeSpan(["stretchy","sout"]),s.height=e.fontMetrics().defaultRuleThickness/n,a=-.5*e.fontMetrics().xHeight;else if(r==="phase"){var l=Xe({number:.6,unit:"pt"},e),h=Xe({number:.35,unit:"ex"},e),d=e.havingBaseSizing();n=n/d.sizeMultiplier;var c=t.height+t.depth+l+h;t.style.paddingLeft=V(c/2+l);var f=Math.floor(1e3*c*n),m=ES(f),g=new Xi([new cr("phase",m)],{width:"400em",height:V(f/1e3),viewBox:"0 0 400000 "+f,preserveAspectRatio:"xMinYMin slice"});s=z.makeSvgSpan(["hide-tail"],[g],e),s.style.height=V(c),a=t.depth+l+h}else{/cancel/.test(r)?o||t.classes.push("cancel-pad"):r==="angl"?t.classes.push("anglpad"):t.classes.push("boxpad");var x=0,y=0,b=0;/box/.test(r)?(b=Math.max(e.fontMetrics().fboxrule,e.minRuleThickness),x=e.fontMetrics().fboxsep+(r==="colorbox"?0:b),y=x):r==="angl"?(b=Math.max(e.fontMetrics().defaultRuleThickness,e.minRuleThickness),x=4*b,y=Math.max(0,.25-t.depth)):(x=o?.2:0,y=x),s=Rr.encloseSpan(t,r,x,y,e),/fbox|boxed|fcolorbox/.test(r)?(s.style.borderStyle="solid",s.style.borderWidth=V(b)):r==="angl"&&b!==.049&&(s.style.borderTopWidth=V(b),s.style.borderRightWidth=V(b)),a=t.depth+y,i.backgroundColor&&(s.style.backgroundColor=i.backgroundColor,i.borderColor&&(s.style.borderColor=i.borderColor))}var S;if(i.backgroundColor)S=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:s,shift:a},{type:"elem",elem:t,shift:0}]},e);else{var A=/cancel|phase/.test(r)?["svg-align"]:[];S=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:t,shift:0},{type:"elem",elem:s,shift:a,wrapperClasses:A}]},e)}return/cancel/.test(r)&&(S.height=t.height,S.depth=t.depth),/cancel/.test(r)&&!o?z.makeSpan(["mord","cancel-lap"],[S],e):z.makeSpan(["mord"],[S],e)},L1=(i,e)=>{var t=0,r=new $.MathNode(i.label.indexOf("colorbox")>-1?"mpadded":"menclose",[ze(i.body,e)]);switch(i.label){case"\\cancel":r.setAttribute("notation","updiagonalstrike");break;case"\\bcancel":r.setAttribute("notation","downdiagonalstrike");break;case"\\phase":r.setAttribute("notation","phasorangle");break;case"\\sout":r.setAttribute("notation","horizontalstrike");break;case"\\fbox":r.setAttribute("notation","box");break;case"\\angl":r.setAttribute("notation","actuarial");break;case"\\fcolorbox":case"\\colorbox":if(t=e.fontMetrics().fboxsep*e.fontMetrics().ptPerEm,r.setAttribute("width","+"+2*t+"pt"),r.setAttribute("height","+"+2*t+"pt"),r.setAttribute("lspace",t+"pt"),r.setAttribute("voffset",t+"pt"),i.label==="\\fcolorbox"){var n=Math.max(e.fontMetrics().fboxrule,e.minRuleThickness);r.setAttribute("style","border: "+n+"em solid "+String(i.borderColor))}break;case"\\xcancel":r.setAttribute("notation","updiagonalstrike downdiagonalstrike");break}return i.backgroundColor&&r.setAttribute("mathbackground",i.backgroundColor),r};Z({type:"enclose",names:["\\colorbox"],props:{numArgs:2,allowedInText:!0,argTypes:["color","text"]},handler(i,e,t){var{parser:r,funcName:n}=i,s=me(e[0],"color-token").color,a=e[1];return{type:"enclose",mode:r.mode,label:n,backgroundColor:s,body:a}},htmlBuilder:_1,mathmlBuilder:L1});Z({type:"enclose",names:["\\fcolorbox"],props:{numArgs:3,allowedInText:!0,argTypes:["color","color","text"]},handler(i,e,t){var{parser:r,funcName:n}=i,s=me(e[0],"color-token").color,a=me(e[1],"color-token").color,o=e[2];return{type:"enclose",mode:r.mode,label:n,backgroundColor:a,borderColor:s,body:o}},htmlBuilder:_1,mathmlBuilder:L1});Z({type:"enclose",names:["\\fbox"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!0},handler(i,e){var{parser:t}=i;return{type:"enclose",mode:t.mode,label:"\\fbox",body:e[0]}}});Z({type:"enclose",names:["\\cancel","\\bcancel","\\xcancel","\\sout","\\phase"],props:{numArgs:1},handler(i,e){var{parser:t,funcName:r}=i,n=e[0];return{type:"enclose",mode:t.mode,label:r,body:n}},htmlBuilder:_1,mathmlBuilder:L1});Z({type:"enclose",names:["\\angl"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!1},handler(i,e){var{parser:t}=i;return{type:"enclose",mode:t.mode,label:"\\angl",body:e[0]}}});$4={};j4={};Dh=i=>{var e=i.parser.settings;if(!e.displayMode)throw new G("{"+i.envName+"} can be used only in display mode.")};fr=function(e,t){var r,n,s=e.body.length,a=e.hLinesBeforeRow,o=0,l=new Array(s),h=[],d=Math.max(t.fontMetrics().arrayRuleWidth,t.minRuleThickness),c=1/t.fontMetrics().ptPerEm,f=5*c;if(e.colSeparationType&&e.colSeparationType==="small"){var m=t.havingStyle(ae.SCRIPT).sizeMultiplier;f=.2778*(m/t.sizeMultiplier)}var g=e.colSeparationType==="CD"?Xe({number:3,unit:"ex"},t):12*c,x=3*c,y=e.arraystretch*g,b=.7*y,S=.3*y,A=0;function C(Bs){for(var $i=0;$i0&&(A+=.25),h.push({pos:A,isDashed:Bs[$i]})}for(C(a[0]),r=0;r0&&(re+=S,qBs))for(r=0;r=o)){var Mr=void 0;(n>0||e.hskipBeforeAndAfter)&&(Mr=Ne.deflt(zt.pregap,f),Mr!==0&&(Y=z.makeSpan(["arraycolsep"],[]),Y.style.width=V(Mr),Q.push(Y)));var tr=[];for(r=0;r0){for(var wi=z.makeLineSpan("hline",t,d),Je=z.makeLineSpan("hdashline",t,d),ve=[{type:"elem",elem:l,shift:0}];h.length>0;){var an=h.pop(),R=an.pos-oe;an.isDashed?ve.push({type:"elem",elem:Je,shift:R}):ve.push({type:"elem",elem:wi,shift:R})}l=z.makeVList({positionType:"individualShift",children:ve},t)}if(ce.length===0)return z.makeSpan(["mord"],[l],t);var Wn=z.makeVList({positionType:"individualShift",children:ce},t);return Wn=z.makeSpan(["tag"],[Wn],t),z.makeFragment([l,Wn])},IA={c:"center ",l:"left ",r:"right "},mr=function(e,t){for(var r=[],n=new $.MathNode("mtd",[],["mtr-glue"]),s=new $.MathNode("mtd",[],["mml-eqn-num"]),a=0;a0){var g=e.cols,x="",y=!1,b=0,S=g.length;g[0].type==="separator"&&(f+="top ",b=1),g[g.length-1].type==="separator"&&(f+="bottom ",S-=1);for(var A=b;A0?"left ":"",f+=P[P.length-1].length>0?"right ":"";for(var W=1;W-1?"alignat":"align",s=e.envName==="split",a=bn(e.parser,{cols:r,addJot:!0,autoTag:s?void 0:I1(e.envName),emptySingleRow:!0,colSeparationType:n,maxNumCols:s?2:void 0,leqno:e.parser.settings.leqno},"display"),o,l=0,h={type:"ordgroup",mode:e.mode,body:[]};if(t[0]&&t[0].type==="ordgroup"){for(var d="",c=0;c0&&m&&(y=1),r[g]={type:"align",align:x,pregap:y,postgap:0}}return a.colSeparationType=m?"align":"alignat",a};ur({type:"array",names:["array","darray"],props:{numArgs:1},handler(i,e){var t=Ih(e[0]),r=t?[e[0]]:me(e[0],"ordgroup").body,n=r.map(function(a){var o=A1(a),l=o.text;if("lcr".indexOf(l)!==-1)return{type:"align",align:l};if(l==="|")return{type:"separator",separator:"|"};if(l===":")return{type:"separator",separator:":"};throw new G("Unknown column alignment: "+l,a)}),s={cols:n,hskipBeforeAndAfter:!0,maxNumCols:n.length};return bn(i.parser,s,z1(i.envName))},htmlBuilder:fr,mathmlBuilder:mr});ur({type:"array",names:["matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix","matrix*","pmatrix*","bmatrix*","Bmatrix*","vmatrix*","Vmatrix*"],props:{numArgs:0},handler(i){var e={matrix:null,pmatrix:["(",")"],bmatrix:["[","]"],Bmatrix:["\\{","\\}"],vmatrix:["|","|"],Vmatrix:["\\Vert","\\Vert"]}[i.envName.replace("*","")],t="c",r={hskipBeforeAndAfter:!1,cols:[{type:"align",align:t}]};if(i.envName.charAt(i.envName.length-1)==="*"){var n=i.parser;if(n.consumeSpaces(),n.fetch().text==="["){if(n.consume(),n.consumeSpaces(),t=n.fetch().text,"lcr".indexOf(t)===-1)throw new G("Expected l or c or r",n.nextToken);n.consume(),n.consumeSpaces(),n.expect("]"),n.consume(),r.cols=[{type:"align",align:t}]}}var s=bn(i.parser,r,z1(i.envName)),a=Math.max(0,...s.body.map(o=>o.length));return s.cols=new Array(a).fill({type:"align",align:t}),e?{type:"leftright",mode:i.mode,body:[s],left:e[0],right:e[1],rightColor:void 0}:s},htmlBuilder:fr,mathmlBuilder:mr});ur({type:"array",names:["smallmatrix"],props:{numArgs:0},handler(i){var e={arraystretch:.5},t=bn(i.parser,e,"script");return t.colSeparationType="small",t},htmlBuilder:fr,mathmlBuilder:mr});ur({type:"array",names:["subarray"],props:{numArgs:1},handler(i,e){var t=Ih(e[0]),r=t?[e[0]]:me(e[0],"ordgroup").body,n=r.map(function(a){var o=A1(a),l=o.text;if("lc".indexOf(l)!==-1)return{type:"align",align:l};throw new G("Unknown column alignment: "+l,a)});if(n.length>1)throw new G("{subarray} can contain only one column");var s={cols:n,hskipBeforeAndAfter:!1,arraystretch:.5};if(s=bn(i.parser,s,"script"),s.body.length>0&&s.body[0].length>1)throw new G("{subarray} can contain only one column");return s},htmlBuilder:fr,mathmlBuilder:mr});ur({type:"array",names:["cases","dcases","rcases","drcases"],props:{numArgs:0},handler(i){var e={arraystretch:1.2,cols:[{type:"align",align:"l",pregap:0,postgap:1},{type:"align",align:"l",pregap:0,postgap:0}]},t=bn(i.parser,e,z1(i.envName));return{type:"leftright",mode:i.mode,body:[t],left:i.envName.indexOf("r")>-1?".":"\\{",right:i.envName.indexOf("r")>-1?"\\}":".",rightColor:void 0}},htmlBuilder:fr,mathmlBuilder:mr});ur({type:"array",names:["align","align*","aligned","split"],props:{numArgs:0},handler:G4,htmlBuilder:fr,mathmlBuilder:mr});ur({type:"array",names:["gathered","gather","gather*"],props:{numArgs:0},handler(i){["gather","gather*"].includes(i.envName)&&Dh(i);var e={cols:[{type:"align",align:"c"}],addJot:!0,colSeparationType:"gather",autoTag:I1(i.envName),emptySingleRow:!0,leqno:i.parser.settings.leqno};return bn(i.parser,e,"display")},htmlBuilder:fr,mathmlBuilder:mr});ur({type:"array",names:["alignat","alignat*","alignedat"],props:{numArgs:1},handler:G4,htmlBuilder:fr,mathmlBuilder:mr});ur({type:"array",names:["equation","equation*"],props:{numArgs:0},handler(i){Dh(i);var e={autoTag:I1(i.envName),emptySingleRow:!0,singleRow:!0,maxNumCols:1,leqno:i.parser.settings.leqno};return bn(i.parser,e,"display")},htmlBuilder:fr,mathmlBuilder:mr});ur({type:"array",names:["CD"],props:{numArgs:0},handler(i){return Dh(i),yA(i.parser)},htmlBuilder:fr,mathmlBuilder:mr});w("\\nonumber","\\gdef\\@eqnsw{0}");w("\\notag","\\nonumber");Z({type:"text",names:["\\hline","\\hdashline"],props:{numArgs:0,allowedInText:!0,allowedInMath:!0},handler(i,e){throw new G(i.funcName+" valid only within array environment")}});K6=$4;Z({type:"environment",names:["\\begin","\\end"],props:{numArgs:1,argTypes:["text"]},handler(i,e){var{parser:t,funcName:r}=i,n=e[0];if(n.type!=="ordgroup")throw new G("Invalid environment name",n);for(var s="",a=0;a{var t=i.font,r=e.withFont(t);return Me(i.body,r)},W4=(i,e)=>{var t=i.font,r=e.withFont(t);return ze(i.body,r)},Z6={"\\Bbb":"\\mathbb","\\bold":"\\mathbf","\\frak":"\\mathfrak","\\bm":"\\boldsymbol"};Z({type:"font",names:["\\mathrm","\\mathit","\\mathbf","\\mathnormal","\\mathsfit","\\mathbb","\\mathcal","\\mathfrak","\\mathscr","\\mathsf","\\mathtt","\\Bbb","\\bold","\\frak"],props:{numArgs:1,allowedInArgument:!0},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=Ah(e[0]),s=r;return s in Z6&&(s=Z6[s]),{type:"font",mode:t.mode,font:s.slice(1),body:n}},htmlBuilder:V4,mathmlBuilder:W4});Z({type:"mclass",names:["\\boldsymbol","\\bm"],props:{numArgs:1},handler:(i,e)=>{var{parser:t}=i,r=e[0],n=Ne.isCharacterBox(r);return{type:"mclass",mode:t.mode,mclass:zh(r),body:[{type:"font",mode:t.mode,font:"boldsymbol",body:r}],isCharacterBox:n}}});Z({type:"font",names:["\\rm","\\sf","\\tt","\\bf","\\it","\\cal"],props:{numArgs:0,allowedInText:!0},handler:(i,e)=>{var{parser:t,funcName:r,breakOnTokenText:n}=i,{mode:s}=t,a=t.parseExpression(!0,n),o="math"+r.slice(1);return{type:"font",mode:s,font:o,body:{type:"ordgroup",mode:t.mode,body:a}}},htmlBuilder:V4,mathmlBuilder:W4});Y4=(i,e)=>{var t=e;return i==="display"?t=t.id>=ae.SCRIPT.id?t.text():ae.DISPLAY:i==="text"&&t.size===ae.DISPLAY.size?t=ae.TEXT:i==="script"?t=ae.SCRIPT:i==="scriptscript"&&(t=ae.SCRIPTSCRIPT),t},R1=(i,e)=>{var t=Y4(i.size,e.style),r=t.fracNum(),n=t.fracDen(),s;s=e.havingStyle(r);var a=Me(i.numer,s,e);if(i.continued){var o=8.5/e.fontMetrics().ptPerEm,l=3.5/e.fontMetrics().ptPerEm;a.height=a.height0?g=3*f:g=7*f,x=e.fontMetrics().denom1):(c>0?(m=e.fontMetrics().num2,g=f):(m=e.fontMetrics().num3,g=3*f),x=e.fontMetrics().denom2);var y;if(d){var S=e.fontMetrics().axisHeight;m-a.depth-(S+.5*c){var t=new $.MathNode("mfrac",[ze(i.numer,e),ze(i.denom,e)]);if(!i.hasBarLine)t.setAttribute("linethickness","0px");else if(i.barSize){var r=Xe(i.barSize,e);t.setAttribute("linethickness",V(r))}var n=Y4(i.size,e.style);if(n.size!==e.style.size){t=new $.MathNode("mstyle",[t]);var s=n.size===ae.DISPLAY.size?"true":"false";t.setAttribute("displaystyle",s),t.setAttribute("scriptlevel","0")}if(i.leftDelim!=null||i.rightDelim!=null){var a=[];if(i.leftDelim!=null){var o=new $.MathNode("mo",[new $.TextNode(i.leftDelim.replace("\\",""))]);o.setAttribute("fence","true"),a.push(o)}if(a.push(t),i.rightDelim!=null){var l=new $.MathNode("mo",[new $.TextNode(i.rightDelim.replace("\\",""))]);l.setAttribute("fence","true"),a.push(l)}return E1(a)}return t};Z({type:"genfrac",names:["\\dfrac","\\frac","\\tfrac","\\dbinom","\\binom","\\tbinom","\\\\atopfrac","\\\\bracefrac","\\\\brackfrac"],props:{numArgs:2,allowedInArgument:!0},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0],s=e[1],a,o=null,l=null,h="auto";switch(r){case"\\dfrac":case"\\frac":case"\\tfrac":a=!0;break;case"\\\\atopfrac":a=!1;break;case"\\dbinom":case"\\binom":case"\\tbinom":a=!1,o="(",l=")";break;case"\\\\bracefrac":a=!1,o="\\{",l="\\}";break;case"\\\\brackfrac":a=!1,o="[",l="]";break;default:throw new Error("Unrecognized genfrac command")}switch(r){case"\\dfrac":case"\\dbinom":h="display";break;case"\\tfrac":case"\\tbinom":h="text";break}return{type:"genfrac",mode:t.mode,continued:!1,numer:n,denom:s,hasBarLine:a,leftDelim:o,rightDelim:l,size:h,barSize:null}},htmlBuilder:R1,mathmlBuilder:D1});Z({type:"genfrac",names:["\\cfrac"],props:{numArgs:2},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0],s=e[1];return{type:"genfrac",mode:t.mode,continued:!0,numer:n,denom:s,hasBarLine:!0,leftDelim:null,rightDelim:null,size:"display",barSize:null}}});Z({type:"infix",names:["\\over","\\choose","\\atop","\\brace","\\brack"],props:{numArgs:0,infix:!0},handler(i){var{parser:e,funcName:t,token:r}=i,n;switch(t){case"\\over":n="\\frac";break;case"\\choose":n="\\binom";break;case"\\atop":n="\\\\atopfrac";break;case"\\brace":n="\\\\bracefrac";break;case"\\brack":n="\\\\brackfrac";break;default:throw new Error("Unrecognized infix genfrac command")}return{type:"infix",mode:e.mode,replaceWith:n,token:r}}});Q6=["display","text","script","scriptscript"],J6=function(e){var t=null;return e.length>0&&(t=e,t=t==="."?null:t),t};Z({type:"genfrac",names:["\\genfrac"],props:{numArgs:6,allowedInArgument:!0,argTypes:["math","math","size","text","math","math"]},handler(i,e){var{parser:t}=i,r=e[4],n=e[5],s=Ah(e[0]),a=s.type==="atom"&&s.family==="open"?J6(s.text):null,o=Ah(e[1]),l=o.type==="atom"&&o.family==="close"?J6(o.text):null,h=me(e[2],"size"),d,c=null;h.isBlank?d=!0:(c=h.value,d=c.number>0);var f="auto",m=e[3];if(m.type==="ordgroup"){if(m.body.length>0){var g=me(m.body[0],"textord");f=Q6[Number(g.text)]}}else m=me(m,"textord"),f=Q6[Number(m.text)];return{type:"genfrac",mode:t.mode,numer:r,denom:n,continued:!1,hasBarLine:d,barSize:c,leftDelim:a,rightDelim:l,size:f}},htmlBuilder:R1,mathmlBuilder:D1});Z({type:"infix",names:["\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler(i,e){var{parser:t,funcName:r,token:n}=i;return{type:"infix",mode:t.mode,replaceWith:"\\\\abovefrac",size:me(e[0],"size").value,token:n}}});Z({type:"genfrac",names:["\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0],s=dS(me(e[1],"infix").size),a=e[2],o=s.number>0;return{type:"genfrac",mode:t.mode,numer:n,denom:a,continued:!1,hasBarLine:o,barSize:s,leftDelim:null,rightDelim:null,size:"auto"}},htmlBuilder:R1,mathmlBuilder:D1});X4=(i,e)=>{var t=e.style,r,n;i.type==="supsub"?(r=i.sup?Me(i.sup,e.havingStyle(t.sup()),e):Me(i.sub,e.havingStyle(t.sub()),e),n=me(i.base,"horizBrace")):n=me(i,"horizBrace");var s=Me(n.base,e.havingBaseStyle(ae.DISPLAY)),a=Rr.svgSpan(n,e),o;if(n.isOver?(o=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"kern",size:.1},{type:"elem",elem:a}]},e),o.children[0].children[0].children[1].classes.push("svg-align")):(o=z.makeVList({positionType:"bottom",positionData:s.depth+.1+a.height,children:[{type:"elem",elem:a},{type:"kern",size:.1},{type:"elem",elem:s}]},e),o.children[0].children[0].children[0].classes.push("svg-align")),r){var l=z.makeSpan(["mord",n.isOver?"mover":"munder"],[o],e);n.isOver?o=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:l},{type:"kern",size:.2},{type:"elem",elem:r}]},e):o=z.makeVList({positionType:"bottom",positionData:l.depth+.2+r.height+r.depth,children:[{type:"elem",elem:r},{type:"kern",size:.2},{type:"elem",elem:l}]},e)}return z.makeSpan(["mord",n.isOver?"mover":"munder"],[o],e)},zA=(i,e)=>{var t=Rr.mathMLnode(i.label);return new $.MathNode(i.isOver?"mover":"munder",[ze(i.base,e),t])};Z({type:"horizBrace",names:["\\overbrace","\\underbrace"],props:{numArgs:1},handler(i,e){var{parser:t,funcName:r}=i;return{type:"horizBrace",mode:t.mode,label:r,isOver:/^\\over/.test(r),base:e[0]}},htmlBuilder:X4,mathmlBuilder:zA});Z({type:"href",names:["\\href"],props:{numArgs:2,argTypes:["url","original"],allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=e[1],n=me(e[0],"url").url;return t.settings.isTrusted({command:"\\href",url:n})?{type:"href",mode:t.mode,href:n,body:rt(r)}:t.formatUnsupportedCmd("\\href")},htmlBuilder:(i,e)=>{var t=ct(i.body,e,!1);return z.makeAnchor(i.href,[],t,e)},mathmlBuilder:(i,e)=>{var t=vn(i.body,e);return t instanceof Bt||(t=new Bt("mrow",[t])),t.setAttribute("href",i.href),t}});Z({type:"href",names:["\\url"],props:{numArgs:1,argTypes:["url"],allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=me(e[0],"url").url;if(!t.settings.isTrusted({command:"\\url",url:r}))return t.formatUnsupportedCmd("\\url");for(var n=[],s=0;s{var{parser:t,funcName:r,token:n}=i,s=me(e[0],"raw").string,a=e[1];t.settings.strict&&t.settings.reportNonstrict("htmlExtension","HTML extension is disabled on strict mode");var o,l={};switch(r){case"\\htmlClass":l.class=s,o={command:"\\htmlClass",class:s};break;case"\\htmlId":l.id=s,o={command:"\\htmlId",id:s};break;case"\\htmlStyle":l.style=s,o={command:"\\htmlStyle",style:s};break;case"\\htmlData":{for(var h=s.split(","),d=0;d{var t=ct(i.body,e,!1),r=["enclosing"];i.attributes.class&&r.push(...i.attributes.class.trim().split(/\s+/));var n=z.makeSpan(r,t,e);for(var s in i.attributes)s!=="class"&&i.attributes.hasOwnProperty(s)&&n.setAttribute(s,i.attributes[s]);return n},mathmlBuilder:(i,e)=>vn(i.body,e)});Z({type:"htmlmathml",names:["\\html@mathml"],props:{numArgs:2,allowedInText:!0},handler:(i,e)=>{var{parser:t}=i;return{type:"htmlmathml",mode:t.mode,html:rt(e[0]),mathml:rt(e[1])}},htmlBuilder:(i,e)=>{var t=ct(i.html,e,!1);return z.makeFragment(t)},mathmlBuilder:(i,e)=>vn(i.mathml,e)});a1=function(e){if(/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(e))return{number:+e,unit:"bp"};var t=/([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(e);if(!t)throw new G("Invalid size: '"+e+"' in \\includegraphics");var r={number:+(t[1]+t[2]),unit:t[3]};if(!m4(r))throw new G("Invalid unit: '"+r.unit+"' in \\includegraphics.");return r};Z({type:"includegraphics",names:["\\includegraphics"],props:{numArgs:1,numOptionalArgs:1,argTypes:["raw","url"],allowedInText:!1},handler:(i,e,t)=>{var{parser:r}=i,n={number:0,unit:"em"},s={number:.9,unit:"em"},a={number:0,unit:"em"},o="";if(t[0])for(var l=me(t[0],"raw").string,h=l.split(","),d=0;d{var t=Xe(i.height,e),r=0;i.totalheight.number>0&&(r=Xe(i.totalheight,e)-t);var n=0;i.width.number>0&&(n=Xe(i.width,e));var s={height:V(t+r)};n>0&&(s.width=V(n)),r>0&&(s.verticalAlign=V(-r));var a=new u1(i.src,i.alt,s);return a.height=t,a.depth=r,a},mathmlBuilder:(i,e)=>{var t=new $.MathNode("mglyph",[]);t.setAttribute("alt",i.alt);var r=Xe(i.height,e),n=0;if(i.totalheight.number>0&&(n=Xe(i.totalheight,e)-r,t.setAttribute("valign",V(-n))),t.setAttribute("height",V(r+n)),i.width.number>0){var s=Xe(i.width,e);t.setAttribute("width",V(s))}return t.setAttribute("src",i.src),t}});Z({type:"kern",names:["\\kern","\\mkern","\\hskip","\\mskip"],props:{numArgs:1,argTypes:["size"],primitive:!0,allowedInText:!0},handler(i,e){var{parser:t,funcName:r}=i,n=me(e[0],"size");if(t.settings.strict){var s=r[1]==="m",a=n.value.unit==="mu";s?(a||t.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+r+" supports only mu units, "+("not "+n.value.unit+" units")),t.mode!=="math"&&t.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+r+" works only in math mode")):a&&t.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+r+" doesn't support mu units")}return{type:"kern",mode:t.mode,dimension:n.value}},htmlBuilder(i,e){return z.makeGlue(i.dimension,e)},mathmlBuilder(i,e){var t=Xe(i.dimension,e);return new $.SpaceNode(t)}});Z({type:"lap",names:["\\mathllap","\\mathrlap","\\mathclap"],props:{numArgs:1,allowedInText:!0},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0];return{type:"lap",mode:t.mode,alignment:r.slice(5),body:n}},htmlBuilder:(i,e)=>{var t;i.alignment==="clap"?(t=z.makeSpan([],[Me(i.body,e)]),t=z.makeSpan(["inner"],[t],e)):t=z.makeSpan(["inner"],[Me(i.body,e)]);var r=z.makeSpan(["fix"],[]),n=z.makeSpan([i.alignment],[t,r],e),s=z.makeSpan(["strut"]);return s.style.height=V(n.height+n.depth),n.depth&&(s.style.verticalAlign=V(-n.depth)),n.children.unshift(s),n=z.makeSpan(["thinbox"],[n],e),z.makeSpan(["mord","vbox"],[n],e)},mathmlBuilder:(i,e)=>{var t=new $.MathNode("mpadded",[ze(i.body,e)]);if(i.alignment!=="rlap"){var r=i.alignment==="llap"?"-1":"-0.5";t.setAttribute("lspace",r+"width")}return t.setAttribute("width","0px"),t}});Z({type:"styling",names:["\\(","$"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(i,e){var{funcName:t,parser:r}=i,n=r.mode;r.switchMode("math");var s=t==="\\("?"\\)":"$",a=r.parseExpression(!1,s);return r.expect(s),r.switchMode(n),{type:"styling",mode:r.mode,style:"text",body:a}}});Z({type:"text",names:["\\)","\\]"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(i,e){throw new G("Mismatched "+i.funcName)}});e4=(i,e)=>{switch(e.style.size){case ae.DISPLAY.size:return i.display;case ae.TEXT.size:return i.text;case ae.SCRIPT.size:return i.script;case ae.SCRIPTSCRIPT.size:return i.scriptscript;default:return i.text}};Z({type:"mathchoice",names:["\\mathchoice"],props:{numArgs:4,primitive:!0},handler:(i,e)=>{var{parser:t}=i;return{type:"mathchoice",mode:t.mode,display:rt(e[0]),text:rt(e[1]),script:rt(e[2]),scriptscript:rt(e[3])}},htmlBuilder:(i,e)=>{var t=e4(i,e),r=ct(t,e,!1);return z.makeFragment(r)},mathmlBuilder:(i,e)=>{var t=e4(i,e);return vn(t,e)}});K4=(i,e,t,r,n,s,a)=>{i=z.makeSpan([],[i]);var o=t&&Ne.isCharacterBox(t),l,h;if(e){var d=Me(e,r.havingStyle(n.sup()),r);h={elem:d,kern:Math.max(r.fontMetrics().bigOpSpacing1,r.fontMetrics().bigOpSpacing3-d.depth)}}if(t){var c=Me(t,r.havingStyle(n.sub()),r);l={elem:c,kern:Math.max(r.fontMetrics().bigOpSpacing2,r.fontMetrics().bigOpSpacing4-c.height)}}var f;if(h&&l){var m=r.fontMetrics().bigOpSpacing5+l.elem.height+l.elem.depth+l.kern+i.depth+a;f=z.makeVList({positionType:"bottom",positionData:m,children:[{type:"kern",size:r.fontMetrics().bigOpSpacing5},{type:"elem",elem:l.elem,marginLeft:V(-s)},{type:"kern",size:l.kern},{type:"elem",elem:i},{type:"kern",size:h.kern},{type:"elem",elem:h.elem,marginLeft:V(s)},{type:"kern",size:r.fontMetrics().bigOpSpacing5}]},r)}else if(l){var g=i.height-a;f=z.makeVList({positionType:"top",positionData:g,children:[{type:"kern",size:r.fontMetrics().bigOpSpacing5},{type:"elem",elem:l.elem,marginLeft:V(-s)},{type:"kern",size:l.kern},{type:"elem",elem:i}]},r)}else if(h){var x=i.depth+a;f=z.makeVList({positionType:"bottom",positionData:x,children:[{type:"elem",elem:i},{type:"kern",size:h.kern},{type:"elem",elem:h.elem,marginLeft:V(s)},{type:"kern",size:r.fontMetrics().bigOpSpacing5}]},r)}else return i;var y=[f];if(l&&s!==0&&!o){var b=z.makeSpan(["mspace"],[],r);b.style.marginRight=V(s),y.unshift(b)}return z.makeSpan(["mop","op-limits"],y,r)},Z4=["\\smallint"],pa=(i,e)=>{var t,r,n=!1,s;i.type==="supsub"?(t=i.sup,r=i.sub,s=me(i.base,"op"),n=!0):s=me(i,"op");var a=e.style,o=!1;a.size===ae.DISPLAY.size&&s.symbol&&!Z4.includes(s.name)&&(o=!0);var l;if(s.symbol){var h=o?"Size2-Regular":"Size1-Regular",d="";if((s.name==="\\oiint"||s.name==="\\oiiint")&&(d=s.name.slice(1),s.name=d==="oiint"?"\\iint":"\\iiint"),l=z.makeSymbol(s.name,h,"math",e,["mop","op-symbol",o?"large-op":"small-op"]),d.length>0){var c=l.italic,f=z.staticSvg(d+"Size"+(o?"2":"1"),e);l=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:l,shift:0},{type:"elem",elem:f,shift:o?.08:0}]},e),s.name="\\"+d,l.classes.unshift("mop"),l.italic=c}}else if(s.body){var m=ct(s.body,e,!0);m.length===1&&m[0]instanceof ei?(l=m[0],l.classes[0]="mop"):l=z.makeSpan(["mop"],m,e)}else{for(var g=[],x=1;x{var t;if(i.symbol)t=new Bt("mo",[Ci(i.name,i.mode)]),Z4.includes(i.name)&&t.setAttribute("largeop","false");else if(i.body)t=new Bt("mo",ti(i.body,e));else{t=new Bt("mi",[new ki(i.name.slice(1))]);var r=new Bt("mo",[Ci("\u2061","text")]);i.parentIsSupSub?t=new Bt("mrow",[t,r]):t=E4([t,r])}return t},RA={"\u220F":"\\prod","\u2210":"\\coprod","\u2211":"\\sum","\u22C0":"\\bigwedge","\u22C1":"\\bigvee","\u22C2":"\\bigcap","\u22C3":"\\bigcup","\u2A00":"\\bigodot","\u2A01":"\\bigoplus","\u2A02":"\\bigotimes","\u2A04":"\\biguplus","\u2A06":"\\bigsqcup"};Z({type:"op",names:["\\coprod","\\bigvee","\\bigwedge","\\biguplus","\\bigcap","\\bigcup","\\intop","\\prod","\\sum","\\bigotimes","\\bigoplus","\\bigodot","\\bigsqcup","\\smallint","\u220F","\u2210","\u2211","\u22C0","\u22C1","\u22C2","\u22C3","\u2A00","\u2A01","\u2A02","\u2A04","\u2A06"],props:{numArgs:0},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=r;return n.length===1&&(n=RA[n]),{type:"op",mode:t.mode,limits:!0,parentIsSupSub:!1,symbol:!0,name:n}},htmlBuilder:pa,mathmlBuilder:rl});Z({type:"op",names:["\\mathop"],props:{numArgs:1,primitive:!0},handler:(i,e)=>{var{parser:t}=i,r=e[0];return{type:"op",mode:t.mode,limits:!1,parentIsSupSub:!1,symbol:!1,body:rt(r)}},htmlBuilder:pa,mathmlBuilder:rl});DA={"\u222B":"\\int","\u222C":"\\iint","\u222D":"\\iiint","\u222E":"\\oint","\u222F":"\\oiint","\u2230":"\\oiiint"};Z({type:"op",names:["\\arcsin","\\arccos","\\arctan","\\arctg","\\arcctg","\\arg","\\ch","\\cos","\\cosec","\\cosh","\\cot","\\cotg","\\coth","\\csc","\\ctg","\\cth","\\deg","\\dim","\\exp","\\hom","\\ker","\\lg","\\ln","\\log","\\sec","\\sin","\\sinh","\\sh","\\tan","\\tanh","\\tg","\\th"],props:{numArgs:0},handler(i){var{parser:e,funcName:t}=i;return{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!1,name:t}},htmlBuilder:pa,mathmlBuilder:rl});Z({type:"op",names:["\\det","\\gcd","\\inf","\\lim","\\max","\\min","\\Pr","\\sup"],props:{numArgs:0},handler(i){var{parser:e,funcName:t}=i;return{type:"op",mode:e.mode,limits:!0,parentIsSupSub:!1,symbol:!1,name:t}},htmlBuilder:pa,mathmlBuilder:rl});Z({type:"op",names:["\\int","\\iint","\\iiint","\\oint","\\oiint","\\oiiint","\u222B","\u222C","\u222D","\u222E","\u222F","\u2230"],props:{numArgs:0,allowedInArgument:!0},handler(i){var{parser:e,funcName:t}=i,r=t;return r.length===1&&(r=DA[r]),{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!0,name:r}},htmlBuilder:pa,mathmlBuilder:rl});Q4=(i,e)=>{var t,r,n=!1,s;i.type==="supsub"?(t=i.sup,r=i.sub,s=me(i.base,"operatorname"),n=!0):s=me(i,"operatorname");var a;if(s.body.length>0){for(var o=s.body.map(c=>{var f=c.text;return typeof f=="string"?{type:"textord",mode:c.mode,text:f}:c}),l=ct(o,e.withFont("mathrm"),!0),h=0;h{for(var t=ti(i.body,e.withFont("mathrm")),r=!0,n=0;nd.toText()).join("");t=[new $.TextNode(o)]}var l=new $.MathNode("mi",t);l.setAttribute("mathvariant","normal");var h=new $.MathNode("mo",[Ci("\u2061","text")]);return i.parentIsSupSub?new $.MathNode("mrow",[l,h]):$.newDocumentFragment([l,h])};Z({type:"operatorname",names:["\\operatorname@","\\operatornamewithlimits"],props:{numArgs:1},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0];return{type:"operatorname",mode:t.mode,body:rt(n),alwaysHandleSupSub:r==="\\operatornamewithlimits",limits:!1,parentIsSupSub:!1}},htmlBuilder:Q4,mathmlBuilder:OA});w("\\operatorname","\\@ifstar\\operatornamewithlimits\\operatorname@");gs({type:"ordgroup",htmlBuilder(i,e){return i.semisimple?z.makeFragment(ct(i.body,e,!1)):z.makeSpan(["mord"],ct(i.body,e,!0),e)},mathmlBuilder(i,e){return vn(i.body,e,!0)}});Z({type:"overline",names:["\\overline"],props:{numArgs:1},handler(i,e){var{parser:t}=i,r=e[0];return{type:"overline",mode:t.mode,body:r}},htmlBuilder(i,e){var t=Me(i.body,e.havingCrampedStyle()),r=z.makeLineSpan("overline-line",e),n=e.fontMetrics().defaultRuleThickness,s=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:t},{type:"kern",size:3*n},{type:"elem",elem:r},{type:"kern",size:n}]},e);return z.makeSpan(["mord","overline"],[s],e)},mathmlBuilder(i,e){var t=new $.MathNode("mo",[new $.TextNode("\u203E")]);t.setAttribute("stretchy","true");var r=new $.MathNode("mover",[ze(i.body,e),t]);return r.setAttribute("accent","true"),r}});Z({type:"phantom",names:["\\phantom"],props:{numArgs:1,allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=e[0];return{type:"phantom",mode:t.mode,body:rt(r)}},htmlBuilder:(i,e)=>{var t=ct(i.body,e.withPhantom(),!1);return z.makeFragment(t)},mathmlBuilder:(i,e)=>{var t=ti(i.body,e);return new $.MathNode("mphantom",t)}});Z({type:"hphantom",names:["\\hphantom"],props:{numArgs:1,allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=e[0];return{type:"hphantom",mode:t.mode,body:r}},htmlBuilder:(i,e)=>{var t=z.makeSpan([],[Me(i.body,e.withPhantom())]);if(t.height=0,t.depth=0,t.children)for(var r=0;r{var t=ti(rt(i.body),e),r=new $.MathNode("mphantom",t),n=new $.MathNode("mpadded",[r]);return n.setAttribute("height","0px"),n.setAttribute("depth","0px"),n}});Z({type:"vphantom",names:["\\vphantom"],props:{numArgs:1,allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=e[0];return{type:"vphantom",mode:t.mode,body:r}},htmlBuilder:(i,e)=>{var t=z.makeSpan(["inner"],[Me(i.body,e.withPhantom())]),r=z.makeSpan(["fix"],[]);return z.makeSpan(["mord","rlap"],[t,r],e)},mathmlBuilder:(i,e)=>{var t=ti(rt(i.body),e),r=new $.MathNode("mphantom",t),n=new $.MathNode("mpadded",[r]);return n.setAttribute("width","0px"),n}});Z({type:"raisebox",names:["\\raisebox"],props:{numArgs:2,argTypes:["size","hbox"],allowedInText:!0},handler(i,e){var{parser:t}=i,r=me(e[0],"size").value,n=e[1];return{type:"raisebox",mode:t.mode,dy:r,body:n}},htmlBuilder(i,e){var t=Me(i.body,e),r=Xe(i.dy,e);return z.makeVList({positionType:"shift",positionData:-r,children:[{type:"elem",elem:t}]},e)},mathmlBuilder(i,e){var t=new $.MathNode("mpadded",[ze(i.body,e)]),r=i.dy.number+i.dy.unit;return t.setAttribute("voffset",r),t}});Z({type:"internal",names:["\\relax"],props:{numArgs:0,allowedInText:!0,allowedInArgument:!0},handler(i){var{parser:e}=i;return{type:"internal",mode:e.mode}}});Z({type:"rule",names:["\\rule"],props:{numArgs:2,numOptionalArgs:1,allowedInText:!0,allowedInMath:!0,argTypes:["size","size","size"]},handler(i,e,t){var{parser:r}=i,n=t[0],s=me(e[0],"size"),a=me(e[1],"size");return{type:"rule",mode:r.mode,shift:n&&me(n,"size").value,width:s.value,height:a.value}},htmlBuilder(i,e){var t=z.makeSpan(["mord","rule"],[],e),r=Xe(i.width,e),n=Xe(i.height,e),s=i.shift?Xe(i.shift,e):0;return t.style.borderRightWidth=V(r),t.style.borderTopWidth=V(n),t.style.bottom=V(s),t.width=r,t.height=n+s,t.depth=-s,t.maxFontSize=n*1.125*e.sizeMultiplier,t},mathmlBuilder(i,e){var t=Xe(i.width,e),r=Xe(i.height,e),n=i.shift?Xe(i.shift,e):0,s=e.color&&e.getColor()||"black",a=new $.MathNode("mspace");a.setAttribute("mathbackground",s),a.setAttribute("width",V(t)),a.setAttribute("height",V(r));var o=new $.MathNode("mpadded",[a]);return n>=0?o.setAttribute("height",V(n)):(o.setAttribute("height",V(n)),o.setAttribute("depth",V(-n))),o.setAttribute("voffset",V(n)),o}});t4=["\\tiny","\\sixptsize","\\scriptsize","\\footnotesize","\\small","\\normalsize","\\large","\\Large","\\LARGE","\\huge","\\Huge"],BA=(i,e)=>{var t=e.havingSize(i.size);return J4(i.body,t,e)};Z({type:"sizing",names:t4,props:{numArgs:0,allowedInText:!0},handler:(i,e)=>{var{breakOnTokenText:t,funcName:r,parser:n}=i,s=n.parseExpression(!1,t);return{type:"sizing",mode:n.mode,size:t4.indexOf(r)+1,body:s}},htmlBuilder:BA,mathmlBuilder:(i,e)=>{var t=e.havingSize(i.size),r=ti(i.body,t),n=new $.MathNode("mstyle",r);return n.setAttribute("mathsize",V(t.sizeMultiplier)),n}});Z({type:"smash",names:["\\smash"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0},handler:(i,e,t)=>{var{parser:r}=i,n=!1,s=!1,a=t[0]&&me(t[0],"ordgroup");if(a)for(var o="",l=0;l{var t=z.makeSpan([],[Me(i.body,e)]);if(!i.smashHeight&&!i.smashDepth)return t;if(i.smashHeight&&(t.height=0,t.children))for(var r=0;r{var t=new $.MathNode("mpadded",[ze(i.body,e)]);return i.smashHeight&&t.setAttribute("height","0px"),i.smashDepth&&t.setAttribute("depth","0px"),t}});Z({type:"sqrt",names:["\\sqrt"],props:{numArgs:1,numOptionalArgs:1},handler(i,e,t){var{parser:r}=i,n=t[0],s=e[0];return{type:"sqrt",mode:r.mode,body:s,index:n}},htmlBuilder(i,e){var t=Me(i.body,e.havingCrampedStyle());t.height===0&&(t.height=e.fontMetrics().xHeight),t=z.wrapFragment(t,e);var r=e.fontMetrics(),n=r.defaultRuleThickness,s=n;e.style.idt.height+t.depth+a&&(a=(a+c-t.height-t.depth)/2);var f=l.height-t.height-a-h;t.style.paddingLeft=V(d);var m=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:t,wrapperClasses:["svg-align"]},{type:"kern",size:-(t.height+f)},{type:"elem",elem:l},{type:"kern",size:h}]},e);if(i.index){var g=e.havingStyle(ae.SCRIPTSCRIPT),x=Me(i.index,g,e),y=.6*(m.height-m.depth),b=z.makeVList({positionType:"shift",positionData:-y,children:[{type:"elem",elem:x}]},e),S=z.makeSpan(["root"],[b]);return z.makeSpan(["mord","sqrt"],[S,m],e)}else return z.makeSpan(["mord","sqrt"],[m],e)},mathmlBuilder(i,e){var{body:t,index:r}=i;return r?new $.MathNode("mroot",[ze(t,e),ze(r,e)]):new $.MathNode("msqrt",[ze(t,e)])}});i4={display:ae.DISPLAY,text:ae.TEXT,script:ae.SCRIPT,scriptscript:ae.SCRIPTSCRIPT};Z({type:"styling",names:["\\displaystyle","\\textstyle","\\scriptstyle","\\scriptscriptstyle"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(i,e){var{breakOnTokenText:t,funcName:r,parser:n}=i,s=n.parseExpression(!0,t),a=r.slice(1,r.length-5);return{type:"styling",mode:n.mode,style:a,body:s}},htmlBuilder(i,e){var t=i4[i.style],r=e.havingStyle(t).withFont("");return J4(i.body,r,e)},mathmlBuilder(i,e){var t=i4[i.style],r=e.havingStyle(t),n=ti(i.body,r),s=new $.MathNode("mstyle",n),a={display:["0","true"],text:["0","false"],script:["1","false"],scriptscript:["2","false"]},o=a[i.style];return s.setAttribute("scriptlevel",o[0]),s.setAttribute("displaystyle",o[1]),s}});PA=function(e,t){var r=e.base;if(r)if(r.type==="op"){var n=r.limits&&(t.style.size===ae.DISPLAY.size||r.alwaysHandleSupSub);return n?pa:null}else if(r.type==="operatorname"){var s=r.alwaysHandleSupSub&&(t.style.size===ae.DISPLAY.size||r.limits);return s?Q4:null}else{if(r.type==="accent")return Ne.isCharacterBox(r.base)?k1:null;if(r.type==="horizBrace"){var a=!e.sub;return a===r.isOver?X4:null}else return null}else return null};gs({type:"supsub",htmlBuilder(i,e){var t=PA(i,e);if(t)return t(i,e);var{base:r,sup:n,sub:s}=i,a=Me(r,e),o,l,h=e.fontMetrics(),d=0,c=0,f=r&&Ne.isCharacterBox(r);if(n){var m=e.havingStyle(e.style.sup());o=Me(n,m,e),f||(d=a.height-m.fontMetrics().supDrop*m.sizeMultiplier/e.sizeMultiplier)}if(s){var g=e.havingStyle(e.style.sub());l=Me(s,g,e),f||(c=a.depth+g.fontMetrics().subDrop*g.sizeMultiplier/e.sizeMultiplier)}var x;e.style===ae.DISPLAY?x=h.sup1:e.style.cramped?x=h.sup3:x=h.sup2;var y=e.sizeMultiplier,b=V(.5/h.ptPerEm/y),S=null;if(l){var A=i.base&&i.base.type==="op"&&i.base.name&&(i.base.name==="\\oiint"||i.base.name==="\\oiiint");(a instanceof ei||A)&&(S=V(-a.italic))}var C;if(o&&l){d=Math.max(d,x,o.depth+.25*h.xHeight),c=Math.max(c,h.sub2);var I=h.defaultRuleThickness,O=4*I;if(d-o.depth-(l.height-c)0&&(d+=q,c-=q)}var P=[{type:"elem",elem:l,shift:c,marginRight:b,marginLeft:S},{type:"elem",elem:o,shift:-d,marginRight:b}];C=z.makeVList({positionType:"individualShift",children:P},e)}else if(l){c=Math.max(c,h.sub1,l.height-.8*h.xHeight);var W=[{type:"elem",elem:l,marginLeft:S,marginRight:b}];C=z.makeVList({positionType:"shift",positionData:c,children:W},e)}else if(o)d=Math.max(d,x,o.depth+.25*h.xHeight),C=z.makeVList({positionType:"shift",positionData:-d,children:[{type:"elem",elem:o,marginRight:b}]},e);else throw new Error("supsub must have either sup or sub.");var ne=m1(a,"right")||"mord";return z.makeSpan([ne],[a,z.makeSpan(["msupsub"],[C])],e)},mathmlBuilder(i,e){var t=!1,r,n;i.base&&i.base.type==="horizBrace"&&(n=!!i.sup,n===i.base.isOver&&(t=!0,r=i.base.isOver)),i.base&&(i.base.type==="op"||i.base.type==="operatorname")&&(i.base.parentIsSupSub=!0);var s=[ze(i.base,e)];i.sub&&s.push(ze(i.sub,e)),i.sup&&s.push(ze(i.sup,e));var a;if(t)a=r?"mover":"munder";else if(i.sub)if(i.sup){var h=i.base;h&&h.type==="op"&&h.limits&&e.style===ae.DISPLAY||h&&h.type==="operatorname"&&h.alwaysHandleSupSub&&(e.style===ae.DISPLAY||h.limits)?a="munderover":a="msubsup"}else{var l=i.base;l&&l.type==="op"&&l.limits&&(e.style===ae.DISPLAY||l.alwaysHandleSupSub)||l&&l.type==="operatorname"&&l.alwaysHandleSupSub&&(l.limits||e.style===ae.DISPLAY)?a="munder":a="msub"}else{var o=i.base;o&&o.type==="op"&&o.limits&&(e.style===ae.DISPLAY||o.alwaysHandleSupSub)||o&&o.type==="operatorname"&&o.alwaysHandleSupSub&&(o.limits||e.style===ae.DISPLAY)?a="mover":a="msup"}return new $.MathNode(a,s)}});gs({type:"atom",htmlBuilder(i,e){return z.mathsym(i.text,i.mode,e,["m"+i.family])},mathmlBuilder(i,e){var t=new $.MathNode("mo",[Ci(i.text,i.mode)]);if(i.family==="bin"){var r=S1(i,e);r==="bold-italic"&&t.setAttribute("mathvariant",r)}else i.family==="punct"?t.setAttribute("separator","true"):(i.family==="open"||i.family==="close")&&t.setAttribute("stretchy","false");return t}});eg={mi:"italic",mn:"normal",mtext:"normal"};gs({type:"mathord",htmlBuilder(i,e){return z.makeOrd(i,e,"mathord")},mathmlBuilder(i,e){var t=new $.MathNode("mi",[Ci(i.text,i.mode,e)]),r=S1(i,e)||"italic";return r!==eg[t.type]&&t.setAttribute("mathvariant",r),t}});gs({type:"textord",htmlBuilder(i,e){return z.makeOrd(i,e,"textord")},mathmlBuilder(i,e){var t=Ci(i.text,i.mode,e),r=S1(i,e)||"normal",n;return i.mode==="text"?n=new $.MathNode("mtext",[t]):/[0-9]/.test(i.text)?n=new $.MathNode("mn",[t]):i.text==="\\prime"?n=new $.MathNode("mo",[t]):n=new $.MathNode("mi",[t]),r!==eg[n.type]&&n.setAttribute("mathvariant",r),n}});o1={"\\nobreak":"nobreak","\\allowbreak":"allowbreak"},l1={" ":{},"\\ ":{},"~":{className:"nobreak"},"\\space":{},"\\nobreakspace":{className:"nobreak"}};gs({type:"spacing",htmlBuilder(i,e){if(l1.hasOwnProperty(i.text)){var t=l1[i.text].className||"";if(i.mode==="text"){var r=z.makeOrd(i,e,"textord");return r.classes.push(t),r}else return z.makeSpan(["mspace",t],[z.mathsym(i.text,i.mode,e)],e)}else{if(o1.hasOwnProperty(i.text))return z.makeSpan(["mspace",o1[i.text]],[],e);throw new G('Unknown type of space "'+i.text+'"')}},mathmlBuilder(i,e){var t;if(l1.hasOwnProperty(i.text))t=new $.MathNode("mtext",[new $.TextNode("\xA0")]);else{if(o1.hasOwnProperty(i.text))return new $.MathNode("mspace");throw new G('Unknown type of space "'+i.text+'"')}return t}});r4=()=>{var i=new $.MathNode("mtd",[]);return i.setAttribute("width","50%"),i};gs({type:"tag",mathmlBuilder(i,e){var t=new $.MathNode("mtable",[new $.MathNode("mtr",[r4(),new $.MathNode("mtd",[vn(i.body,e)]),r4(),new $.MathNode("mtd",[vn(i.tag,e)])])]);return t.setAttribute("width","100%"),t}});n4={"\\text":void 0,"\\textrm":"textrm","\\textsf":"textsf","\\texttt":"texttt","\\textnormal":"textrm"},s4={"\\textbf":"textbf","\\textmd":"textmd"},FA={"\\textit":"textit","\\textup":"textup"},a4=(i,e)=>{var t=i.font;if(t){if(n4[t])return e.withTextFontFamily(n4[t]);if(s4[t])return e.withTextFontWeight(s4[t]);if(t==="\\emph")return e.fontShape==="textit"?e.withTextFontShape("textup"):e.withTextFontShape("textit")}else return e;return e.withTextFontShape(FA[t])};Z({type:"text",names:["\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup","\\emph"],props:{numArgs:1,argTypes:["text"],allowedInArgument:!0,allowedInText:!0},handler(i,e){var{parser:t,funcName:r}=i,n=e[0];return{type:"text",mode:t.mode,body:rt(n),font:r}},htmlBuilder(i,e){var t=a4(i,e),r=ct(i.body,t,!0);return z.makeSpan(["mord","text"],r,t)},mathmlBuilder(i,e){var t=a4(i,e);return vn(i.body,t)}});Z({type:"underline",names:["\\underline"],props:{numArgs:1,allowedInText:!0},handler(i,e){var{parser:t}=i;return{type:"underline",mode:t.mode,body:e[0]}},htmlBuilder(i,e){var t=Me(i.body,e),r=z.makeLineSpan("underline-line",e),n=e.fontMetrics().defaultRuleThickness,s=z.makeVList({positionType:"top",positionData:t.height,children:[{type:"kern",size:n},{type:"elem",elem:r},{type:"kern",size:3*n},{type:"elem",elem:t}]},e);return z.makeSpan(["mord","underline"],[s],e)},mathmlBuilder(i,e){var t=new $.MathNode("mo",[new $.TextNode("\u203E")]);t.setAttribute("stretchy","true");var r=new $.MathNode("munder",[ze(i.body,e),t]);return r.setAttribute("accentunder","true"),r}});Z({type:"vcenter",names:["\\vcenter"],props:{numArgs:1,argTypes:["original"],allowedInText:!1},handler(i,e){var{parser:t}=i;return{type:"vcenter",mode:t.mode,body:e[0]}},htmlBuilder(i,e){var t=Me(i.body,e),r=e.fontMetrics().axisHeight,n=.5*(t.height-r-(t.depth+r));return z.makeVList({positionType:"shift",positionData:n,children:[{type:"elem",elem:t}]},e)},mathmlBuilder(i,e){return new $.MathNode("mpadded",[ze(i.body,e)],["vcenter"])}});Z({type:"verb",names:["\\verb"],props:{numArgs:0,allowedInText:!0},handler(i,e,t){throw new G("\\verb ended by end of line instead of matching delimiter")},htmlBuilder(i,e){for(var t=o4(i),r=[],n=e.havingStyle(e.style.text()),s=0;si.body.replace(/ /g,i.star?"\u2423":"\xA0"),xn=T4,tg=`[ \r - ]`,qA="\\\\[a-zA-Z@]+",HA="\\\\[^\uD800-\uDFFF]",UA="("+qA+")"+tg+"*",$A=`\\\\( +-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z`;default:throw new Error("Unknown stretchy delimiter.")}},ms=class{constructor(e){this.children=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.children=e,this.classes=[],this.height=0,this.depth=0,this.maxFontSize=0,this.style={}}hasClass(e){return this.classes.includes(e)}toNode(){for(var e=document.createDocumentFragment(),t=0;tt.toText();return this.children.map(e).join("")}},lr={"AMS-Regular":{32:[0,0,0,0,.25],65:[0,.68889,0,0,.72222],66:[0,.68889,0,0,.66667],67:[0,.68889,0,0,.72222],68:[0,.68889,0,0,.72222],69:[0,.68889,0,0,.66667],70:[0,.68889,0,0,.61111],71:[0,.68889,0,0,.77778],72:[0,.68889,0,0,.77778],73:[0,.68889,0,0,.38889],74:[.16667,.68889,0,0,.5],75:[0,.68889,0,0,.77778],76:[0,.68889,0,0,.66667],77:[0,.68889,0,0,.94445],78:[0,.68889,0,0,.72222],79:[.16667,.68889,0,0,.77778],80:[0,.68889,0,0,.61111],81:[.16667,.68889,0,0,.77778],82:[0,.68889,0,0,.72222],83:[0,.68889,0,0,.55556],84:[0,.68889,0,0,.66667],85:[0,.68889,0,0,.72222],86:[0,.68889,0,0,.72222],87:[0,.68889,0,0,1],88:[0,.68889,0,0,.72222],89:[0,.68889,0,0,.72222],90:[0,.68889,0,0,.66667],107:[0,.68889,0,0,.55556],160:[0,0,0,0,.25],165:[0,.675,.025,0,.75],174:[.15559,.69224,0,0,.94666],240:[0,.68889,0,0,.55556],295:[0,.68889,0,0,.54028],710:[0,.825,0,0,2.33334],732:[0,.9,0,0,2.33334],770:[0,.825,0,0,2.33334],771:[0,.9,0,0,2.33334],989:[.08167,.58167,0,0,.77778],1008:[0,.43056,.04028,0,.66667],8245:[0,.54986,0,0,.275],8463:[0,.68889,0,0,.54028],8487:[0,.68889,0,0,.72222],8498:[0,.68889,0,0,.55556],8502:[0,.68889,0,0,.66667],8503:[0,.68889,0,0,.44445],8504:[0,.68889,0,0,.66667],8513:[0,.68889,0,0,.63889],8592:[-.03598,.46402,0,0,.5],8594:[-.03598,.46402,0,0,.5],8602:[-.13313,.36687,0,0,1],8603:[-.13313,.36687,0,0,1],8606:[.01354,.52239,0,0,1],8608:[.01354,.52239,0,0,1],8610:[.01354,.52239,0,0,1.11111],8611:[.01354,.52239,0,0,1.11111],8619:[0,.54986,0,0,1],8620:[0,.54986,0,0,1],8621:[-.13313,.37788,0,0,1.38889],8622:[-.13313,.36687,0,0,1],8624:[0,.69224,0,0,.5],8625:[0,.69224,0,0,.5],8630:[0,.43056,0,0,1],8631:[0,.43056,0,0,1],8634:[.08198,.58198,0,0,.77778],8635:[.08198,.58198,0,0,.77778],8638:[.19444,.69224,0,0,.41667],8639:[.19444,.69224,0,0,.41667],8642:[.19444,.69224,0,0,.41667],8643:[.19444,.69224,0,0,.41667],8644:[.1808,.675,0,0,1],8646:[.1808,.675,0,0,1],8647:[.1808,.675,0,0,1],8648:[.19444,.69224,0,0,.83334],8649:[.1808,.675,0,0,1],8650:[.19444,.69224,0,0,.83334],8651:[.01354,.52239,0,0,1],8652:[.01354,.52239,0,0,1],8653:[-.13313,.36687,0,0,1],8654:[-.13313,.36687,0,0,1],8655:[-.13313,.36687,0,0,1],8666:[.13667,.63667,0,0,1],8667:[.13667,.63667,0,0,1],8669:[-.13313,.37788,0,0,1],8672:[-.064,.437,0,0,1.334],8674:[-.064,.437,0,0,1.334],8705:[0,.825,0,0,.5],8708:[0,.68889,0,0,.55556],8709:[.08167,.58167,0,0,.77778],8717:[0,.43056,0,0,.42917],8722:[-.03598,.46402,0,0,.5],8724:[.08198,.69224,0,0,.77778],8726:[.08167,.58167,0,0,.77778],8733:[0,.69224,0,0,.77778],8736:[0,.69224,0,0,.72222],8737:[0,.69224,0,0,.72222],8738:[.03517,.52239,0,0,.72222],8739:[.08167,.58167,0,0,.22222],8740:[.25142,.74111,0,0,.27778],8741:[.08167,.58167,0,0,.38889],8742:[.25142,.74111,0,0,.5],8756:[0,.69224,0,0,.66667],8757:[0,.69224,0,0,.66667],8764:[-.13313,.36687,0,0,.77778],8765:[-.13313,.37788,0,0,.77778],8769:[-.13313,.36687,0,0,.77778],8770:[-.03625,.46375,0,0,.77778],8774:[.30274,.79383,0,0,.77778],8776:[-.01688,.48312,0,0,.77778],8778:[.08167,.58167,0,0,.77778],8782:[.06062,.54986,0,0,.77778],8783:[.06062,.54986,0,0,.77778],8785:[.08198,.58198,0,0,.77778],8786:[.08198,.58198,0,0,.77778],8787:[.08198,.58198,0,0,.77778],8790:[0,.69224,0,0,.77778],8791:[.22958,.72958,0,0,.77778],8796:[.08198,.91667,0,0,.77778],8806:[.25583,.75583,0,0,.77778],8807:[.25583,.75583,0,0,.77778],8808:[.25142,.75726,0,0,.77778],8809:[.25142,.75726,0,0,.77778],8812:[.25583,.75583,0,0,.5],8814:[.20576,.70576,0,0,.77778],8815:[.20576,.70576,0,0,.77778],8816:[.30274,.79383,0,0,.77778],8817:[.30274,.79383,0,0,.77778],8818:[.22958,.72958,0,0,.77778],8819:[.22958,.72958,0,0,.77778],8822:[.1808,.675,0,0,.77778],8823:[.1808,.675,0,0,.77778],8828:[.13667,.63667,0,0,.77778],8829:[.13667,.63667,0,0,.77778],8830:[.22958,.72958,0,0,.77778],8831:[.22958,.72958,0,0,.77778],8832:[.20576,.70576,0,0,.77778],8833:[.20576,.70576,0,0,.77778],8840:[.30274,.79383,0,0,.77778],8841:[.30274,.79383,0,0,.77778],8842:[.13597,.63597,0,0,.77778],8843:[.13597,.63597,0,0,.77778],8847:[.03517,.54986,0,0,.77778],8848:[.03517,.54986,0,0,.77778],8858:[.08198,.58198,0,0,.77778],8859:[.08198,.58198,0,0,.77778],8861:[.08198,.58198,0,0,.77778],8862:[0,.675,0,0,.77778],8863:[0,.675,0,0,.77778],8864:[0,.675,0,0,.77778],8865:[0,.675,0,0,.77778],8872:[0,.69224,0,0,.61111],8873:[0,.69224,0,0,.72222],8874:[0,.69224,0,0,.88889],8876:[0,.68889,0,0,.61111],8877:[0,.68889,0,0,.61111],8878:[0,.68889,0,0,.72222],8879:[0,.68889,0,0,.72222],8882:[.03517,.54986,0,0,.77778],8883:[.03517,.54986,0,0,.77778],8884:[.13667,.63667,0,0,.77778],8885:[.13667,.63667,0,0,.77778],8888:[0,.54986,0,0,1.11111],8890:[.19444,.43056,0,0,.55556],8891:[.19444,.69224,0,0,.61111],8892:[.19444,.69224,0,0,.61111],8901:[0,.54986,0,0,.27778],8903:[.08167,.58167,0,0,.77778],8905:[.08167,.58167,0,0,.77778],8906:[.08167,.58167,0,0,.77778],8907:[0,.69224,0,0,.77778],8908:[0,.69224,0,0,.77778],8909:[-.03598,.46402,0,0,.77778],8910:[0,.54986,0,0,.76042],8911:[0,.54986,0,0,.76042],8912:[.03517,.54986,0,0,.77778],8913:[.03517,.54986,0,0,.77778],8914:[0,.54986,0,0,.66667],8915:[0,.54986,0,0,.66667],8916:[0,.69224,0,0,.66667],8918:[.0391,.5391,0,0,.77778],8919:[.0391,.5391,0,0,.77778],8920:[.03517,.54986,0,0,1.33334],8921:[.03517,.54986,0,0,1.33334],8922:[.38569,.88569,0,0,.77778],8923:[.38569,.88569,0,0,.77778],8926:[.13667,.63667,0,0,.77778],8927:[.13667,.63667,0,0,.77778],8928:[.30274,.79383,0,0,.77778],8929:[.30274,.79383,0,0,.77778],8934:[.23222,.74111,0,0,.77778],8935:[.23222,.74111,0,0,.77778],8936:[.23222,.74111,0,0,.77778],8937:[.23222,.74111,0,0,.77778],8938:[.20576,.70576,0,0,.77778],8939:[.20576,.70576,0,0,.77778],8940:[.30274,.79383,0,0,.77778],8941:[.30274,.79383,0,0,.77778],8994:[.19444,.69224,0,0,.77778],8995:[.19444,.69224,0,0,.77778],9416:[.15559,.69224,0,0,.90222],9484:[0,.69224,0,0,.5],9488:[0,.69224,0,0,.5],9492:[0,.37788,0,0,.5],9496:[0,.37788,0,0,.5],9585:[.19444,.68889,0,0,.88889],9586:[.19444,.74111,0,0,.88889],9632:[0,.675,0,0,.77778],9633:[0,.675,0,0,.77778],9650:[0,.54986,0,0,.72222],9651:[0,.54986,0,0,.72222],9654:[.03517,.54986,0,0,.77778],9660:[0,.54986,0,0,.72222],9661:[0,.54986,0,0,.72222],9664:[.03517,.54986,0,0,.77778],9674:[.11111,.69224,0,0,.66667],9733:[.19444,.69224,0,0,.94445],10003:[0,.69224,0,0,.83334],10016:[0,.69224,0,0,.83334],10731:[.11111,.69224,0,0,.66667],10846:[.19444,.75583,0,0,.61111],10877:[.13667,.63667,0,0,.77778],10878:[.13667,.63667,0,0,.77778],10885:[.25583,.75583,0,0,.77778],10886:[.25583,.75583,0,0,.77778],10887:[.13597,.63597,0,0,.77778],10888:[.13597,.63597,0,0,.77778],10889:[.26167,.75726,0,0,.77778],10890:[.26167,.75726,0,0,.77778],10891:[.48256,.98256,0,0,.77778],10892:[.48256,.98256,0,0,.77778],10901:[.13667,.63667,0,0,.77778],10902:[.13667,.63667,0,0,.77778],10933:[.25142,.75726,0,0,.77778],10934:[.25142,.75726,0,0,.77778],10935:[.26167,.75726,0,0,.77778],10936:[.26167,.75726,0,0,.77778],10937:[.26167,.75726,0,0,.77778],10938:[.26167,.75726,0,0,.77778],10949:[.25583,.75583,0,0,.77778],10950:[.25583,.75583,0,0,.77778],10955:[.28481,.79383,0,0,.77778],10956:[.28481,.79383,0,0,.77778],57350:[.08167,.58167,0,0,.22222],57351:[.08167,.58167,0,0,.38889],57352:[.08167,.58167,0,0,.77778],57353:[0,.43056,.04028,0,.66667],57356:[.25142,.75726,0,0,.77778],57357:[.25142,.75726,0,0,.77778],57358:[.41951,.91951,0,0,.77778],57359:[.30274,.79383,0,0,.77778],57360:[.30274,.79383,0,0,.77778],57361:[.41951,.91951,0,0,.77778],57366:[.25142,.75726,0,0,.77778],57367:[.25142,.75726,0,0,.77778],57368:[.25142,.75726,0,0,.77778],57369:[.25142,.75726,0,0,.77778],57370:[.13597,.63597,0,0,.77778],57371:[.13597,.63597,0,0,.77778]},"Caligraphic-Regular":{32:[0,0,0,0,.25],65:[0,.68333,0,.19445,.79847],66:[0,.68333,.03041,.13889,.65681],67:[0,.68333,.05834,.13889,.52653],68:[0,.68333,.02778,.08334,.77139],69:[0,.68333,.08944,.11111,.52778],70:[0,.68333,.09931,.11111,.71875],71:[.09722,.68333,.0593,.11111,.59487],72:[0,.68333,.00965,.11111,.84452],73:[0,.68333,.07382,0,.54452],74:[.09722,.68333,.18472,.16667,.67778],75:[0,.68333,.01445,.05556,.76195],76:[0,.68333,0,.13889,.68972],77:[0,.68333,0,.13889,1.2009],78:[0,.68333,.14736,.08334,.82049],79:[0,.68333,.02778,.11111,.79611],80:[0,.68333,.08222,.08334,.69556],81:[.09722,.68333,0,.11111,.81667],82:[0,.68333,0,.08334,.8475],83:[0,.68333,.075,.13889,.60556],84:[0,.68333,.25417,0,.54464],85:[0,.68333,.09931,.08334,.62583],86:[0,.68333,.08222,0,.61278],87:[0,.68333,.08222,.08334,.98778],88:[0,.68333,.14643,.13889,.7133],89:[.09722,.68333,.08222,.08334,.66834],90:[0,.68333,.07944,.13889,.72473],160:[0,0,0,0,.25]},"Fraktur-Regular":{32:[0,0,0,0,.25],33:[0,.69141,0,0,.29574],34:[0,.69141,0,0,.21471],38:[0,.69141,0,0,.73786],39:[0,.69141,0,0,.21201],40:[.24982,.74947,0,0,.38865],41:[.24982,.74947,0,0,.38865],42:[0,.62119,0,0,.27764],43:[.08319,.58283,0,0,.75623],44:[0,.10803,0,0,.27764],45:[.08319,.58283,0,0,.75623],46:[0,.10803,0,0,.27764],47:[.24982,.74947,0,0,.50181],48:[0,.47534,0,0,.50181],49:[0,.47534,0,0,.50181],50:[0,.47534,0,0,.50181],51:[.18906,.47534,0,0,.50181],52:[.18906,.47534,0,0,.50181],53:[.18906,.47534,0,0,.50181],54:[0,.69141,0,0,.50181],55:[.18906,.47534,0,0,.50181],56:[0,.69141,0,0,.50181],57:[.18906,.47534,0,0,.50181],58:[0,.47534,0,0,.21606],59:[.12604,.47534,0,0,.21606],61:[-.13099,.36866,0,0,.75623],63:[0,.69141,0,0,.36245],65:[0,.69141,0,0,.7176],66:[0,.69141,0,0,.88397],67:[0,.69141,0,0,.61254],68:[0,.69141,0,0,.83158],69:[0,.69141,0,0,.66278],70:[.12604,.69141,0,0,.61119],71:[0,.69141,0,0,.78539],72:[.06302,.69141,0,0,.7203],73:[0,.69141,0,0,.55448],74:[.12604,.69141,0,0,.55231],75:[0,.69141,0,0,.66845],76:[0,.69141,0,0,.66602],77:[0,.69141,0,0,1.04953],78:[0,.69141,0,0,.83212],79:[0,.69141,0,0,.82699],80:[.18906,.69141,0,0,.82753],81:[.03781,.69141,0,0,.82699],82:[0,.69141,0,0,.82807],83:[0,.69141,0,0,.82861],84:[0,.69141,0,0,.66899],85:[0,.69141,0,0,.64576],86:[0,.69141,0,0,.83131],87:[0,.69141,0,0,1.04602],88:[0,.69141,0,0,.71922],89:[.18906,.69141,0,0,.83293],90:[.12604,.69141,0,0,.60201],91:[.24982,.74947,0,0,.27764],93:[.24982,.74947,0,0,.27764],94:[0,.69141,0,0,.49965],97:[0,.47534,0,0,.50046],98:[0,.69141,0,0,.51315],99:[0,.47534,0,0,.38946],100:[0,.62119,0,0,.49857],101:[0,.47534,0,0,.40053],102:[.18906,.69141,0,0,.32626],103:[.18906,.47534,0,0,.5037],104:[.18906,.69141,0,0,.52126],105:[0,.69141,0,0,.27899],106:[0,.69141,0,0,.28088],107:[0,.69141,0,0,.38946],108:[0,.69141,0,0,.27953],109:[0,.47534,0,0,.76676],110:[0,.47534,0,0,.52666],111:[0,.47534,0,0,.48885],112:[.18906,.52396,0,0,.50046],113:[.18906,.47534,0,0,.48912],114:[0,.47534,0,0,.38919],115:[0,.47534,0,0,.44266],116:[0,.62119,0,0,.33301],117:[0,.47534,0,0,.5172],118:[0,.52396,0,0,.5118],119:[0,.52396,0,0,.77351],120:[.18906,.47534,0,0,.38865],121:[.18906,.47534,0,0,.49884],122:[.18906,.47534,0,0,.39054],160:[0,0,0,0,.25],8216:[0,.69141,0,0,.21471],8217:[0,.69141,0,0,.21471],58112:[0,.62119,0,0,.49749],58113:[0,.62119,0,0,.4983],58114:[.18906,.69141,0,0,.33328],58115:[.18906,.69141,0,0,.32923],58116:[.18906,.47534,0,0,.50343],58117:[0,.69141,0,0,.33301],58118:[0,.62119,0,0,.33409],58119:[0,.47534,0,0,.50073]},"Main-Bold":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.35],34:[0,.69444,0,0,.60278],35:[.19444,.69444,0,0,.95833],36:[.05556,.75,0,0,.575],37:[.05556,.75,0,0,.95833],38:[0,.69444,0,0,.89444],39:[0,.69444,0,0,.31944],40:[.25,.75,0,0,.44722],41:[.25,.75,0,0,.44722],42:[0,.75,0,0,.575],43:[.13333,.63333,0,0,.89444],44:[.19444,.15556,0,0,.31944],45:[0,.44444,0,0,.38333],46:[0,.15556,0,0,.31944],47:[.25,.75,0,0,.575],48:[0,.64444,0,0,.575],49:[0,.64444,0,0,.575],50:[0,.64444,0,0,.575],51:[0,.64444,0,0,.575],52:[0,.64444,0,0,.575],53:[0,.64444,0,0,.575],54:[0,.64444,0,0,.575],55:[0,.64444,0,0,.575],56:[0,.64444,0,0,.575],57:[0,.64444,0,0,.575],58:[0,.44444,0,0,.31944],59:[.19444,.44444,0,0,.31944],60:[.08556,.58556,0,0,.89444],61:[-.10889,.39111,0,0,.89444],62:[.08556,.58556,0,0,.89444],63:[0,.69444,0,0,.54305],64:[0,.69444,0,0,.89444],65:[0,.68611,0,0,.86944],66:[0,.68611,0,0,.81805],67:[0,.68611,0,0,.83055],68:[0,.68611,0,0,.88194],69:[0,.68611,0,0,.75555],70:[0,.68611,0,0,.72361],71:[0,.68611,0,0,.90416],72:[0,.68611,0,0,.9],73:[0,.68611,0,0,.43611],74:[0,.68611,0,0,.59444],75:[0,.68611,0,0,.90138],76:[0,.68611,0,0,.69166],77:[0,.68611,0,0,1.09166],78:[0,.68611,0,0,.9],79:[0,.68611,0,0,.86388],80:[0,.68611,0,0,.78611],81:[.19444,.68611,0,0,.86388],82:[0,.68611,0,0,.8625],83:[0,.68611,0,0,.63889],84:[0,.68611,0,0,.8],85:[0,.68611,0,0,.88472],86:[0,.68611,.01597,0,.86944],87:[0,.68611,.01597,0,1.18888],88:[0,.68611,0,0,.86944],89:[0,.68611,.02875,0,.86944],90:[0,.68611,0,0,.70277],91:[.25,.75,0,0,.31944],92:[.25,.75,0,0,.575],93:[.25,.75,0,0,.31944],94:[0,.69444,0,0,.575],95:[.31,.13444,.03194,0,.575],97:[0,.44444,0,0,.55902],98:[0,.69444,0,0,.63889],99:[0,.44444,0,0,.51111],100:[0,.69444,0,0,.63889],101:[0,.44444,0,0,.52708],102:[0,.69444,.10903,0,.35139],103:[.19444,.44444,.01597,0,.575],104:[0,.69444,0,0,.63889],105:[0,.69444,0,0,.31944],106:[.19444,.69444,0,0,.35139],107:[0,.69444,0,0,.60694],108:[0,.69444,0,0,.31944],109:[0,.44444,0,0,.95833],110:[0,.44444,0,0,.63889],111:[0,.44444,0,0,.575],112:[.19444,.44444,0,0,.63889],113:[.19444,.44444,0,0,.60694],114:[0,.44444,0,0,.47361],115:[0,.44444,0,0,.45361],116:[0,.63492,0,0,.44722],117:[0,.44444,0,0,.63889],118:[0,.44444,.01597,0,.60694],119:[0,.44444,.01597,0,.83055],120:[0,.44444,0,0,.60694],121:[.19444,.44444,.01597,0,.60694],122:[0,.44444,0,0,.51111],123:[.25,.75,0,0,.575],124:[.25,.75,0,0,.31944],125:[.25,.75,0,0,.575],126:[.35,.34444,0,0,.575],160:[0,0,0,0,.25],163:[0,.69444,0,0,.86853],168:[0,.69444,0,0,.575],172:[0,.44444,0,0,.76666],176:[0,.69444,0,0,.86944],177:[.13333,.63333,0,0,.89444],184:[.17014,0,0,0,.51111],198:[0,.68611,0,0,1.04166],215:[.13333,.63333,0,0,.89444],216:[.04861,.73472,0,0,.89444],223:[0,.69444,0,0,.59722],230:[0,.44444,0,0,.83055],247:[.13333,.63333,0,0,.89444],248:[.09722,.54167,0,0,.575],305:[0,.44444,0,0,.31944],338:[0,.68611,0,0,1.16944],339:[0,.44444,0,0,.89444],567:[.19444,.44444,0,0,.35139],710:[0,.69444,0,0,.575],711:[0,.63194,0,0,.575],713:[0,.59611,0,0,.575],714:[0,.69444,0,0,.575],715:[0,.69444,0,0,.575],728:[0,.69444,0,0,.575],729:[0,.69444,0,0,.31944],730:[0,.69444,0,0,.86944],732:[0,.69444,0,0,.575],733:[0,.69444,0,0,.575],915:[0,.68611,0,0,.69166],916:[0,.68611,0,0,.95833],920:[0,.68611,0,0,.89444],923:[0,.68611,0,0,.80555],926:[0,.68611,0,0,.76666],928:[0,.68611,0,0,.9],931:[0,.68611,0,0,.83055],933:[0,.68611,0,0,.89444],934:[0,.68611,0,0,.83055],936:[0,.68611,0,0,.89444],937:[0,.68611,0,0,.83055],8211:[0,.44444,.03194,0,.575],8212:[0,.44444,.03194,0,1.14999],8216:[0,.69444,0,0,.31944],8217:[0,.69444,0,0,.31944],8220:[0,.69444,0,0,.60278],8221:[0,.69444,0,0,.60278],8224:[.19444,.69444,0,0,.51111],8225:[.19444,.69444,0,0,.51111],8242:[0,.55556,0,0,.34444],8407:[0,.72444,.15486,0,.575],8463:[0,.69444,0,0,.66759],8465:[0,.69444,0,0,.83055],8467:[0,.69444,0,0,.47361],8472:[.19444,.44444,0,0,.74027],8476:[0,.69444,0,0,.83055],8501:[0,.69444,0,0,.70277],8592:[-.10889,.39111,0,0,1.14999],8593:[.19444,.69444,0,0,.575],8594:[-.10889,.39111,0,0,1.14999],8595:[.19444,.69444,0,0,.575],8596:[-.10889,.39111,0,0,1.14999],8597:[.25,.75,0,0,.575],8598:[.19444,.69444,0,0,1.14999],8599:[.19444,.69444,0,0,1.14999],8600:[.19444,.69444,0,0,1.14999],8601:[.19444,.69444,0,0,1.14999],8636:[-.10889,.39111,0,0,1.14999],8637:[-.10889,.39111,0,0,1.14999],8640:[-.10889,.39111,0,0,1.14999],8641:[-.10889,.39111,0,0,1.14999],8656:[-.10889,.39111,0,0,1.14999],8657:[.19444,.69444,0,0,.70277],8658:[-.10889,.39111,0,0,1.14999],8659:[.19444,.69444,0,0,.70277],8660:[-.10889,.39111,0,0,1.14999],8661:[.25,.75,0,0,.70277],8704:[0,.69444,0,0,.63889],8706:[0,.69444,.06389,0,.62847],8707:[0,.69444,0,0,.63889],8709:[.05556,.75,0,0,.575],8711:[0,.68611,0,0,.95833],8712:[.08556,.58556,0,0,.76666],8715:[.08556,.58556,0,0,.76666],8722:[.13333,.63333,0,0,.89444],8723:[.13333,.63333,0,0,.89444],8725:[.25,.75,0,0,.575],8726:[.25,.75,0,0,.575],8727:[-.02778,.47222,0,0,.575],8728:[-.02639,.47361,0,0,.575],8729:[-.02639,.47361,0,0,.575],8730:[.18,.82,0,0,.95833],8733:[0,.44444,0,0,.89444],8734:[0,.44444,0,0,1.14999],8736:[0,.69224,0,0,.72222],8739:[.25,.75,0,0,.31944],8741:[.25,.75,0,0,.575],8743:[0,.55556,0,0,.76666],8744:[0,.55556,0,0,.76666],8745:[0,.55556,0,0,.76666],8746:[0,.55556,0,0,.76666],8747:[.19444,.69444,.12778,0,.56875],8764:[-.10889,.39111,0,0,.89444],8768:[.19444,.69444,0,0,.31944],8771:[.00222,.50222,0,0,.89444],8773:[.027,.638,0,0,.894],8776:[.02444,.52444,0,0,.89444],8781:[.00222,.50222,0,0,.89444],8801:[.00222,.50222,0,0,.89444],8804:[.19667,.69667,0,0,.89444],8805:[.19667,.69667,0,0,.89444],8810:[.08556,.58556,0,0,1.14999],8811:[.08556,.58556,0,0,1.14999],8826:[.08556,.58556,0,0,.89444],8827:[.08556,.58556,0,0,.89444],8834:[.08556,.58556,0,0,.89444],8835:[.08556,.58556,0,0,.89444],8838:[.19667,.69667,0,0,.89444],8839:[.19667,.69667,0,0,.89444],8846:[0,.55556,0,0,.76666],8849:[.19667,.69667,0,0,.89444],8850:[.19667,.69667,0,0,.89444],8851:[0,.55556,0,0,.76666],8852:[0,.55556,0,0,.76666],8853:[.13333,.63333,0,0,.89444],8854:[.13333,.63333,0,0,.89444],8855:[.13333,.63333,0,0,.89444],8856:[.13333,.63333,0,0,.89444],8857:[.13333,.63333,0,0,.89444],8866:[0,.69444,0,0,.70277],8867:[0,.69444,0,0,.70277],8868:[0,.69444,0,0,.89444],8869:[0,.69444,0,0,.89444],8900:[-.02639,.47361,0,0,.575],8901:[-.02639,.47361,0,0,.31944],8902:[-.02778,.47222,0,0,.575],8968:[.25,.75,0,0,.51111],8969:[.25,.75,0,0,.51111],8970:[.25,.75,0,0,.51111],8971:[.25,.75,0,0,.51111],8994:[-.13889,.36111,0,0,1.14999],8995:[-.13889,.36111,0,0,1.14999],9651:[.19444,.69444,0,0,1.02222],9657:[-.02778,.47222,0,0,.575],9661:[.19444,.69444,0,0,1.02222],9667:[-.02778,.47222,0,0,.575],9711:[.19444,.69444,0,0,1.14999],9824:[.12963,.69444,0,0,.89444],9825:[.12963,.69444,0,0,.89444],9826:[.12963,.69444,0,0,.89444],9827:[.12963,.69444,0,0,.89444],9837:[0,.75,0,0,.44722],9838:[.19444,.69444,0,0,.44722],9839:[.19444,.69444,0,0,.44722],10216:[.25,.75,0,0,.44722],10217:[.25,.75,0,0,.44722],10815:[0,.68611,0,0,.9],10927:[.19667,.69667,0,0,.89444],10928:[.19667,.69667,0,0,.89444],57376:[.19444,.69444,0,0,0]},"Main-BoldItalic":{32:[0,0,0,0,.25],33:[0,.69444,.11417,0,.38611],34:[0,.69444,.07939,0,.62055],35:[.19444,.69444,.06833,0,.94444],37:[.05556,.75,.12861,0,.94444],38:[0,.69444,.08528,0,.88555],39:[0,.69444,.12945,0,.35555],40:[.25,.75,.15806,0,.47333],41:[.25,.75,.03306,0,.47333],42:[0,.75,.14333,0,.59111],43:[.10333,.60333,.03306,0,.88555],44:[.19444,.14722,0,0,.35555],45:[0,.44444,.02611,0,.41444],46:[0,.14722,0,0,.35555],47:[.25,.75,.15806,0,.59111],48:[0,.64444,.13167,0,.59111],49:[0,.64444,.13167,0,.59111],50:[0,.64444,.13167,0,.59111],51:[0,.64444,.13167,0,.59111],52:[.19444,.64444,.13167,0,.59111],53:[0,.64444,.13167,0,.59111],54:[0,.64444,.13167,0,.59111],55:[.19444,.64444,.13167,0,.59111],56:[0,.64444,.13167,0,.59111],57:[0,.64444,.13167,0,.59111],58:[0,.44444,.06695,0,.35555],59:[.19444,.44444,.06695,0,.35555],61:[-.10889,.39111,.06833,0,.88555],63:[0,.69444,.11472,0,.59111],64:[0,.69444,.09208,0,.88555],65:[0,.68611,0,0,.86555],66:[0,.68611,.0992,0,.81666],67:[0,.68611,.14208,0,.82666],68:[0,.68611,.09062,0,.87555],69:[0,.68611,.11431,0,.75666],70:[0,.68611,.12903,0,.72722],71:[0,.68611,.07347,0,.89527],72:[0,.68611,.17208,0,.8961],73:[0,.68611,.15681,0,.47166],74:[0,.68611,.145,0,.61055],75:[0,.68611,.14208,0,.89499],76:[0,.68611,0,0,.69777],77:[0,.68611,.17208,0,1.07277],78:[0,.68611,.17208,0,.8961],79:[0,.68611,.09062,0,.85499],80:[0,.68611,.0992,0,.78721],81:[.19444,.68611,.09062,0,.85499],82:[0,.68611,.02559,0,.85944],83:[0,.68611,.11264,0,.64999],84:[0,.68611,.12903,0,.7961],85:[0,.68611,.17208,0,.88083],86:[0,.68611,.18625,0,.86555],87:[0,.68611,.18625,0,1.15999],88:[0,.68611,.15681,0,.86555],89:[0,.68611,.19803,0,.86555],90:[0,.68611,.14208,0,.70888],91:[.25,.75,.1875,0,.35611],93:[.25,.75,.09972,0,.35611],94:[0,.69444,.06709,0,.59111],95:[.31,.13444,.09811,0,.59111],97:[0,.44444,.09426,0,.59111],98:[0,.69444,.07861,0,.53222],99:[0,.44444,.05222,0,.53222],100:[0,.69444,.10861,0,.59111],101:[0,.44444,.085,0,.53222],102:[.19444,.69444,.21778,0,.4],103:[.19444,.44444,.105,0,.53222],104:[0,.69444,.09426,0,.59111],105:[0,.69326,.11387,0,.35555],106:[.19444,.69326,.1672,0,.35555],107:[0,.69444,.11111,0,.53222],108:[0,.69444,.10861,0,.29666],109:[0,.44444,.09426,0,.94444],110:[0,.44444,.09426,0,.64999],111:[0,.44444,.07861,0,.59111],112:[.19444,.44444,.07861,0,.59111],113:[.19444,.44444,.105,0,.53222],114:[0,.44444,.11111,0,.50167],115:[0,.44444,.08167,0,.48694],116:[0,.63492,.09639,0,.385],117:[0,.44444,.09426,0,.62055],118:[0,.44444,.11111,0,.53222],119:[0,.44444,.11111,0,.76777],120:[0,.44444,.12583,0,.56055],121:[.19444,.44444,.105,0,.56166],122:[0,.44444,.13889,0,.49055],126:[.35,.34444,.11472,0,.59111],160:[0,0,0,0,.25],168:[0,.69444,.11473,0,.59111],176:[0,.69444,0,0,.94888],184:[.17014,0,0,0,.53222],198:[0,.68611,.11431,0,1.02277],216:[.04861,.73472,.09062,0,.88555],223:[.19444,.69444,.09736,0,.665],230:[0,.44444,.085,0,.82666],248:[.09722,.54167,.09458,0,.59111],305:[0,.44444,.09426,0,.35555],338:[0,.68611,.11431,0,1.14054],339:[0,.44444,.085,0,.82666],567:[.19444,.44444,.04611,0,.385],710:[0,.69444,.06709,0,.59111],711:[0,.63194,.08271,0,.59111],713:[0,.59444,.10444,0,.59111],714:[0,.69444,.08528,0,.59111],715:[0,.69444,0,0,.59111],728:[0,.69444,.10333,0,.59111],729:[0,.69444,.12945,0,.35555],730:[0,.69444,0,0,.94888],732:[0,.69444,.11472,0,.59111],733:[0,.69444,.11472,0,.59111],915:[0,.68611,.12903,0,.69777],916:[0,.68611,0,0,.94444],920:[0,.68611,.09062,0,.88555],923:[0,.68611,0,0,.80666],926:[0,.68611,.15092,0,.76777],928:[0,.68611,.17208,0,.8961],931:[0,.68611,.11431,0,.82666],933:[0,.68611,.10778,0,.88555],934:[0,.68611,.05632,0,.82666],936:[0,.68611,.10778,0,.88555],937:[0,.68611,.0992,0,.82666],8211:[0,.44444,.09811,0,.59111],8212:[0,.44444,.09811,0,1.18221],8216:[0,.69444,.12945,0,.35555],8217:[0,.69444,.12945,0,.35555],8220:[0,.69444,.16772,0,.62055],8221:[0,.69444,.07939,0,.62055]},"Main-Italic":{32:[0,0,0,0,.25],33:[0,.69444,.12417,0,.30667],34:[0,.69444,.06961,0,.51444],35:[.19444,.69444,.06616,0,.81777],37:[.05556,.75,.13639,0,.81777],38:[0,.69444,.09694,0,.76666],39:[0,.69444,.12417,0,.30667],40:[.25,.75,.16194,0,.40889],41:[.25,.75,.03694,0,.40889],42:[0,.75,.14917,0,.51111],43:[.05667,.56167,.03694,0,.76666],44:[.19444,.10556,0,0,.30667],45:[0,.43056,.02826,0,.35778],46:[0,.10556,0,0,.30667],47:[.25,.75,.16194,0,.51111],48:[0,.64444,.13556,0,.51111],49:[0,.64444,.13556,0,.51111],50:[0,.64444,.13556,0,.51111],51:[0,.64444,.13556,0,.51111],52:[.19444,.64444,.13556,0,.51111],53:[0,.64444,.13556,0,.51111],54:[0,.64444,.13556,0,.51111],55:[.19444,.64444,.13556,0,.51111],56:[0,.64444,.13556,0,.51111],57:[0,.64444,.13556,0,.51111],58:[0,.43056,.0582,0,.30667],59:[.19444,.43056,.0582,0,.30667],61:[-.13313,.36687,.06616,0,.76666],63:[0,.69444,.1225,0,.51111],64:[0,.69444,.09597,0,.76666],65:[0,.68333,0,0,.74333],66:[0,.68333,.10257,0,.70389],67:[0,.68333,.14528,0,.71555],68:[0,.68333,.09403,0,.755],69:[0,.68333,.12028,0,.67833],70:[0,.68333,.13305,0,.65277],71:[0,.68333,.08722,0,.77361],72:[0,.68333,.16389,0,.74333],73:[0,.68333,.15806,0,.38555],74:[0,.68333,.14028,0,.525],75:[0,.68333,.14528,0,.76888],76:[0,.68333,0,0,.62722],77:[0,.68333,.16389,0,.89666],78:[0,.68333,.16389,0,.74333],79:[0,.68333,.09403,0,.76666],80:[0,.68333,.10257,0,.67833],81:[.19444,.68333,.09403,0,.76666],82:[0,.68333,.03868,0,.72944],83:[0,.68333,.11972,0,.56222],84:[0,.68333,.13305,0,.71555],85:[0,.68333,.16389,0,.74333],86:[0,.68333,.18361,0,.74333],87:[0,.68333,.18361,0,.99888],88:[0,.68333,.15806,0,.74333],89:[0,.68333,.19383,0,.74333],90:[0,.68333,.14528,0,.61333],91:[.25,.75,.1875,0,.30667],93:[.25,.75,.10528,0,.30667],94:[0,.69444,.06646,0,.51111],95:[.31,.12056,.09208,0,.51111],97:[0,.43056,.07671,0,.51111],98:[0,.69444,.06312,0,.46],99:[0,.43056,.05653,0,.46],100:[0,.69444,.10333,0,.51111],101:[0,.43056,.07514,0,.46],102:[.19444,.69444,.21194,0,.30667],103:[.19444,.43056,.08847,0,.46],104:[0,.69444,.07671,0,.51111],105:[0,.65536,.1019,0,.30667],106:[.19444,.65536,.14467,0,.30667],107:[0,.69444,.10764,0,.46],108:[0,.69444,.10333,0,.25555],109:[0,.43056,.07671,0,.81777],110:[0,.43056,.07671,0,.56222],111:[0,.43056,.06312,0,.51111],112:[.19444,.43056,.06312,0,.51111],113:[.19444,.43056,.08847,0,.46],114:[0,.43056,.10764,0,.42166],115:[0,.43056,.08208,0,.40889],116:[0,.61508,.09486,0,.33222],117:[0,.43056,.07671,0,.53666],118:[0,.43056,.10764,0,.46],119:[0,.43056,.10764,0,.66444],120:[0,.43056,.12042,0,.46389],121:[.19444,.43056,.08847,0,.48555],122:[0,.43056,.12292,0,.40889],126:[.35,.31786,.11585,0,.51111],160:[0,0,0,0,.25],168:[0,.66786,.10474,0,.51111],176:[0,.69444,0,0,.83129],184:[.17014,0,0,0,.46],198:[0,.68333,.12028,0,.88277],216:[.04861,.73194,.09403,0,.76666],223:[.19444,.69444,.10514,0,.53666],230:[0,.43056,.07514,0,.71555],248:[.09722,.52778,.09194,0,.51111],338:[0,.68333,.12028,0,.98499],339:[0,.43056,.07514,0,.71555],710:[0,.69444,.06646,0,.51111],711:[0,.62847,.08295,0,.51111],713:[0,.56167,.10333,0,.51111],714:[0,.69444,.09694,0,.51111],715:[0,.69444,0,0,.51111],728:[0,.69444,.10806,0,.51111],729:[0,.66786,.11752,0,.30667],730:[0,.69444,0,0,.83129],732:[0,.66786,.11585,0,.51111],733:[0,.69444,.1225,0,.51111],915:[0,.68333,.13305,0,.62722],916:[0,.68333,0,0,.81777],920:[0,.68333,.09403,0,.76666],923:[0,.68333,0,0,.69222],926:[0,.68333,.15294,0,.66444],928:[0,.68333,.16389,0,.74333],931:[0,.68333,.12028,0,.71555],933:[0,.68333,.11111,0,.76666],934:[0,.68333,.05986,0,.71555],936:[0,.68333,.11111,0,.76666],937:[0,.68333,.10257,0,.71555],8211:[0,.43056,.09208,0,.51111],8212:[0,.43056,.09208,0,1.02222],8216:[0,.69444,.12417,0,.30667],8217:[0,.69444,.12417,0,.30667],8220:[0,.69444,.1685,0,.51444],8221:[0,.69444,.06961,0,.51444],8463:[0,.68889,0,0,.54028]},"Main-Regular":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.27778],34:[0,.69444,0,0,.5],35:[.19444,.69444,0,0,.83334],36:[.05556,.75,0,0,.5],37:[.05556,.75,0,0,.83334],38:[0,.69444,0,0,.77778],39:[0,.69444,0,0,.27778],40:[.25,.75,0,0,.38889],41:[.25,.75,0,0,.38889],42:[0,.75,0,0,.5],43:[.08333,.58333,0,0,.77778],44:[.19444,.10556,0,0,.27778],45:[0,.43056,0,0,.33333],46:[0,.10556,0,0,.27778],47:[.25,.75,0,0,.5],48:[0,.64444,0,0,.5],49:[0,.64444,0,0,.5],50:[0,.64444,0,0,.5],51:[0,.64444,0,0,.5],52:[0,.64444,0,0,.5],53:[0,.64444,0,0,.5],54:[0,.64444,0,0,.5],55:[0,.64444,0,0,.5],56:[0,.64444,0,0,.5],57:[0,.64444,0,0,.5],58:[0,.43056,0,0,.27778],59:[.19444,.43056,0,0,.27778],60:[.0391,.5391,0,0,.77778],61:[-.13313,.36687,0,0,.77778],62:[.0391,.5391,0,0,.77778],63:[0,.69444,0,0,.47222],64:[0,.69444,0,0,.77778],65:[0,.68333,0,0,.75],66:[0,.68333,0,0,.70834],67:[0,.68333,0,0,.72222],68:[0,.68333,0,0,.76389],69:[0,.68333,0,0,.68056],70:[0,.68333,0,0,.65278],71:[0,.68333,0,0,.78472],72:[0,.68333,0,0,.75],73:[0,.68333,0,0,.36111],74:[0,.68333,0,0,.51389],75:[0,.68333,0,0,.77778],76:[0,.68333,0,0,.625],77:[0,.68333,0,0,.91667],78:[0,.68333,0,0,.75],79:[0,.68333,0,0,.77778],80:[0,.68333,0,0,.68056],81:[.19444,.68333,0,0,.77778],82:[0,.68333,0,0,.73611],83:[0,.68333,0,0,.55556],84:[0,.68333,0,0,.72222],85:[0,.68333,0,0,.75],86:[0,.68333,.01389,0,.75],87:[0,.68333,.01389,0,1.02778],88:[0,.68333,0,0,.75],89:[0,.68333,.025,0,.75],90:[0,.68333,0,0,.61111],91:[.25,.75,0,0,.27778],92:[.25,.75,0,0,.5],93:[.25,.75,0,0,.27778],94:[0,.69444,0,0,.5],95:[.31,.12056,.02778,0,.5],97:[0,.43056,0,0,.5],98:[0,.69444,0,0,.55556],99:[0,.43056,0,0,.44445],100:[0,.69444,0,0,.55556],101:[0,.43056,0,0,.44445],102:[0,.69444,.07778,0,.30556],103:[.19444,.43056,.01389,0,.5],104:[0,.69444,0,0,.55556],105:[0,.66786,0,0,.27778],106:[.19444,.66786,0,0,.30556],107:[0,.69444,0,0,.52778],108:[0,.69444,0,0,.27778],109:[0,.43056,0,0,.83334],110:[0,.43056,0,0,.55556],111:[0,.43056,0,0,.5],112:[.19444,.43056,0,0,.55556],113:[.19444,.43056,0,0,.52778],114:[0,.43056,0,0,.39167],115:[0,.43056,0,0,.39445],116:[0,.61508,0,0,.38889],117:[0,.43056,0,0,.55556],118:[0,.43056,.01389,0,.52778],119:[0,.43056,.01389,0,.72222],120:[0,.43056,0,0,.52778],121:[.19444,.43056,.01389,0,.52778],122:[0,.43056,0,0,.44445],123:[.25,.75,0,0,.5],124:[.25,.75,0,0,.27778],125:[.25,.75,0,0,.5],126:[.35,.31786,0,0,.5],160:[0,0,0,0,.25],163:[0,.69444,0,0,.76909],167:[.19444,.69444,0,0,.44445],168:[0,.66786,0,0,.5],172:[0,.43056,0,0,.66667],176:[0,.69444,0,0,.75],177:[.08333,.58333,0,0,.77778],182:[.19444,.69444,0,0,.61111],184:[.17014,0,0,0,.44445],198:[0,.68333,0,0,.90278],215:[.08333,.58333,0,0,.77778],216:[.04861,.73194,0,0,.77778],223:[0,.69444,0,0,.5],230:[0,.43056,0,0,.72222],247:[.08333,.58333,0,0,.77778],248:[.09722,.52778,0,0,.5],305:[0,.43056,0,0,.27778],338:[0,.68333,0,0,1.01389],339:[0,.43056,0,0,.77778],567:[.19444,.43056,0,0,.30556],710:[0,.69444,0,0,.5],711:[0,.62847,0,0,.5],713:[0,.56778,0,0,.5],714:[0,.69444,0,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,0,0,.5],729:[0,.66786,0,0,.27778],730:[0,.69444,0,0,.75],732:[0,.66786,0,0,.5],733:[0,.69444,0,0,.5],915:[0,.68333,0,0,.625],916:[0,.68333,0,0,.83334],920:[0,.68333,0,0,.77778],923:[0,.68333,0,0,.69445],926:[0,.68333,0,0,.66667],928:[0,.68333,0,0,.75],931:[0,.68333,0,0,.72222],933:[0,.68333,0,0,.77778],934:[0,.68333,0,0,.72222],936:[0,.68333,0,0,.77778],937:[0,.68333,0,0,.72222],8211:[0,.43056,.02778,0,.5],8212:[0,.43056,.02778,0,1],8216:[0,.69444,0,0,.27778],8217:[0,.69444,0,0,.27778],8220:[0,.69444,0,0,.5],8221:[0,.69444,0,0,.5],8224:[.19444,.69444,0,0,.44445],8225:[.19444,.69444,0,0,.44445],8230:[0,.123,0,0,1.172],8242:[0,.55556,0,0,.275],8407:[0,.71444,.15382,0,.5],8463:[0,.68889,0,0,.54028],8465:[0,.69444,0,0,.72222],8467:[0,.69444,0,.11111,.41667],8472:[.19444,.43056,0,.11111,.63646],8476:[0,.69444,0,0,.72222],8501:[0,.69444,0,0,.61111],8592:[-.13313,.36687,0,0,1],8593:[.19444,.69444,0,0,.5],8594:[-.13313,.36687,0,0,1],8595:[.19444,.69444,0,0,.5],8596:[-.13313,.36687,0,0,1],8597:[.25,.75,0,0,.5],8598:[.19444,.69444,0,0,1],8599:[.19444,.69444,0,0,1],8600:[.19444,.69444,0,0,1],8601:[.19444,.69444,0,0,1],8614:[.011,.511,0,0,1],8617:[.011,.511,0,0,1.126],8618:[.011,.511,0,0,1.126],8636:[-.13313,.36687,0,0,1],8637:[-.13313,.36687,0,0,1],8640:[-.13313,.36687,0,0,1],8641:[-.13313,.36687,0,0,1],8652:[.011,.671,0,0,1],8656:[-.13313,.36687,0,0,1],8657:[.19444,.69444,0,0,.61111],8658:[-.13313,.36687,0,0,1],8659:[.19444,.69444,0,0,.61111],8660:[-.13313,.36687,0,0,1],8661:[.25,.75,0,0,.61111],8704:[0,.69444,0,0,.55556],8706:[0,.69444,.05556,.08334,.5309],8707:[0,.69444,0,0,.55556],8709:[.05556,.75,0,0,.5],8711:[0,.68333,0,0,.83334],8712:[.0391,.5391,0,0,.66667],8715:[.0391,.5391,0,0,.66667],8722:[.08333,.58333,0,0,.77778],8723:[.08333,.58333,0,0,.77778],8725:[.25,.75,0,0,.5],8726:[.25,.75,0,0,.5],8727:[-.03472,.46528,0,0,.5],8728:[-.05555,.44445,0,0,.5],8729:[-.05555,.44445,0,0,.5],8730:[.2,.8,0,0,.83334],8733:[0,.43056,0,0,.77778],8734:[0,.43056,0,0,1],8736:[0,.69224,0,0,.72222],8739:[.25,.75,0,0,.27778],8741:[.25,.75,0,0,.5],8743:[0,.55556,0,0,.66667],8744:[0,.55556,0,0,.66667],8745:[0,.55556,0,0,.66667],8746:[0,.55556,0,0,.66667],8747:[.19444,.69444,.11111,0,.41667],8764:[-.13313,.36687,0,0,.77778],8768:[.19444,.69444,0,0,.27778],8771:[-.03625,.46375,0,0,.77778],8773:[-.022,.589,0,0,.778],8776:[-.01688,.48312,0,0,.77778],8781:[-.03625,.46375,0,0,.77778],8784:[-.133,.673,0,0,.778],8801:[-.03625,.46375,0,0,.77778],8804:[.13597,.63597,0,0,.77778],8805:[.13597,.63597,0,0,.77778],8810:[.0391,.5391,0,0,1],8811:[.0391,.5391,0,0,1],8826:[.0391,.5391,0,0,.77778],8827:[.0391,.5391,0,0,.77778],8834:[.0391,.5391,0,0,.77778],8835:[.0391,.5391,0,0,.77778],8838:[.13597,.63597,0,0,.77778],8839:[.13597,.63597,0,0,.77778],8846:[0,.55556,0,0,.66667],8849:[.13597,.63597,0,0,.77778],8850:[.13597,.63597,0,0,.77778],8851:[0,.55556,0,0,.66667],8852:[0,.55556,0,0,.66667],8853:[.08333,.58333,0,0,.77778],8854:[.08333,.58333,0,0,.77778],8855:[.08333,.58333,0,0,.77778],8856:[.08333,.58333,0,0,.77778],8857:[.08333,.58333,0,0,.77778],8866:[0,.69444,0,0,.61111],8867:[0,.69444,0,0,.61111],8868:[0,.69444,0,0,.77778],8869:[0,.69444,0,0,.77778],8872:[.249,.75,0,0,.867],8900:[-.05555,.44445,0,0,.5],8901:[-.05555,.44445,0,0,.27778],8902:[-.03472,.46528,0,0,.5],8904:[.005,.505,0,0,.9],8942:[.03,.903,0,0,.278],8943:[-.19,.313,0,0,1.172],8945:[-.1,.823,0,0,1.282],8968:[.25,.75,0,0,.44445],8969:[.25,.75,0,0,.44445],8970:[.25,.75,0,0,.44445],8971:[.25,.75,0,0,.44445],8994:[-.14236,.35764,0,0,1],8995:[-.14236,.35764,0,0,1],9136:[.244,.744,0,0,.412],9137:[.244,.745,0,0,.412],9651:[.19444,.69444,0,0,.88889],9657:[-.03472,.46528,0,0,.5],9661:[.19444,.69444,0,0,.88889],9667:[-.03472,.46528,0,0,.5],9711:[.19444,.69444,0,0,1],9824:[.12963,.69444,0,0,.77778],9825:[.12963,.69444,0,0,.77778],9826:[.12963,.69444,0,0,.77778],9827:[.12963,.69444,0,0,.77778],9837:[0,.75,0,0,.38889],9838:[.19444,.69444,0,0,.38889],9839:[.19444,.69444,0,0,.38889],10216:[.25,.75,0,0,.38889],10217:[.25,.75,0,0,.38889],10222:[.244,.744,0,0,.412],10223:[.244,.745,0,0,.412],10229:[.011,.511,0,0,1.609],10230:[.011,.511,0,0,1.638],10231:[.011,.511,0,0,1.859],10232:[.024,.525,0,0,1.609],10233:[.024,.525,0,0,1.638],10234:[.024,.525,0,0,1.858],10236:[.011,.511,0,0,1.638],10815:[0,.68333,0,0,.75],10927:[.13597,.63597,0,0,.77778],10928:[.13597,.63597,0,0,.77778],57376:[.19444,.69444,0,0,0]},"Math-BoldItalic":{32:[0,0,0,0,.25],48:[0,.44444,0,0,.575],49:[0,.44444,0,0,.575],50:[0,.44444,0,0,.575],51:[.19444,.44444,0,0,.575],52:[.19444,.44444,0,0,.575],53:[.19444,.44444,0,0,.575],54:[0,.64444,0,0,.575],55:[.19444,.44444,0,0,.575],56:[0,.64444,0,0,.575],57:[.19444,.44444,0,0,.575],65:[0,.68611,0,0,.86944],66:[0,.68611,.04835,0,.8664],67:[0,.68611,.06979,0,.81694],68:[0,.68611,.03194,0,.93812],69:[0,.68611,.05451,0,.81007],70:[0,.68611,.15972,0,.68889],71:[0,.68611,0,0,.88673],72:[0,.68611,.08229,0,.98229],73:[0,.68611,.07778,0,.51111],74:[0,.68611,.10069,0,.63125],75:[0,.68611,.06979,0,.97118],76:[0,.68611,0,0,.75555],77:[0,.68611,.11424,0,1.14201],78:[0,.68611,.11424,0,.95034],79:[0,.68611,.03194,0,.83666],80:[0,.68611,.15972,0,.72309],81:[.19444,.68611,0,0,.86861],82:[0,.68611,.00421,0,.87235],83:[0,.68611,.05382,0,.69271],84:[0,.68611,.15972,0,.63663],85:[0,.68611,.11424,0,.80027],86:[0,.68611,.25555,0,.67778],87:[0,.68611,.15972,0,1.09305],88:[0,.68611,.07778,0,.94722],89:[0,.68611,.25555,0,.67458],90:[0,.68611,.06979,0,.77257],97:[0,.44444,0,0,.63287],98:[0,.69444,0,0,.52083],99:[0,.44444,0,0,.51342],100:[0,.69444,0,0,.60972],101:[0,.44444,0,0,.55361],102:[.19444,.69444,.11042,0,.56806],103:[.19444,.44444,.03704,0,.5449],104:[0,.69444,0,0,.66759],105:[0,.69326,0,0,.4048],106:[.19444,.69326,.0622,0,.47083],107:[0,.69444,.01852,0,.6037],108:[0,.69444,.0088,0,.34815],109:[0,.44444,0,0,1.0324],110:[0,.44444,0,0,.71296],111:[0,.44444,0,0,.58472],112:[.19444,.44444,0,0,.60092],113:[.19444,.44444,.03704,0,.54213],114:[0,.44444,.03194,0,.5287],115:[0,.44444,0,0,.53125],116:[0,.63492,0,0,.41528],117:[0,.44444,0,0,.68102],118:[0,.44444,.03704,0,.56666],119:[0,.44444,.02778,0,.83148],120:[0,.44444,0,0,.65903],121:[.19444,.44444,.03704,0,.59028],122:[0,.44444,.04213,0,.55509],160:[0,0,0,0,.25],915:[0,.68611,.15972,0,.65694],916:[0,.68611,0,0,.95833],920:[0,.68611,.03194,0,.86722],923:[0,.68611,0,0,.80555],926:[0,.68611,.07458,0,.84125],928:[0,.68611,.08229,0,.98229],931:[0,.68611,.05451,0,.88507],933:[0,.68611,.15972,0,.67083],934:[0,.68611,0,0,.76666],936:[0,.68611,.11653,0,.71402],937:[0,.68611,.04835,0,.8789],945:[0,.44444,0,0,.76064],946:[.19444,.69444,.03403,0,.65972],947:[.19444,.44444,.06389,0,.59003],948:[0,.69444,.03819,0,.52222],949:[0,.44444,0,0,.52882],950:[.19444,.69444,.06215,0,.50833],951:[.19444,.44444,.03704,0,.6],952:[0,.69444,.03194,0,.5618],953:[0,.44444,0,0,.41204],954:[0,.44444,0,0,.66759],955:[0,.69444,0,0,.67083],956:[.19444,.44444,0,0,.70787],957:[0,.44444,.06898,0,.57685],958:[.19444,.69444,.03021,0,.50833],959:[0,.44444,0,0,.58472],960:[0,.44444,.03704,0,.68241],961:[.19444,.44444,0,0,.6118],962:[.09722,.44444,.07917,0,.42361],963:[0,.44444,.03704,0,.68588],964:[0,.44444,.13472,0,.52083],965:[0,.44444,.03704,0,.63055],966:[.19444,.44444,0,0,.74722],967:[.19444,.44444,0,0,.71805],968:[.19444,.69444,.03704,0,.75833],969:[0,.44444,.03704,0,.71782],977:[0,.69444,0,0,.69155],981:[.19444,.69444,0,0,.7125],982:[0,.44444,.03194,0,.975],1009:[.19444,.44444,0,0,.6118],1013:[0,.44444,0,0,.48333],57649:[0,.44444,0,0,.39352],57911:[.19444,.44444,0,0,.43889]},"Math-Italic":{32:[0,0,0,0,.25],48:[0,.43056,0,0,.5],49:[0,.43056,0,0,.5],50:[0,.43056,0,0,.5],51:[.19444,.43056,0,0,.5],52:[.19444,.43056,0,0,.5],53:[.19444,.43056,0,0,.5],54:[0,.64444,0,0,.5],55:[.19444,.43056,0,0,.5],56:[0,.64444,0,0,.5],57:[.19444,.43056,0,0,.5],65:[0,.68333,0,.13889,.75],66:[0,.68333,.05017,.08334,.75851],67:[0,.68333,.07153,.08334,.71472],68:[0,.68333,.02778,.05556,.82792],69:[0,.68333,.05764,.08334,.7382],70:[0,.68333,.13889,.08334,.64306],71:[0,.68333,0,.08334,.78625],72:[0,.68333,.08125,.05556,.83125],73:[0,.68333,.07847,.11111,.43958],74:[0,.68333,.09618,.16667,.55451],75:[0,.68333,.07153,.05556,.84931],76:[0,.68333,0,.02778,.68056],77:[0,.68333,.10903,.08334,.97014],78:[0,.68333,.10903,.08334,.80347],79:[0,.68333,.02778,.08334,.76278],80:[0,.68333,.13889,.08334,.64201],81:[.19444,.68333,0,.08334,.79056],82:[0,.68333,.00773,.08334,.75929],83:[0,.68333,.05764,.08334,.6132],84:[0,.68333,.13889,.08334,.58438],85:[0,.68333,.10903,.02778,.68278],86:[0,.68333,.22222,0,.58333],87:[0,.68333,.13889,0,.94445],88:[0,.68333,.07847,.08334,.82847],89:[0,.68333,.22222,0,.58056],90:[0,.68333,.07153,.08334,.68264],97:[0,.43056,0,0,.52859],98:[0,.69444,0,0,.42917],99:[0,.43056,0,.05556,.43276],100:[0,.69444,0,.16667,.52049],101:[0,.43056,0,.05556,.46563],102:[.19444,.69444,.10764,.16667,.48959],103:[.19444,.43056,.03588,.02778,.47697],104:[0,.69444,0,0,.57616],105:[0,.65952,0,0,.34451],106:[.19444,.65952,.05724,0,.41181],107:[0,.69444,.03148,0,.5206],108:[0,.69444,.01968,.08334,.29838],109:[0,.43056,0,0,.87801],110:[0,.43056,0,0,.60023],111:[0,.43056,0,.05556,.48472],112:[.19444,.43056,0,.08334,.50313],113:[.19444,.43056,.03588,.08334,.44641],114:[0,.43056,.02778,.05556,.45116],115:[0,.43056,0,.05556,.46875],116:[0,.61508,0,.08334,.36111],117:[0,.43056,0,.02778,.57246],118:[0,.43056,.03588,.02778,.48472],119:[0,.43056,.02691,.08334,.71592],120:[0,.43056,0,.02778,.57153],121:[.19444,.43056,.03588,.05556,.49028],122:[0,.43056,.04398,.05556,.46505],160:[0,0,0,0,.25],915:[0,.68333,.13889,.08334,.61528],916:[0,.68333,0,.16667,.83334],920:[0,.68333,.02778,.08334,.76278],923:[0,.68333,0,.16667,.69445],926:[0,.68333,.07569,.08334,.74236],928:[0,.68333,.08125,.05556,.83125],931:[0,.68333,.05764,.08334,.77986],933:[0,.68333,.13889,.05556,.58333],934:[0,.68333,0,.08334,.66667],936:[0,.68333,.11,.05556,.61222],937:[0,.68333,.05017,.08334,.7724],945:[0,.43056,.0037,.02778,.6397],946:[.19444,.69444,.05278,.08334,.56563],947:[.19444,.43056,.05556,0,.51773],948:[0,.69444,.03785,.05556,.44444],949:[0,.43056,0,.08334,.46632],950:[.19444,.69444,.07378,.08334,.4375],951:[.19444,.43056,.03588,.05556,.49653],952:[0,.69444,.02778,.08334,.46944],953:[0,.43056,0,.05556,.35394],954:[0,.43056,0,0,.57616],955:[0,.69444,0,0,.58334],956:[.19444,.43056,0,.02778,.60255],957:[0,.43056,.06366,.02778,.49398],958:[.19444,.69444,.04601,.11111,.4375],959:[0,.43056,0,.05556,.48472],960:[0,.43056,.03588,0,.57003],961:[.19444,.43056,0,.08334,.51702],962:[.09722,.43056,.07986,.08334,.36285],963:[0,.43056,.03588,0,.57141],964:[0,.43056,.1132,.02778,.43715],965:[0,.43056,.03588,.02778,.54028],966:[.19444,.43056,0,.08334,.65417],967:[.19444,.43056,0,.05556,.62569],968:[.19444,.69444,.03588,.11111,.65139],969:[0,.43056,.03588,0,.62245],977:[0,.69444,0,.08334,.59144],981:[.19444,.69444,0,.08334,.59583],982:[0,.43056,.02778,0,.82813],1009:[.19444,.43056,0,.08334,.51702],1013:[0,.43056,0,.05556,.4059],57649:[0,.43056,0,.02778,.32246],57911:[.19444,.43056,0,.08334,.38403]},"SansSerif-Bold":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.36667],34:[0,.69444,0,0,.55834],35:[.19444,.69444,0,0,.91667],36:[.05556,.75,0,0,.55],37:[.05556,.75,0,0,1.02912],38:[0,.69444,0,0,.83056],39:[0,.69444,0,0,.30556],40:[.25,.75,0,0,.42778],41:[.25,.75,0,0,.42778],42:[0,.75,0,0,.55],43:[.11667,.61667,0,0,.85556],44:[.10556,.13056,0,0,.30556],45:[0,.45833,0,0,.36667],46:[0,.13056,0,0,.30556],47:[.25,.75,0,0,.55],48:[0,.69444,0,0,.55],49:[0,.69444,0,0,.55],50:[0,.69444,0,0,.55],51:[0,.69444,0,0,.55],52:[0,.69444,0,0,.55],53:[0,.69444,0,0,.55],54:[0,.69444,0,0,.55],55:[0,.69444,0,0,.55],56:[0,.69444,0,0,.55],57:[0,.69444,0,0,.55],58:[0,.45833,0,0,.30556],59:[.10556,.45833,0,0,.30556],61:[-.09375,.40625,0,0,.85556],63:[0,.69444,0,0,.51945],64:[0,.69444,0,0,.73334],65:[0,.69444,0,0,.73334],66:[0,.69444,0,0,.73334],67:[0,.69444,0,0,.70278],68:[0,.69444,0,0,.79445],69:[0,.69444,0,0,.64167],70:[0,.69444,0,0,.61111],71:[0,.69444,0,0,.73334],72:[0,.69444,0,0,.79445],73:[0,.69444,0,0,.33056],74:[0,.69444,0,0,.51945],75:[0,.69444,0,0,.76389],76:[0,.69444,0,0,.58056],77:[0,.69444,0,0,.97778],78:[0,.69444,0,0,.79445],79:[0,.69444,0,0,.79445],80:[0,.69444,0,0,.70278],81:[.10556,.69444,0,0,.79445],82:[0,.69444,0,0,.70278],83:[0,.69444,0,0,.61111],84:[0,.69444,0,0,.73334],85:[0,.69444,0,0,.76389],86:[0,.69444,.01528,0,.73334],87:[0,.69444,.01528,0,1.03889],88:[0,.69444,0,0,.73334],89:[0,.69444,.0275,0,.73334],90:[0,.69444,0,0,.67223],91:[.25,.75,0,0,.34306],93:[.25,.75,0,0,.34306],94:[0,.69444,0,0,.55],95:[.35,.10833,.03056,0,.55],97:[0,.45833,0,0,.525],98:[0,.69444,0,0,.56111],99:[0,.45833,0,0,.48889],100:[0,.69444,0,0,.56111],101:[0,.45833,0,0,.51111],102:[0,.69444,.07639,0,.33611],103:[.19444,.45833,.01528,0,.55],104:[0,.69444,0,0,.56111],105:[0,.69444,0,0,.25556],106:[.19444,.69444,0,0,.28611],107:[0,.69444,0,0,.53056],108:[0,.69444,0,0,.25556],109:[0,.45833,0,0,.86667],110:[0,.45833,0,0,.56111],111:[0,.45833,0,0,.55],112:[.19444,.45833,0,0,.56111],113:[.19444,.45833,0,0,.56111],114:[0,.45833,.01528,0,.37222],115:[0,.45833,0,0,.42167],116:[0,.58929,0,0,.40417],117:[0,.45833,0,0,.56111],118:[0,.45833,.01528,0,.5],119:[0,.45833,.01528,0,.74445],120:[0,.45833,0,0,.5],121:[.19444,.45833,.01528,0,.5],122:[0,.45833,0,0,.47639],126:[.35,.34444,0,0,.55],160:[0,0,0,0,.25],168:[0,.69444,0,0,.55],176:[0,.69444,0,0,.73334],180:[0,.69444,0,0,.55],184:[.17014,0,0,0,.48889],305:[0,.45833,0,0,.25556],567:[.19444,.45833,0,0,.28611],710:[0,.69444,0,0,.55],711:[0,.63542,0,0,.55],713:[0,.63778,0,0,.55],728:[0,.69444,0,0,.55],729:[0,.69444,0,0,.30556],730:[0,.69444,0,0,.73334],732:[0,.69444,0,0,.55],733:[0,.69444,0,0,.55],915:[0,.69444,0,0,.58056],916:[0,.69444,0,0,.91667],920:[0,.69444,0,0,.85556],923:[0,.69444,0,0,.67223],926:[0,.69444,0,0,.73334],928:[0,.69444,0,0,.79445],931:[0,.69444,0,0,.79445],933:[0,.69444,0,0,.85556],934:[0,.69444,0,0,.79445],936:[0,.69444,0,0,.85556],937:[0,.69444,0,0,.79445],8211:[0,.45833,.03056,0,.55],8212:[0,.45833,.03056,0,1.10001],8216:[0,.69444,0,0,.30556],8217:[0,.69444,0,0,.30556],8220:[0,.69444,0,0,.55834],8221:[0,.69444,0,0,.55834]},"SansSerif-Italic":{32:[0,0,0,0,.25],33:[0,.69444,.05733,0,.31945],34:[0,.69444,.00316,0,.5],35:[.19444,.69444,.05087,0,.83334],36:[.05556,.75,.11156,0,.5],37:[.05556,.75,.03126,0,.83334],38:[0,.69444,.03058,0,.75834],39:[0,.69444,.07816,0,.27778],40:[.25,.75,.13164,0,.38889],41:[.25,.75,.02536,0,.38889],42:[0,.75,.11775,0,.5],43:[.08333,.58333,.02536,0,.77778],44:[.125,.08333,0,0,.27778],45:[0,.44444,.01946,0,.33333],46:[0,.08333,0,0,.27778],47:[.25,.75,.13164,0,.5],48:[0,.65556,.11156,0,.5],49:[0,.65556,.11156,0,.5],50:[0,.65556,.11156,0,.5],51:[0,.65556,.11156,0,.5],52:[0,.65556,.11156,0,.5],53:[0,.65556,.11156,0,.5],54:[0,.65556,.11156,0,.5],55:[0,.65556,.11156,0,.5],56:[0,.65556,.11156,0,.5],57:[0,.65556,.11156,0,.5],58:[0,.44444,.02502,0,.27778],59:[.125,.44444,.02502,0,.27778],61:[-.13,.37,.05087,0,.77778],63:[0,.69444,.11809,0,.47222],64:[0,.69444,.07555,0,.66667],65:[0,.69444,0,0,.66667],66:[0,.69444,.08293,0,.66667],67:[0,.69444,.11983,0,.63889],68:[0,.69444,.07555,0,.72223],69:[0,.69444,.11983,0,.59722],70:[0,.69444,.13372,0,.56945],71:[0,.69444,.11983,0,.66667],72:[0,.69444,.08094,0,.70834],73:[0,.69444,.13372,0,.27778],74:[0,.69444,.08094,0,.47222],75:[0,.69444,.11983,0,.69445],76:[0,.69444,0,0,.54167],77:[0,.69444,.08094,0,.875],78:[0,.69444,.08094,0,.70834],79:[0,.69444,.07555,0,.73611],80:[0,.69444,.08293,0,.63889],81:[.125,.69444,.07555,0,.73611],82:[0,.69444,.08293,0,.64584],83:[0,.69444,.09205,0,.55556],84:[0,.69444,.13372,0,.68056],85:[0,.69444,.08094,0,.6875],86:[0,.69444,.1615,0,.66667],87:[0,.69444,.1615,0,.94445],88:[0,.69444,.13372,0,.66667],89:[0,.69444,.17261,0,.66667],90:[0,.69444,.11983,0,.61111],91:[.25,.75,.15942,0,.28889],93:[.25,.75,.08719,0,.28889],94:[0,.69444,.0799,0,.5],95:[.35,.09444,.08616,0,.5],97:[0,.44444,.00981,0,.48056],98:[0,.69444,.03057,0,.51667],99:[0,.44444,.08336,0,.44445],100:[0,.69444,.09483,0,.51667],101:[0,.44444,.06778,0,.44445],102:[0,.69444,.21705,0,.30556],103:[.19444,.44444,.10836,0,.5],104:[0,.69444,.01778,0,.51667],105:[0,.67937,.09718,0,.23889],106:[.19444,.67937,.09162,0,.26667],107:[0,.69444,.08336,0,.48889],108:[0,.69444,.09483,0,.23889],109:[0,.44444,.01778,0,.79445],110:[0,.44444,.01778,0,.51667],111:[0,.44444,.06613,0,.5],112:[.19444,.44444,.0389,0,.51667],113:[.19444,.44444,.04169,0,.51667],114:[0,.44444,.10836,0,.34167],115:[0,.44444,.0778,0,.38333],116:[0,.57143,.07225,0,.36111],117:[0,.44444,.04169,0,.51667],118:[0,.44444,.10836,0,.46111],119:[0,.44444,.10836,0,.68334],120:[0,.44444,.09169,0,.46111],121:[.19444,.44444,.10836,0,.46111],122:[0,.44444,.08752,0,.43472],126:[.35,.32659,.08826,0,.5],160:[0,0,0,0,.25],168:[0,.67937,.06385,0,.5],176:[0,.69444,0,0,.73752],184:[.17014,0,0,0,.44445],305:[0,.44444,.04169,0,.23889],567:[.19444,.44444,.04169,0,.26667],710:[0,.69444,.0799,0,.5],711:[0,.63194,.08432,0,.5],713:[0,.60889,.08776,0,.5],714:[0,.69444,.09205,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,.09483,0,.5],729:[0,.67937,.07774,0,.27778],730:[0,.69444,0,0,.73752],732:[0,.67659,.08826,0,.5],733:[0,.69444,.09205,0,.5],915:[0,.69444,.13372,0,.54167],916:[0,.69444,0,0,.83334],920:[0,.69444,.07555,0,.77778],923:[0,.69444,0,0,.61111],926:[0,.69444,.12816,0,.66667],928:[0,.69444,.08094,0,.70834],931:[0,.69444,.11983,0,.72222],933:[0,.69444,.09031,0,.77778],934:[0,.69444,.04603,0,.72222],936:[0,.69444,.09031,0,.77778],937:[0,.69444,.08293,0,.72222],8211:[0,.44444,.08616,0,.5],8212:[0,.44444,.08616,0,1],8216:[0,.69444,.07816,0,.27778],8217:[0,.69444,.07816,0,.27778],8220:[0,.69444,.14205,0,.5],8221:[0,.69444,.00316,0,.5]},"SansSerif-Regular":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.31945],34:[0,.69444,0,0,.5],35:[.19444,.69444,0,0,.83334],36:[.05556,.75,0,0,.5],37:[.05556,.75,0,0,.83334],38:[0,.69444,0,0,.75834],39:[0,.69444,0,0,.27778],40:[.25,.75,0,0,.38889],41:[.25,.75,0,0,.38889],42:[0,.75,0,0,.5],43:[.08333,.58333,0,0,.77778],44:[.125,.08333,0,0,.27778],45:[0,.44444,0,0,.33333],46:[0,.08333,0,0,.27778],47:[.25,.75,0,0,.5],48:[0,.65556,0,0,.5],49:[0,.65556,0,0,.5],50:[0,.65556,0,0,.5],51:[0,.65556,0,0,.5],52:[0,.65556,0,0,.5],53:[0,.65556,0,0,.5],54:[0,.65556,0,0,.5],55:[0,.65556,0,0,.5],56:[0,.65556,0,0,.5],57:[0,.65556,0,0,.5],58:[0,.44444,0,0,.27778],59:[.125,.44444,0,0,.27778],61:[-.13,.37,0,0,.77778],63:[0,.69444,0,0,.47222],64:[0,.69444,0,0,.66667],65:[0,.69444,0,0,.66667],66:[0,.69444,0,0,.66667],67:[0,.69444,0,0,.63889],68:[0,.69444,0,0,.72223],69:[0,.69444,0,0,.59722],70:[0,.69444,0,0,.56945],71:[0,.69444,0,0,.66667],72:[0,.69444,0,0,.70834],73:[0,.69444,0,0,.27778],74:[0,.69444,0,0,.47222],75:[0,.69444,0,0,.69445],76:[0,.69444,0,0,.54167],77:[0,.69444,0,0,.875],78:[0,.69444,0,0,.70834],79:[0,.69444,0,0,.73611],80:[0,.69444,0,0,.63889],81:[.125,.69444,0,0,.73611],82:[0,.69444,0,0,.64584],83:[0,.69444,0,0,.55556],84:[0,.69444,0,0,.68056],85:[0,.69444,0,0,.6875],86:[0,.69444,.01389,0,.66667],87:[0,.69444,.01389,0,.94445],88:[0,.69444,0,0,.66667],89:[0,.69444,.025,0,.66667],90:[0,.69444,0,0,.61111],91:[.25,.75,0,0,.28889],93:[.25,.75,0,0,.28889],94:[0,.69444,0,0,.5],95:[.35,.09444,.02778,0,.5],97:[0,.44444,0,0,.48056],98:[0,.69444,0,0,.51667],99:[0,.44444,0,0,.44445],100:[0,.69444,0,0,.51667],101:[0,.44444,0,0,.44445],102:[0,.69444,.06944,0,.30556],103:[.19444,.44444,.01389,0,.5],104:[0,.69444,0,0,.51667],105:[0,.67937,0,0,.23889],106:[.19444,.67937,0,0,.26667],107:[0,.69444,0,0,.48889],108:[0,.69444,0,0,.23889],109:[0,.44444,0,0,.79445],110:[0,.44444,0,0,.51667],111:[0,.44444,0,0,.5],112:[.19444,.44444,0,0,.51667],113:[.19444,.44444,0,0,.51667],114:[0,.44444,.01389,0,.34167],115:[0,.44444,0,0,.38333],116:[0,.57143,0,0,.36111],117:[0,.44444,0,0,.51667],118:[0,.44444,.01389,0,.46111],119:[0,.44444,.01389,0,.68334],120:[0,.44444,0,0,.46111],121:[.19444,.44444,.01389,0,.46111],122:[0,.44444,0,0,.43472],126:[.35,.32659,0,0,.5],160:[0,0,0,0,.25],168:[0,.67937,0,0,.5],176:[0,.69444,0,0,.66667],184:[.17014,0,0,0,.44445],305:[0,.44444,0,0,.23889],567:[.19444,.44444,0,0,.26667],710:[0,.69444,0,0,.5],711:[0,.63194,0,0,.5],713:[0,.60889,0,0,.5],714:[0,.69444,0,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,0,0,.5],729:[0,.67937,0,0,.27778],730:[0,.69444,0,0,.66667],732:[0,.67659,0,0,.5],733:[0,.69444,0,0,.5],915:[0,.69444,0,0,.54167],916:[0,.69444,0,0,.83334],920:[0,.69444,0,0,.77778],923:[0,.69444,0,0,.61111],926:[0,.69444,0,0,.66667],928:[0,.69444,0,0,.70834],931:[0,.69444,0,0,.72222],933:[0,.69444,0,0,.77778],934:[0,.69444,0,0,.72222],936:[0,.69444,0,0,.77778],937:[0,.69444,0,0,.72222],8211:[0,.44444,.02778,0,.5],8212:[0,.44444,.02778,0,1],8216:[0,.69444,0,0,.27778],8217:[0,.69444,0,0,.27778],8220:[0,.69444,0,0,.5],8221:[0,.69444,0,0,.5]},"Script-Regular":{32:[0,0,0,0,.25],65:[0,.7,.22925,0,.80253],66:[0,.7,.04087,0,.90757],67:[0,.7,.1689,0,.66619],68:[0,.7,.09371,0,.77443],69:[0,.7,.18583,0,.56162],70:[0,.7,.13634,0,.89544],71:[0,.7,.17322,0,.60961],72:[0,.7,.29694,0,.96919],73:[0,.7,.19189,0,.80907],74:[.27778,.7,.19189,0,1.05159],75:[0,.7,.31259,0,.91364],76:[0,.7,.19189,0,.87373],77:[0,.7,.15981,0,1.08031],78:[0,.7,.3525,0,.9015],79:[0,.7,.08078,0,.73787],80:[0,.7,.08078,0,1.01262],81:[0,.7,.03305,0,.88282],82:[0,.7,.06259,0,.85],83:[0,.7,.19189,0,.86767],84:[0,.7,.29087,0,.74697],85:[0,.7,.25815,0,.79996],86:[0,.7,.27523,0,.62204],87:[0,.7,.27523,0,.80532],88:[0,.7,.26006,0,.94445],89:[0,.7,.2939,0,.70961],90:[0,.7,.24037,0,.8212],160:[0,0,0,0,.25]},"Size1-Regular":{32:[0,0,0,0,.25],40:[.35001,.85,0,0,.45834],41:[.35001,.85,0,0,.45834],47:[.35001,.85,0,0,.57778],91:[.35001,.85,0,0,.41667],92:[.35001,.85,0,0,.57778],93:[.35001,.85,0,0,.41667],123:[.35001,.85,0,0,.58334],125:[.35001,.85,0,0,.58334],160:[0,0,0,0,.25],710:[0,.72222,0,0,.55556],732:[0,.72222,0,0,.55556],770:[0,.72222,0,0,.55556],771:[0,.72222,0,0,.55556],8214:[-99e-5,.601,0,0,.77778],8593:[1e-5,.6,0,0,.66667],8595:[1e-5,.6,0,0,.66667],8657:[1e-5,.6,0,0,.77778],8659:[1e-5,.6,0,0,.77778],8719:[.25001,.75,0,0,.94445],8720:[.25001,.75,0,0,.94445],8721:[.25001,.75,0,0,1.05556],8730:[.35001,.85,0,0,1],8739:[-.00599,.606,0,0,.33333],8741:[-.00599,.606,0,0,.55556],8747:[.30612,.805,.19445,0,.47222],8748:[.306,.805,.19445,0,.47222],8749:[.306,.805,.19445,0,.47222],8750:[.30612,.805,.19445,0,.47222],8896:[.25001,.75,0,0,.83334],8897:[.25001,.75,0,0,.83334],8898:[.25001,.75,0,0,.83334],8899:[.25001,.75,0,0,.83334],8968:[.35001,.85,0,0,.47222],8969:[.35001,.85,0,0,.47222],8970:[.35001,.85,0,0,.47222],8971:[.35001,.85,0,0,.47222],9168:[-99e-5,.601,0,0,.66667],10216:[.35001,.85,0,0,.47222],10217:[.35001,.85,0,0,.47222],10752:[.25001,.75,0,0,1.11111],10753:[.25001,.75,0,0,1.11111],10754:[.25001,.75,0,0,1.11111],10756:[.25001,.75,0,0,.83334],10758:[.25001,.75,0,0,.83334]},"Size2-Regular":{32:[0,0,0,0,.25],40:[.65002,1.15,0,0,.59722],41:[.65002,1.15,0,0,.59722],47:[.65002,1.15,0,0,.81111],91:[.65002,1.15,0,0,.47222],92:[.65002,1.15,0,0,.81111],93:[.65002,1.15,0,0,.47222],123:[.65002,1.15,0,0,.66667],125:[.65002,1.15,0,0,.66667],160:[0,0,0,0,.25],710:[0,.75,0,0,1],732:[0,.75,0,0,1],770:[0,.75,0,0,1],771:[0,.75,0,0,1],8719:[.55001,1.05,0,0,1.27778],8720:[.55001,1.05,0,0,1.27778],8721:[.55001,1.05,0,0,1.44445],8730:[.65002,1.15,0,0,1],8747:[.86225,1.36,.44445,0,.55556],8748:[.862,1.36,.44445,0,.55556],8749:[.862,1.36,.44445,0,.55556],8750:[.86225,1.36,.44445,0,.55556],8896:[.55001,1.05,0,0,1.11111],8897:[.55001,1.05,0,0,1.11111],8898:[.55001,1.05,0,0,1.11111],8899:[.55001,1.05,0,0,1.11111],8968:[.65002,1.15,0,0,.52778],8969:[.65002,1.15,0,0,.52778],8970:[.65002,1.15,0,0,.52778],8971:[.65002,1.15,0,0,.52778],10216:[.65002,1.15,0,0,.61111],10217:[.65002,1.15,0,0,.61111],10752:[.55001,1.05,0,0,1.51112],10753:[.55001,1.05,0,0,1.51112],10754:[.55001,1.05,0,0,1.51112],10756:[.55001,1.05,0,0,1.11111],10758:[.55001,1.05,0,0,1.11111]},"Size3-Regular":{32:[0,0,0,0,.25],40:[.95003,1.45,0,0,.73611],41:[.95003,1.45,0,0,.73611],47:[.95003,1.45,0,0,1.04445],91:[.95003,1.45,0,0,.52778],92:[.95003,1.45,0,0,1.04445],93:[.95003,1.45,0,0,.52778],123:[.95003,1.45,0,0,.75],125:[.95003,1.45,0,0,.75],160:[0,0,0,0,.25],710:[0,.75,0,0,1.44445],732:[0,.75,0,0,1.44445],770:[0,.75,0,0,1.44445],771:[0,.75,0,0,1.44445],8730:[.95003,1.45,0,0,1],8968:[.95003,1.45,0,0,.58334],8969:[.95003,1.45,0,0,.58334],8970:[.95003,1.45,0,0,.58334],8971:[.95003,1.45,0,0,.58334],10216:[.95003,1.45,0,0,.75],10217:[.95003,1.45,0,0,.75]},"Size4-Regular":{32:[0,0,0,0,.25],40:[1.25003,1.75,0,0,.79167],41:[1.25003,1.75,0,0,.79167],47:[1.25003,1.75,0,0,1.27778],91:[1.25003,1.75,0,0,.58334],92:[1.25003,1.75,0,0,1.27778],93:[1.25003,1.75,0,0,.58334],123:[1.25003,1.75,0,0,.80556],125:[1.25003,1.75,0,0,.80556],160:[0,0,0,0,.25],710:[0,.825,0,0,1.8889],732:[0,.825,0,0,1.8889],770:[0,.825,0,0,1.8889],771:[0,.825,0,0,1.8889],8730:[1.25003,1.75,0,0,1],8968:[1.25003,1.75,0,0,.63889],8969:[1.25003,1.75,0,0,.63889],8970:[1.25003,1.75,0,0,.63889],8971:[1.25003,1.75,0,0,.63889],9115:[.64502,1.155,0,0,.875],9116:[1e-5,.6,0,0,.875],9117:[.64502,1.155,0,0,.875],9118:[.64502,1.155,0,0,.875],9119:[1e-5,.6,0,0,.875],9120:[.64502,1.155,0,0,.875],9121:[.64502,1.155,0,0,.66667],9122:[-99e-5,.601,0,0,.66667],9123:[.64502,1.155,0,0,.66667],9124:[.64502,1.155,0,0,.66667],9125:[-99e-5,.601,0,0,.66667],9126:[.64502,1.155,0,0,.66667],9127:[1e-5,.9,0,0,.88889],9128:[.65002,1.15,0,0,.88889],9129:[.90001,0,0,0,.88889],9130:[0,.3,0,0,.88889],9131:[1e-5,.9,0,0,.88889],9132:[.65002,1.15,0,0,.88889],9133:[.90001,0,0,0,.88889],9143:[.88502,.915,0,0,1.05556],10216:[1.25003,1.75,0,0,.80556],10217:[1.25003,1.75,0,0,.80556],57344:[-.00499,.605,0,0,1.05556],57345:[-.00499,.605,0,0,1.05556],57680:[0,.12,0,0,.45],57681:[0,.12,0,0,.45],57682:[0,.12,0,0,.45],57683:[0,.12,0,0,.45]},"Typewriter-Regular":{32:[0,0,0,0,.525],33:[0,.61111,0,0,.525],34:[0,.61111,0,0,.525],35:[0,.61111,0,0,.525],36:[.08333,.69444,0,0,.525],37:[.08333,.69444,0,0,.525],38:[0,.61111,0,0,.525],39:[0,.61111,0,0,.525],40:[.08333,.69444,0,0,.525],41:[.08333,.69444,0,0,.525],42:[0,.52083,0,0,.525],43:[-.08056,.53055,0,0,.525],44:[.13889,.125,0,0,.525],45:[-.08056,.53055,0,0,.525],46:[0,.125,0,0,.525],47:[.08333,.69444,0,0,.525],48:[0,.61111,0,0,.525],49:[0,.61111,0,0,.525],50:[0,.61111,0,0,.525],51:[0,.61111,0,0,.525],52:[0,.61111,0,0,.525],53:[0,.61111,0,0,.525],54:[0,.61111,0,0,.525],55:[0,.61111,0,0,.525],56:[0,.61111,0,0,.525],57:[0,.61111,0,0,.525],58:[0,.43056,0,0,.525],59:[.13889,.43056,0,0,.525],60:[-.05556,.55556,0,0,.525],61:[-.19549,.41562,0,0,.525],62:[-.05556,.55556,0,0,.525],63:[0,.61111,0,0,.525],64:[0,.61111,0,0,.525],65:[0,.61111,0,0,.525],66:[0,.61111,0,0,.525],67:[0,.61111,0,0,.525],68:[0,.61111,0,0,.525],69:[0,.61111,0,0,.525],70:[0,.61111,0,0,.525],71:[0,.61111,0,0,.525],72:[0,.61111,0,0,.525],73:[0,.61111,0,0,.525],74:[0,.61111,0,0,.525],75:[0,.61111,0,0,.525],76:[0,.61111,0,0,.525],77:[0,.61111,0,0,.525],78:[0,.61111,0,0,.525],79:[0,.61111,0,0,.525],80:[0,.61111,0,0,.525],81:[.13889,.61111,0,0,.525],82:[0,.61111,0,0,.525],83:[0,.61111,0,0,.525],84:[0,.61111,0,0,.525],85:[0,.61111,0,0,.525],86:[0,.61111,0,0,.525],87:[0,.61111,0,0,.525],88:[0,.61111,0,0,.525],89:[0,.61111,0,0,.525],90:[0,.61111,0,0,.525],91:[.08333,.69444,0,0,.525],92:[.08333,.69444,0,0,.525],93:[.08333,.69444,0,0,.525],94:[0,.61111,0,0,.525],95:[.09514,0,0,0,.525],96:[0,.61111,0,0,.525],97:[0,.43056,0,0,.525],98:[0,.61111,0,0,.525],99:[0,.43056,0,0,.525],100:[0,.61111,0,0,.525],101:[0,.43056,0,0,.525],102:[0,.61111,0,0,.525],103:[.22222,.43056,0,0,.525],104:[0,.61111,0,0,.525],105:[0,.61111,0,0,.525],106:[.22222,.61111,0,0,.525],107:[0,.61111,0,0,.525],108:[0,.61111,0,0,.525],109:[0,.43056,0,0,.525],110:[0,.43056,0,0,.525],111:[0,.43056,0,0,.525],112:[.22222,.43056,0,0,.525],113:[.22222,.43056,0,0,.525],114:[0,.43056,0,0,.525],115:[0,.43056,0,0,.525],116:[0,.55358,0,0,.525],117:[0,.43056,0,0,.525],118:[0,.43056,0,0,.525],119:[0,.43056,0,0,.525],120:[0,.43056,0,0,.525],121:[.22222,.43056,0,0,.525],122:[0,.43056,0,0,.525],123:[.08333,.69444,0,0,.525],124:[.08333,.69444,0,0,.525],125:[.08333,.69444,0,0,.525],126:[0,.61111,0,0,.525],127:[0,.61111,0,0,.525],160:[0,0,0,0,.525],176:[0,.61111,0,0,.525],184:[.19445,0,0,0,.525],305:[0,.43056,0,0,.525],567:[.22222,.43056,0,0,.525],711:[0,.56597,0,0,.525],713:[0,.56555,0,0,.525],714:[0,.61111,0,0,.525],715:[0,.61111,0,0,.525],728:[0,.61111,0,0,.525],730:[0,.61111,0,0,.525],770:[0,.61111,0,0,.525],771:[0,.61111,0,0,.525],776:[0,.61111,0,0,.525],915:[0,.61111,0,0,.525],916:[0,.61111,0,0,.525],920:[0,.61111,0,0,.525],923:[0,.61111,0,0,.525],926:[0,.61111,0,0,.525],928:[0,.61111,0,0,.525],931:[0,.61111,0,0,.525],933:[0,.61111,0,0,.525],934:[0,.61111,0,0,.525],936:[0,.61111,0,0,.525],937:[0,.61111,0,0,.525],8216:[0,.61111,0,0,.525],8217:[0,.61111,0,0,.525],8242:[0,.61111,0,0,.525],9251:[.11111,.21944,0,0,.525]}},xh={slant:[.25,.25,.25],space:[0,0,0],stretch:[0,0,0],shrink:[0,0,0],xHeight:[.431,.431,.431],quad:[1,1.171,1.472],extraSpace:[0,0,0],num1:[.677,.732,.925],num2:[.394,.384,.387],num3:[.444,.471,.504],denom1:[.686,.752,1.025],denom2:[.345,.344,.532],sup1:[.413,.503,.504],sup2:[.363,.431,.404],sup3:[.289,.286,.294],sub1:[.15,.143,.2],sub2:[.247,.286,.4],supDrop:[.386,.353,.494],subDrop:[.05,.071,.1],delim1:[2.39,1.7,1.98],delim2:[1.01,1.157,1.42],axisHeight:[.25,.25,.25],defaultRuleThickness:[.04,.049,.049],bigOpSpacing1:[.111,.111,.111],bigOpSpacing2:[.166,.166,.166],bigOpSpacing3:[.2,.2,.2],bigOpSpacing4:[.6,.611,.611],bigOpSpacing5:[.1,.143,.143],sqrtRuleThickness:[.04,.04,.04],ptPerEm:[10,10,10],doubleRuleSep:[.2,.2,.2],arrayRuleWidth:[.04,.04,.04],fboxsep:[.3,.3,.3],fboxrule:[.04,.04,.04]},K6={\u00C5:"A",\u00D0:"D",\u00DE:"o",\u00E5:"a",\u00F0:"d",\u00FE:"o",\u0410:"A",\u0411:"B",\u0412:"B",\u0413:"F",\u0414:"A",\u0415:"E",\u0416:"K",\u0417:"3",\u0418:"N",\u0419:"N",\u041A:"K",\u041B:"N",\u041C:"M",\u041D:"H",\u041E:"O",\u041F:"N",\u0420:"P",\u0421:"C",\u0422:"T",\u0423:"y",\u0424:"O",\u0425:"X",\u0426:"U",\u0427:"h",\u0428:"W",\u0429:"W",\u042A:"B",\u042B:"X",\u042C:"B",\u042D:"3",\u042E:"X",\u042F:"R",\u0430:"a",\u0431:"b",\u0432:"a",\u0433:"r",\u0434:"y",\u0435:"e",\u0436:"m",\u0437:"e",\u0438:"n",\u0439:"n",\u043A:"n",\u043B:"n",\u043C:"m",\u043D:"n",\u043E:"o",\u043F:"n",\u0440:"p",\u0441:"c",\u0442:"o",\u0443:"y",\u0444:"b",\u0445:"x",\u0446:"n",\u0447:"n",\u0448:"w",\u0449:"w",\u044A:"a",\u044B:"m",\u044C:"a",\u044D:"e",\u044E:"m",\u044F:"r"};l1={};iA=[[1,1,1],[2,1,1],[3,1,1],[4,2,1],[5,2,1],[6,3,1],[7,4,2],[8,6,3],[9,7,6],[10,8,7],[11,10,9]],Z6=[.5,.6,.7,.8,.9,1,1.2,1.44,1.728,2.074,2.488],Q6=function(e,t){return t.size<2?e:iA[e-1][t.size-1]},Lh=class i{constructor(e){this.style=void 0,this.color=void 0,this.size=void 0,this.textSize=void 0,this.phantom=void 0,this.font=void 0,this.fontFamily=void 0,this.fontWeight=void 0,this.fontShape=void 0,this.sizeMultiplier=void 0,this.maxSize=void 0,this.minRuleThickness=void 0,this._fontMetrics=void 0,this.style=e.style,this.color=e.color,this.size=e.size||i.BASESIZE,this.textSize=e.textSize||this.size,this.phantom=!!e.phantom,this.font=e.font||"",this.fontFamily=e.fontFamily||"",this.fontWeight=e.fontWeight||"",this.fontShape=e.fontShape||"",this.sizeMultiplier=Z6[this.size-1],this.maxSize=e.maxSize,this.minRuleThickness=e.minRuleThickness,this._fontMetrics=void 0}extend(e){var t={style:this.style,size:this.size,textSize:this.textSize,color:this.color,phantom:this.phantom,font:this.font,fontFamily:this.fontFamily,fontWeight:this.fontWeight,fontShape:this.fontShape,maxSize:this.maxSize,minRuleThickness:this.minRuleThickness};for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);return new i(t)}havingStyle(e){return this.style===e?this:this.extend({style:e,size:Q6(this.textSize,e)})}havingCrampedStyle(){return this.havingStyle(this.style.cramp())}havingSize(e){return this.size===e&&this.textSize===e?this:this.extend({style:this.style.text(),size:e,textSize:e,sizeMultiplier:Z6[e-1]})}havingBaseStyle(e){e=e||this.style.text();var t=Q6(i.BASESIZE,e);return this.size===t&&this.textSize===i.BASESIZE&&this.style===e?this:this.extend({style:e,size:t})}havingBaseSizing(){var e;switch(this.style.id){case 4:case 5:e=3;break;case 6:case 7:e=1;break;default:e=6}return this.extend({style:this.style.text(),size:e})}withColor(e){return this.extend({color:e})}withPhantom(){return this.extend({phantom:!0})}withFont(e){return this.extend({font:e})}withTextFontFamily(e){return this.extend({fontFamily:e,font:""})}withTextFontWeight(e){return this.extend({fontWeight:e,font:""})}withTextFontShape(e){return this.extend({fontShape:e,font:""})}sizingClasses(e){return e.size!==this.size?["sizing","reset-size"+e.size,"size"+this.size]:[]}baseSizingClasses(){return this.size!==i.BASESIZE?["sizing","reset-size"+this.size,"size"+i.BASESIZE]:[]}fontMetrics(){return this._fontMetrics||(this._fontMetrics=tA(this.size)),this._fontMetrics}getColor(){return this.phantom?"transparent":this.color}};Lh.BASESIZE=6;M1={pt:1,mm:7227/2540,cm:7227/254,in:72.27,bp:803/800,pc:12,dd:1238/1157,cc:14856/1157,nd:685/642,nc:1370/107,sp:1/65536,px:803/800},rA={ex:!0,em:!0,mu:!0},k4=function(e){return typeof e!="string"&&(e=e.unit),e in M1||e in rA||e==="ex"},Ke=function(e,t){var r;if(e.unit in M1)r=M1[e.unit]/t.fontMetrics().ptPerEm/t.sizeMultiplier;else if(e.unit==="mu")r=t.fontMetrics().cssEmPerMu;else{var n;if(t.style.isTight()?n=t.havingStyle(t.style.text()):n=t,e.unit==="ex")r=n.fontMetrics().xHeight;else if(e.unit==="em")r=n.fontMetrics().quad;else throw new G("Invalid unit: '"+e.unit+"'");n!==t&&(r*=n.sizeMultiplier/t.sizeMultiplier)}return Math.min(e.number*r,t.maxSize)},V=function(e){return+e.toFixed(4)+"em"},xn=function(e){return e.filter(t=>t).join(" ")},C4=function(e,t,r){if(this.classes=e||[],this.attributes={},this.height=0,this.depth=0,this.maxFontSize=0,this.style=r||{},t){t.style.isTight()&&this.classes.push("mtight");var n=t.getColor();n&&(this.style.color=n)}},_4=function(e){var t=document.createElement(e);t.className=xn(this.classes);for(var r in this.style)this.style.hasOwnProperty(r)&&(t.style[r]=this.style[r]);for(var n in this.attributes)this.attributes.hasOwnProperty(n)&&t.setAttribute(n,this.attributes[n]);for(var s=0;s/=\x00-\x1f]/,L4=function(e){var t="<"+e;this.classes.length&&(t+=' class="'+Ee.escape(xn(this.classes))+'"');var r="";for(var n in this.style)this.style.hasOwnProperty(n)&&(r+=Ee.hyphenate(n)+":"+this.style[n]+";");r&&(t+=' style="'+Ee.escape(r)+'"');for(var s in this.attributes)if(this.attributes.hasOwnProperty(s)){if(nA.test(s))throw new G("Invalid attribute name '"+s+"'");t+=" "+s+'="'+Ee.escape(this.attributes[s])+'"'}t+=">";for(var a=0;a",t},ps=class{constructor(e,t,r,n){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.width=void 0,this.maxFontSize=void 0,this.style=void 0,C4.call(this,e,r,n),this.children=t||[]}setAttribute(e,t){this.attributes[e]=t}hasClass(e){return this.classes.includes(e)}toNode(){return _4.call(this,"span")}toMarkup(){return L4.call(this,"span")}},al=class{constructor(e,t,r,n){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,C4.call(this,t,n),this.children=r||[],this.setAttribute("href",e)}setAttribute(e,t){this.attributes[e]=t}hasClass(e){return this.classes.includes(e)}toNode(){return _4.call(this,"a")}toMarkup(){return L4.call(this,"a")}},T1=class{constructor(e,t,r){this.src=void 0,this.alt=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.alt=t,this.src=e,this.classes=["mord"],this.style=r}hasClass(e){return this.classes.includes(e)}toNode(){var e=document.createElement("img");e.src=this.src,e.alt=this.alt,e.className="mord";for(var t in this.style)this.style.hasOwnProperty(t)&&(e.style[t]=this.style[t]);return e}toMarkup(){var e=''+Ee.escape(this.alt)+'0&&(t=document.createElement("span"),t.style.marginRight=V(this.italic)),this.classes.length>0&&(t=t||document.createElement("span"),t.className=xn(this.classes));for(var r in this.style)this.style.hasOwnProperty(r)&&(t=t||document.createElement("span"),t.style[r]=this.style[r]);return t?(t.appendChild(e),t):e}toMarkup(){var e=!1,t="0&&(r+="margin-right:"+this.italic+"em;");for(var n in this.style)this.style.hasOwnProperty(n)&&(r+=Ee.hyphenate(n)+":"+this.style[n]+";");r&&(e=!0,t+=' style="'+Ee.escape(r)+'"');var s=Ee.escape(this.text);return e?(t+=">",t+=s,t+="",t):s}},Yi=class{constructor(e,t){this.children=void 0,this.attributes=void 0,this.children=e||[],this.attributes=t||{}}toNode(){var e="http://www.w3.org/2000/svg",t=document.createElementNS(e,"svg");for(var r in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,r)&&t.setAttribute(r,this.attributes[r]);for(var n=0;n':''}},ol=class{constructor(e){this.attributes=void 0,this.attributes=e||{}}toNode(){var e="http://www.w3.org/2000/svg",t=document.createElementNS(e,"line");for(var r in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,r)&&t.setAttribute(r,this.attributes[r]);return t}toMarkup(){var e="","\\gt",!0);u(p,v,S,"\u2208","\\in",!0);u(p,v,S,"\uE020","\\@not");u(p,v,S,"\u2282","\\subset",!0);u(p,v,S,"\u2283","\\supset",!0);u(p,v,S,"\u2286","\\subseteq",!0);u(p,v,S,"\u2287","\\supseteq",!0);u(p,N,S,"\u2288","\\nsubseteq",!0);u(p,N,S,"\u2289","\\nsupseteq",!0);u(p,v,S,"\u22A8","\\models");u(p,v,S,"\u2190","\\leftarrow",!0);u(p,v,S,"\u2264","\\le");u(p,v,S,"\u2264","\\leq",!0);u(p,v,S,"<","\\lt",!0);u(p,v,S,"\u2192","\\rightarrow",!0);u(p,v,S,"\u2192","\\to");u(p,N,S,"\u2271","\\ngeq",!0);u(p,N,S,"\u2270","\\nleq",!0);u(p,v,Dr,"\xA0","\\ ");u(p,v,Dr,"\xA0","\\space");u(p,v,Dr,"\xA0","\\nobreakspace");u(H,v,Dr,"\xA0","\\ ");u(H,v,Dr,"\xA0"," ");u(H,v,Dr,"\xA0","\\space");u(H,v,Dr,"\xA0","\\nobreakspace");u(p,v,Dr,null,"\\nobreak");u(p,v,Dr,null,"\\allowbreak");u(p,v,Ph,",",",");u(p,v,Ph,";",";");u(p,N,ie,"\u22BC","\\barwedge",!0);u(p,N,ie,"\u22BB","\\veebar",!0);u(p,v,ie,"\u2299","\\odot",!0);u(p,v,ie,"\u2295","\\oplus",!0);u(p,v,ie,"\u2297","\\otimes",!0);u(p,v,_,"\u2202","\\partial",!0);u(p,v,ie,"\u2298","\\oslash",!0);u(p,N,ie,"\u229A","\\circledcirc",!0);u(p,N,ie,"\u22A1","\\boxdot",!0);u(p,v,ie,"\u25B3","\\bigtriangleup");u(p,v,ie,"\u25BD","\\bigtriangledown");u(p,v,ie,"\u2020","\\dagger");u(p,v,ie,"\u22C4","\\diamond");u(p,v,ie,"\u22C6","\\star");u(p,v,ie,"\u25C3","\\triangleleft");u(p,v,ie,"\u25B9","\\triangleright");u(p,v,pi,"{","\\{");u(H,v,_,"{","\\{");u(H,v,_,"{","\\textbraceleft");u(p,v,Ft,"}","\\}");u(H,v,_,"}","\\}");u(H,v,_,"}","\\textbraceright");u(p,v,pi,"{","\\lbrace");u(p,v,Ft,"}","\\rbrace");u(p,v,pi,"[","\\lbrack",!0);u(H,v,_,"[","\\lbrack",!0);u(p,v,Ft,"]","\\rbrack",!0);u(H,v,_,"]","\\rbrack",!0);u(p,v,pi,"(","\\lparen",!0);u(p,v,Ft,")","\\rparen",!0);u(H,v,_,"<","\\textless",!0);u(H,v,_,">","\\textgreater",!0);u(p,v,pi,"\u230A","\\lfloor",!0);u(p,v,Ft,"\u230B","\\rfloor",!0);u(p,v,pi,"\u2308","\\lceil",!0);u(p,v,Ft,"\u2309","\\rceil",!0);u(p,v,_,"\\","\\backslash");u(p,v,_,"\u2223","|");u(p,v,_,"\u2223","\\vert");u(H,v,_,"|","\\textbar",!0);u(p,v,_,"\u2225","\\|");u(p,v,_,"\u2225","\\Vert");u(H,v,_,"\u2225","\\textbardbl");u(H,v,_,"~","\\textasciitilde");u(H,v,_,"\\","\\textbackslash");u(H,v,_,"^","\\textasciicircum");u(p,v,S,"\u2191","\\uparrow",!0);u(p,v,S,"\u21D1","\\Uparrow",!0);u(p,v,S,"\u2193","\\downarrow",!0);u(p,v,S,"\u21D3","\\Downarrow",!0);u(p,v,S,"\u2195","\\updownarrow",!0);u(p,v,S,"\u21D5","\\Updownarrow",!0);u(p,v,lt,"\u2210","\\coprod");u(p,v,lt,"\u22C1","\\bigvee");u(p,v,lt,"\u22C0","\\bigwedge");u(p,v,lt,"\u2A04","\\biguplus");u(p,v,lt,"\u22C2","\\bigcap");u(p,v,lt,"\u22C3","\\bigcup");u(p,v,lt,"\u222B","\\int");u(p,v,lt,"\u222B","\\intop");u(p,v,lt,"\u222C","\\iint");u(p,v,lt,"\u222D","\\iiint");u(p,v,lt,"\u220F","\\prod");u(p,v,lt,"\u2211","\\sum");u(p,v,lt,"\u2A02","\\bigotimes");u(p,v,lt,"\u2A01","\\bigoplus");u(p,v,lt,"\u2A00","\\bigodot");u(p,v,lt,"\u222E","\\oint");u(p,v,lt,"\u222F","\\oiint");u(p,v,lt,"\u2230","\\oiiint");u(p,v,lt,"\u2A06","\\bigsqcup");u(p,v,lt,"\u222B","\\smallint");u(H,v,pa,"\u2026","\\textellipsis");u(p,v,pa,"\u2026","\\mathellipsis");u(H,v,pa,"\u2026","\\ldots",!0);u(p,v,pa,"\u2026","\\ldots",!0);u(p,v,pa,"\u22EF","\\@cdots",!0);u(p,v,pa,"\u22F1","\\ddots",!0);u(p,v,_,"\u22EE","\\varvdots");u(H,v,_,"\u22EE","\\varvdots");u(p,v,We,"\u02CA","\\acute");u(p,v,We,"\u02CB","\\grave");u(p,v,We,"\xA8","\\ddot");u(p,v,We,"~","\\tilde");u(p,v,We,"\u02C9","\\bar");u(p,v,We,"\u02D8","\\breve");u(p,v,We,"\u02C7","\\check");u(p,v,We,"^","\\hat");u(p,v,We,"\u20D7","\\vec");u(p,v,We,"\u02D9","\\dot");u(p,v,We,"\u02DA","\\mathring");u(p,v,se,"\uE131","\\@imath");u(p,v,se,"\uE237","\\@jmath");u(p,v,_,"\u0131","\u0131");u(p,v,_,"\u0237","\u0237");u(H,v,_,"\u0131","\\i",!0);u(H,v,_,"\u0237","\\j",!0);u(H,v,_,"\xDF","\\ss",!0);u(H,v,_,"\xE6","\\ae",!0);u(H,v,_,"\u0153","\\oe",!0);u(H,v,_,"\xF8","\\o",!0);u(H,v,_,"\xC6","\\AE",!0);u(H,v,_,"\u0152","\\OE",!0);u(H,v,_,"\xD8","\\O",!0);u(H,v,We,"\u02CA","\\'");u(H,v,We,"\u02CB","\\`");u(H,v,We,"\u02C6","\\^");u(H,v,We,"\u02DC","\\~");u(H,v,We,"\u02C9","\\=");u(H,v,We,"\u02D8","\\u");u(H,v,We,"\u02D9","\\.");u(H,v,We,"\xB8","\\c");u(H,v,We,"\u02DA","\\r");u(H,v,We,"\u02C7","\\v");u(H,v,We,"\xA8",'\\"');u(H,v,We,"\u02DD","\\H");u(H,v,We,"\u25EF","\\textcircled");I4={"--":!0,"---":!0,"``":!0,"''":!0};u(H,v,_,"\u2013","--",!0);u(H,v,_,"\u2013","\\textendash");u(H,v,_,"\u2014","---",!0);u(H,v,_,"\u2014","\\textemdash");u(H,v,_,"\u2018","`",!0);u(H,v,_,"\u2018","\\textquoteleft");u(H,v,_,"\u2019","'",!0);u(H,v,_,"\u2019","\\textquoteright");u(H,v,_,"\u201C","``",!0);u(H,v,_,"\u201C","\\textquotedblleft");u(H,v,_,"\u201D","''",!0);u(H,v,_,"\u201D","\\textquotedblright");u(p,v,_,"\xB0","\\degree",!0);u(H,v,_,"\xB0","\\degree");u(H,v,_,"\xB0","\\textdegree",!0);u(p,v,_,"\xA3","\\pounds");u(p,v,_,"\xA3","\\mathsterling",!0);u(H,v,_,"\xA3","\\pounds");u(H,v,_,"\xA3","\\textsterling",!0);u(p,N,_,"\u2720","\\maltese");u(H,N,_,"\u2720","\\maltese");e4='0123456789/@."';for(yh=0;yh0)return Vi(s,h,n,t,a.concat(d));if(l){var c,f;if(l==="boldsymbol"){var m=cA(s,n,t,a,r);c=m.fontName,f=[m.fontClass]}else o?(c=D4[l].fontName,f=[l]):(c=Th(l,t.fontWeight,t.fontShape),f=[l,t.fontWeight,t.fontShape]);if(Fh(s,c,n).metrics)return Vi(s,c,n,t,a.concat(f));if(I4.hasOwnProperty(s)&&c.slice(0,10)==="Typewriter"){for(var g=[],x=0;x{if(xn(i.classes)!==xn(e.classes)||i.skew!==e.skew||i.maxFontSize!==e.maxFontSize)return!1;if(i.classes.length===1){var t=i.classes[0];if(t==="mbin"||t==="mord")return!1}for(var r in i.style)if(i.style.hasOwnProperty(r)&&i.style[r]!==e.style[r])return!1;for(var n in e.style)if(e.style.hasOwnProperty(n)&&i.style[n]!==e.style[n])return!1;return!0},mA=i=>{for(var e=0;et&&(t=a.height),a.depth>r&&(r=a.depth),a.maxFontSize>n&&(n=a.maxFontSize)}e.height=t,e.depth=r,e.maxFontSize=n},ei=function(e,t,r,n){var s=new ps(e,t,r,n);return D1(s),s},z4=(i,e,t,r)=>new ps(i,e,t,r),pA=function(e,t,r){var n=ei([e],[],t);return n.height=Math.max(r||t.fontMetrics().defaultRuleThickness,t.minRuleThickness),n.style.borderBottomWidth=V(n.height),n.maxFontSize=1,n},gA=function(e,t,r,n){var s=new al(e,t,r,n);return D1(s),s},R4=function(e){var t=new ms(e);return D1(t),t},xA=function(e,t){return e instanceof ms?ei([],[e],t):e},yA=function(e){if(e.positionType==="individualShift"){for(var t=e.children,r=[t[0]],n=-t[0].shift-t[0].elem.depth,s=n,a=1;a{var t=ei(["mspace"],[],e),r=Ke(i,e);return t.style.marginRight=V(r),t},Th=function(e,t,r){var n="";switch(e){case"amsrm":n="AMS";break;case"textrm":n="Main";break;case"textsf":n="SansSerif";break;case"texttt":n="Typewriter";break;default:n=e}var s;return t==="textbf"&&r==="textit"?s="BoldItalic":t==="textbf"?s="Bold":t==="textit"?s="Italic":s="Regular",n+"-"+s},D4={mathbf:{variant:"bold",fontName:"Main-Bold"},mathrm:{variant:"normal",fontName:"Main-Regular"},textit:{variant:"italic",fontName:"Main-Italic"},mathit:{variant:"italic",fontName:"Main-Italic"},mathnormal:{variant:"italic",fontName:"Math-Italic"},mathsfit:{variant:"sans-serif-italic",fontName:"SansSerif-Italic"},mathbb:{variant:"double-struck",fontName:"AMS-Regular"},mathcal:{variant:"script",fontName:"Caligraphic-Regular"},mathfrak:{variant:"fraktur",fontName:"Fraktur-Regular"},mathscr:{variant:"script",fontName:"Script-Regular"},mathsf:{variant:"sans-serif",fontName:"SansSerif-Regular"},mathtt:{variant:"monospace",fontName:"Typewriter-Regular"}},O4={vec:["vec",.471,.714],oiintSize1:["oiintSize1",.957,.499],oiintSize2:["oiintSize2",1.472,.659],oiiintSize1:["oiiintSize1",1.304,.499],oiiintSize2:["oiiintSize2",1.98,.659]},wA=function(e,t){var[r,n,s]=O4[e],a=new hr(r),o=new Yi([a],{width:V(n),height:V(s),style:"width:"+V(n),viewBox:"0 0 "+1e3*n+" "+1e3*s,preserveAspectRatio:"xMinYMin"}),l=z4(["overlay"],[o],t);return l.height=s,l.style.height=V(s),l.style.width=V(n),l},z={fontMap:D4,makeSymbol:Vi,mathsym:dA,makeSpan:ei,makeSvgSpan:z4,makeLineSpan:pA,makeAnchor:gA,makeFragment:R4,wrapFragment:xA,makeVList:vA,makeOrd:uA,makeGlue:bA,staticSvg:wA,svgData:O4,tryCombineChars:mA},Xe={number:3,unit:"mu"},fs={number:4,unit:"mu"},_r={number:5,unit:"mu"},MA={mord:{mop:Xe,mbin:fs,mrel:_r,minner:Xe},mop:{mord:Xe,mop:Xe,mrel:_r,minner:Xe},mbin:{mord:fs,mop:fs,mopen:fs,minner:fs},mrel:{mord:_r,mop:_r,mopen:_r,minner:_r},mopen:{},mclose:{mop:Xe,mbin:fs,mrel:_r,minner:Xe},mpunct:{mord:Xe,mop:Xe,mrel:_r,mopen:Xe,mclose:Xe,mpunct:Xe,minner:Xe},minner:{mord:Xe,mop:Xe,mbin:fs,mrel:_r,mopen:Xe,mpunct:Xe,minner:Xe}},TA={mord:{mop:Xe},mop:{mord:Xe,mop:Xe},mbin:{},mrel:{},mopen:{},mclose:{mop:Xe},mpunct:{},minner:{mop:Xe}},B4={},zh={},Rh={};Dh=function(e){return e.type==="ordgroup"&&e.body.length===1?e.body[0]:e},rt=function(e){return e.type==="ordgroup"?e.body:[e]},zr=z.makeSpan,NA=["leftmost","mbin","mopen","mrel","mop","mpunct"],EA=["rightmost","mrel","mclose","mpunct"],SA={display:ae.DISPLAY,text:ae.TEXT,script:ae.SCRIPT,scriptscript:ae.SCRIPTSCRIPT},AA={mord:"mord",mop:"mop",mbin:"mbin",mrel:"mrel",mopen:"mopen",mclose:"mclose",mpunct:"mpunct",minner:"minner"},mt=function(e,t,r,n){n===void 0&&(n=[null,null]);for(var s=[],a=0;a{var y=x.classes[0],b=g.classes[0];y==="mbin"&&EA.includes(b)?x.classes[0]="mord":b==="mbin"&&NA.includes(y)&&(g.classes[0]="mord")},{node:c},f,m),r4(s,(g,x)=>{var y=E1(x),b=E1(g),E=y&&b?g.hasClass("mtight")?TA[y][b]:MA[y][b]:null;if(E)return z.makeGlue(E,h)},{node:c},f,m),s},r4=function i(e,t,r,n,s){n&&e.push(n);for(var a=0;af=>{e.splice(c+1,0,f),a++})(a)}n&&e.pop()},P4=function(e){return e instanceof ms||e instanceof al||e instanceof ps&&e.hasClass("enclosing")?e:null},kA=function i(e,t){var r=P4(e);if(r){var n=r.children;if(n.length){if(t==="right")return i(n[n.length-1],"right");if(t==="left")return i(n[0],"left")}}return e},E1=function(e,t){return e?(t&&(e=kA(e,t)),AA[e.classes[0]]||null):null},ll=function(e,t){var r=["nulldelimiter"].concat(e.baseSizingClasses());return zr(t.concat(r))},Te=function(e,t,r){if(!e)return zr();if(zh[e.type]){var n=zh[e.type](e,t);if(r&&t.size!==r.size){n=zr(t.sizingClasses(r),[n],t);var s=t.sizeMultiplier/r.sizeMultiplier;n.height*=s,n.depth*=s}return n}else throw new G("Got group of unknown type: '"+e.type+"'")};Bt=class{constructor(e,t,r){this.type=void 0,this.attributes=void 0,this.children=void 0,this.classes=void 0,this.type=e,this.attributes={},this.children=t||[],this.classes=r||[]}setAttribute(e,t){this.attributes[e]=t}getAttribute(e){return this.attributes[e]}toNode(){var e=document.createElementNS("http://www.w3.org/1998/Math/MathML",this.type);for(var t in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,t)&&e.setAttribute(t,this.attributes[t]);this.classes.length>0&&(e.className=xn(this.classes));for(var r=0;r0&&(e+=' class ="'+Ee.escape(xn(this.classes))+'"'),e+=">";for(var r=0;r",e}toText(){return this.children.map(e=>e.toText()).join("")}},Ci=class{constructor(e){this.text=void 0,this.text=e}toNode(){return document.createTextNode(this.text)}toMarkup(){return Ee.escape(this.toText())}toText(){return this.text}},A1=class{constructor(e){this.width=void 0,this.character=void 0,this.width=e,e>=.05555&&e<=.05556?this.character="\u200A":e>=.1666&&e<=.1667?this.character="\u2009":e>=.2222&&e<=.2223?this.character="\u2005":e>=.2777&&e<=.2778?this.character="\u2005\u200A":e>=-.05556&&e<=-.05555?this.character="\u200A\u2063":e>=-.1667&&e<=-.1666?this.character="\u2009\u2063":e>=-.2223&&e<=-.2222?this.character="\u205F\u2063":e>=-.2778&&e<=-.2777?this.character="\u2005\u2063":this.character=null}toNode(){if(this.character)return document.createTextNode(this.character);var e=document.createElementNS("http://www.w3.org/1998/Math/MathML","mspace");return e.setAttribute("width",V(this.width)),e}toMarkup(){return this.character?""+this.character+"":''}toText(){return this.character?this.character:" "}},$={MathNode:Bt,TextNode:Ci,SpaceNode:A1,newDocumentFragment:F4},_i=function(e,t,r){return Be[t][e]&&Be[t][e].replace&&e.charCodeAt(0)!==55349&&!(I4.hasOwnProperty(e)&&r&&(r.fontFamily&&r.fontFamily.slice(4,6)==="tt"||r.font&&r.font.slice(4,6)==="tt"))&&(e=Be[t][e].replace),new $.TextNode(e)},O1=function(e){return e.length===1?e[0]:new $.MathNode("mrow",e)},B1=function(e,t){if(t.fontFamily==="texttt")return"monospace";if(t.fontFamily==="textsf")return t.fontShape==="textit"&&t.fontWeight==="textbf"?"sans-serif-bold-italic":t.fontShape==="textit"?"sans-serif-italic":t.fontWeight==="textbf"?"bold-sans-serif":"sans-serif";if(t.fontShape==="textit"&&t.fontWeight==="textbf")return"bold-italic";if(t.fontShape==="textit")return"italic";if(t.fontWeight==="textbf")return"bold";var r=t.font;if(!r||r==="mathnormal")return null;var n=e.mode;if(r==="mathit")return"italic";if(r==="boldsymbol")return e.type==="textord"?"bold":"bold-italic";if(r==="mathbf")return"bold";if(r==="mathbb")return"double-struck";if(r==="mathsfit")return"sans-serif-italic";if(r==="mathfrak")return"fraktur";if(r==="mathscr"||r==="mathcal")return"script";if(r==="mathsf")return"sans-serif";if(r==="mathtt")return"monospace";var s=e.text;if(["\\imath","\\jmath"].includes(s))return null;Be[n][s]&&Be[n][s].replace&&(s=Be[n][s].replace);var a=z.fontMap[r].fontName;return R1(s,a,n)?z.fontMap[r].variant:null};ri=function(e,t,r){if(e.length===1){var n=Re(e[0],t);return r&&n instanceof Bt&&n.type==="mo"&&(n.setAttribute("lspace","0em"),n.setAttribute("rspace","0em")),[n]}for(var s=[],a,o=0;o=1&&(a.type==="mn"||c1(a))){var h=l.children[0];h instanceof Bt&&h.type==="mn"&&(h.children=[...a.children,...h.children],s.pop())}else if(a.type==="mi"&&a.children.length===1){var d=a.children[0];if(d instanceof Ci&&d.text==="\u0338"&&(l.type==="mo"||l.type==="mi"||l.type==="mn")){var c=l.children[0];c instanceof Ci&&c.text.length>0&&(c.text=c.text.slice(0,1)+"\u0338"+c.text.slice(1),s.pop())}}}s.push(l),a=l}return s},yn=function(e,t,r){return O1(ri(e,t,r))},Re=function(e,t){if(!e)return new $.MathNode("mrow");if(Rh[e.type]){var r=Rh[e.type](e,t);return r}else throw new G("Got group of unknown type: '"+e.type+"'")};q4=function(e){return new Lh({style:e.displayMode?ae.DISPLAY:ae.TEXT,maxSize:e.maxSize,minRuleThickness:e.minRuleThickness})},H4=function(e,t){if(t.displayMode){var r=["katex-display"];t.leqno&&r.push("leqno"),t.fleqn&&r.push("fleqn"),e=z.makeSpan(r,[e])}return e},CA=function(e,t,r){var n=q4(r),s;if(r.output==="mathml")return n4(e,t,n,r.displayMode,!0);if(r.output==="html"){var a=S1(e,n);s=z.makeSpan(["katex"],[a])}else{var o=n4(e,t,n,r.displayMode,!1),l=S1(e,n);s=z.makeSpan(["katex"],[o,l])}return H4(s,r)},_A=function(e,t,r){var n=q4(r),s=S1(e,n),a=z.makeSpan(["katex"],[s]);return H4(a,r)},LA={widehat:"^",widecheck:"\u02C7",widetilde:"~",utilde:"~",overleftarrow:"\u2190",underleftarrow:"\u2190",xleftarrow:"\u2190",overrightarrow:"\u2192",underrightarrow:"\u2192",xrightarrow:"\u2192",underbrace:"\u23DF",overbrace:"\u23DE",overgroup:"\u23E0",undergroup:"\u23E1",overleftrightarrow:"\u2194",underleftrightarrow:"\u2194",xleftrightarrow:"\u2194",Overrightarrow:"\u21D2",xRightarrow:"\u21D2",overleftharpoon:"\u21BC",xleftharpoonup:"\u21BC",overrightharpoon:"\u21C0",xrightharpoonup:"\u21C0",xLeftarrow:"\u21D0",xLeftrightarrow:"\u21D4",xhookleftarrow:"\u21A9",xhookrightarrow:"\u21AA",xmapsto:"\u21A6",xrightharpoondown:"\u21C1",xleftharpoondown:"\u21BD",xrightleftharpoons:"\u21CC",xleftrightharpoons:"\u21CB",xtwoheadleftarrow:"\u219E",xtwoheadrightarrow:"\u21A0",xlongequal:"=",xtofrom:"\u21C4",xrightleftarrows:"\u21C4",xrightequilibrium:"\u21CC",xleftequilibrium:"\u21CB","\\cdrightarrow":"\u2192","\\cdleftarrow":"\u2190","\\cdlongequal":"="},IA=function(e){var t=new $.MathNode("mo",[new $.TextNode(LA[e.replace(/^\\/,"")])]);return t.setAttribute("stretchy","true"),t},zA={overrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],overleftarrow:[["leftarrow"],.888,522,"xMinYMin"],underrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],underleftarrow:[["leftarrow"],.888,522,"xMinYMin"],xrightarrow:[["rightarrow"],1.469,522,"xMaxYMin"],"\\cdrightarrow":[["rightarrow"],3,522,"xMaxYMin"],xleftarrow:[["leftarrow"],1.469,522,"xMinYMin"],"\\cdleftarrow":[["leftarrow"],3,522,"xMinYMin"],Overrightarrow:[["doublerightarrow"],.888,560,"xMaxYMin"],xRightarrow:[["doublerightarrow"],1.526,560,"xMaxYMin"],xLeftarrow:[["doubleleftarrow"],1.526,560,"xMinYMin"],overleftharpoon:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoonup:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoondown:[["leftharpoondown"],.888,522,"xMinYMin"],overrightharpoon:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoonup:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoondown:[["rightharpoondown"],.888,522,"xMaxYMin"],xlongequal:[["longequal"],.888,334,"xMinYMin"],"\\cdlongequal":[["longequal"],3,334,"xMinYMin"],xtwoheadleftarrow:[["twoheadleftarrow"],.888,334,"xMinYMin"],xtwoheadrightarrow:[["twoheadrightarrow"],.888,334,"xMaxYMin"],overleftrightarrow:[["leftarrow","rightarrow"],.888,522],overbrace:[["leftbrace","midbrace","rightbrace"],1.6,548],underbrace:[["leftbraceunder","midbraceunder","rightbraceunder"],1.6,548],underleftrightarrow:[["leftarrow","rightarrow"],.888,522],xleftrightarrow:[["leftarrow","rightarrow"],1.75,522],xLeftrightarrow:[["doubleleftarrow","doublerightarrow"],1.75,560],xrightleftharpoons:[["leftharpoondownplus","rightharpoonplus"],1.75,716],xleftrightharpoons:[["leftharpoonplus","rightharpoondownplus"],1.75,716],xhookleftarrow:[["leftarrow","righthook"],1.08,522],xhookrightarrow:[["lefthook","rightarrow"],1.08,522],overlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],underlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],overgroup:[["leftgroup","rightgroup"],.888,342],undergroup:[["leftgroupunder","rightgroupunder"],.888,342],xmapsto:[["leftmapsto","rightarrow"],1.5,522],xtofrom:[["leftToFrom","rightToFrom"],1.75,528],xrightleftarrows:[["baraboveleftarrow","rightarrowabovebar"],1.75,901],xrightequilibrium:[["baraboveshortleftharpoon","rightharpoonaboveshortbar"],1.75,716],xleftequilibrium:[["shortbaraboveleftharpoon","shortrightharpoonabovebar"],1.75,716]},RA=function(e){return e.type==="ordgroup"?e.body.length:1},DA=function(e,t){function r(){var o=4e5,l=e.label.slice(1);if(["widehat","widecheck","widetilde","utilde"].includes(l)){var h=e,d=RA(h.base),c,f,m;if(d>5)l==="widehat"||l==="widecheck"?(c=420,o=2364,m=.42,f=l+"4"):(c=312,o=2340,m=.34,f="tilde4");else{var g=[1,1,2,2,3,3][d];l==="widehat"||l==="widecheck"?(o=[0,1062,2364,2364,2364][g],c=[0,239,300,360,420][g],m=[0,.24,.3,.3,.36,.42][g],f=l+g):(o=[0,600,1033,2339,2340][g],c=[0,260,286,306,312][g],m=[0,.26,.286,.3,.306,.34][g],f="tilde"+g)}var x=new hr(f),y=new Yi([x],{width:"100%",height:V(m),viewBox:"0 0 "+o+" "+c,preserveAspectRatio:"none"});return{span:z.makeSvgSpan([],[y],t),minWidth:0,height:m}}else{var b=[],E=zA[l],[k,L,I]=E,O=I/1e3,q=k.length,F,W;if(q===1){var ne=E[3];F=["hide-tail"],W=[ne]}else if(q===2)F=["halfarrow-left","halfarrow-right"],W=["xMinYMin","xMaxYMin"];else if(q===3)F=["brace-left","brace-center","brace-right"],W=["xMinYMin","xMidYMin","xMaxYMin"];else throw new Error(`Correct katexImagesData or update code here to support + `+q+" children.");for(var re=0;re0&&(n.style.minWidth=V(s)),n},OA=function(e,t,r,n,s){var a,o=e.height+e.depth+r+n;if(/fbox|color|angl/.test(t)){if(a=z.makeSpan(["stretchy",t],[],s),t==="fbox"){var l=s.color&&s.getColor();l&&(a.style.borderColor=l)}}else{var h=[];/^[bx]cancel$/.test(t)&&h.push(new ol({x1:"0",y1:"0",x2:"100%",y2:"100%","stroke-width":"0.046em"})),/^x?cancel$/.test(t)&&h.push(new ol({x1:"0",y1:"100%",x2:"100%",y2:"0","stroke-width":"0.046em"}));var d=new Yi(h,{width:"100%",height:V(o)});a=z.makeSvgSpan([],[d],s)}return a.height=o,a.style.height=V(o),a},Rr={encloseSpan:OA,mathMLnode:IA,svgSpan:DA};F1=(i,e)=>{var t,r,n;i&&i.type==="supsub"?(r=me(i.base,"accent"),t=r.base,i.base=t,n=aA(Te(i,e)),i.base=r):(r=me(i,"accent"),t=r.base);var s=Te(t,e.havingCrampedStyle()),a=r.isShifty&&Ee.isCharacterBox(t),o=0;if(a){var l=Ee.getBaseElem(t),h=Te(l,e.havingCrampedStyle());o=J6(h).skew}var d=r.label==="\\c",c=d?s.height+s.depth:Math.min(s.height,e.fontMetrics().xHeight),f;if(r.isStretchy)f=Rr.svgSpan(r,e),f=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"elem",elem:f,wrapperClasses:["svg-align"],wrapperStyle:o>0?{width:"calc(100% - "+V(2*o)+")",marginLeft:V(2*o)}:void 0}]},e);else{var m,g;r.label==="\\vec"?(m=z.staticSvg("vec",e),g=z.svgData.vec[1]):(m=z.makeOrd({mode:r.mode,text:r.label},e,"textord"),m=J6(m),m.italic=0,g=m.width,d&&(c+=m.depth)),f=z.makeSpan(["accent-body"],[m]);var x=r.label==="\\textcircled";x&&(f.classes.push("accent-full"),c=s.height);var y=o;x||(y-=g/2),f.style.left=V(y),r.label==="\\textcircled"&&(f.style.top=".2em"),f=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"kern",size:-c},{type:"elem",elem:f}]},e)}var b=z.makeSpan(["mord","accent"],[f],e);return n?(n.children[0]=b,n.height=Math.max(b.height,n.height),n.classes[0]="mord",n):b},U4=(i,e)=>{var t=i.isStretchy?Rr.mathMLnode(i.label):new $.MathNode("mo",[_i(i.label,i.mode)]),r=new $.MathNode("mover",[Re(i.base,e),t]);return r.setAttribute("accent","true"),r},BA=new RegExp(["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring"].map(i=>"\\"+i).join("|"));Z({type:"accent",names:["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring","\\widecheck","\\widehat","\\widetilde","\\overrightarrow","\\overleftarrow","\\Overrightarrow","\\overleftrightarrow","\\overgroup","\\overlinesegment","\\overleftharpoon","\\overrightharpoon"],props:{numArgs:1},handler:(i,e)=>{var t=Dh(e[0]),r=!BA.test(i.funcName),n=!r||i.funcName==="\\widehat"||i.funcName==="\\widetilde"||i.funcName==="\\widecheck";return{type:"accent",mode:i.parser.mode,label:i.funcName,isStretchy:r,isShifty:n,base:t}},htmlBuilder:F1,mathmlBuilder:U4});Z({type:"accent",names:["\\'","\\`","\\^","\\~","\\=","\\u","\\.",'\\"',"\\c","\\r","\\H","\\v","\\textcircled"],props:{numArgs:1,allowedInText:!0,allowedInMath:!0,argTypes:["primitive"]},handler:(i,e)=>{var t=e[0],r=i.parser.mode;return r==="math"&&(i.parser.settings.reportNonstrict("mathVsTextAccents","LaTeX's accent "+i.funcName+" works only in text mode"),r="text"),{type:"accent",mode:r,label:i.funcName,isStretchy:!1,isShifty:!0,base:t}},htmlBuilder:F1,mathmlBuilder:U4});Z({type:"accentUnder",names:["\\underleftarrow","\\underrightarrow","\\underleftrightarrow","\\undergroup","\\underlinesegment","\\utilde"],props:{numArgs:1},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0];return{type:"accentUnder",mode:t.mode,label:r,base:n}},htmlBuilder:(i,e)=>{var t=Te(i.base,e),r=Rr.svgSpan(i,e),n=i.label==="\\utilde"?.12:0,s=z.makeVList({positionType:"top",positionData:t.height,children:[{type:"elem",elem:r,wrapperClasses:["svg-align"]},{type:"kern",size:n},{type:"elem",elem:t}]},e);return z.makeSpan(["mord","accentunder"],[s],e)},mathmlBuilder:(i,e)=>{var t=Rr.mathMLnode(i.label),r=new $.MathNode("munder",[Re(i.base,e),t]);return r.setAttribute("accentunder","true"),r}});Eh=i=>{var e=new $.MathNode("mpadded",i?[i]:[]);return e.setAttribute("width","+0.6em"),e.setAttribute("lspace","0.3em"),e};Z({type:"xArrow",names:["\\xleftarrow","\\xrightarrow","\\xLeftarrow","\\xRightarrow","\\xleftrightarrow","\\xLeftrightarrow","\\xhookleftarrow","\\xhookrightarrow","\\xmapsto","\\xrightharpoondown","\\xrightharpoonup","\\xleftharpoondown","\\xleftharpoonup","\\xrightleftharpoons","\\xleftrightharpoons","\\xlongequal","\\xtwoheadrightarrow","\\xtwoheadleftarrow","\\xtofrom","\\xrightleftarrows","\\xrightequilibrium","\\xleftequilibrium","\\\\cdrightarrow","\\\\cdleftarrow","\\\\cdlongequal"],props:{numArgs:1,numOptionalArgs:1},handler(i,e,t){var{parser:r,funcName:n}=i;return{type:"xArrow",mode:r.mode,label:n,body:e[0],below:t[0]}},htmlBuilder(i,e){var t=e.style,r=e.havingStyle(t.sup()),n=z.wrapFragment(Te(i.body,r,e),e),s=i.label.slice(0,2)==="\\x"?"x":"cd";n.classes.push(s+"-arrow-pad");var a;i.below&&(r=e.havingStyle(t.sub()),a=z.wrapFragment(Te(i.below,r,e),e),a.classes.push(s+"-arrow-pad"));var o=Rr.svgSpan(i,e),l=-e.fontMetrics().axisHeight+.5*o.height,h=-e.fontMetrics().axisHeight-.5*o.height-.111;(n.depth>.25||i.label==="\\xleftequilibrium")&&(h-=n.depth);var d;if(a){var c=-e.fontMetrics().axisHeight+a.height+.5*o.height+.111;d=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:n,shift:h},{type:"elem",elem:o,shift:l},{type:"elem",elem:a,shift:c}]},e)}else d=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:n,shift:h},{type:"elem",elem:o,shift:l}]},e);return d.children[0].children[0].children[1].classes.push("svg-align"),z.makeSpan(["mrel","x-arrow"],[d],e)},mathmlBuilder(i,e){var t=Rr.mathMLnode(i.label);t.setAttribute("minsize",i.label.charAt(0)==="x"?"1.75em":"3.0em");var r;if(i.body){var n=Eh(Re(i.body,e));if(i.below){var s=Eh(Re(i.below,e));r=new $.MathNode("munderover",[t,s,n])}else r=new $.MathNode("mover",[t,n])}else if(i.below){var a=Eh(Re(i.below,e));r=new $.MathNode("munder",[t,a])}else r=Eh(),r=new $.MathNode("mover",[t,r]);return r}});PA=z.makeSpan;Z({type:"mclass",names:["\\mathord","\\mathbin","\\mathrel","\\mathopen","\\mathclose","\\mathpunct","\\mathinner"],props:{numArgs:1,primitive:!0},handler(i,e){var{parser:t,funcName:r}=i,n=e[0];return{type:"mclass",mode:t.mode,mclass:"m"+r.slice(5),body:rt(n),isCharacterBox:Ee.isCharacterBox(n)}},htmlBuilder:$4,mathmlBuilder:j4});Hh=i=>{var e=i.type==="ordgroup"&&i.body.length?i.body[0]:i;return e.type==="atom"&&(e.family==="bin"||e.family==="rel")?"m"+e.family:"mord"};Z({type:"mclass",names:["\\@binrel"],props:{numArgs:2},handler(i,e){var{parser:t}=i;return{type:"mclass",mode:t.mode,mclass:Hh(e[0]),body:rt(e[1]),isCharacterBox:Ee.isCharacterBox(e[1])}}});Z({type:"mclass",names:["\\stackrel","\\overset","\\underset"],props:{numArgs:2},handler(i,e){var{parser:t,funcName:r}=i,n=e[1],s=e[0],a;r!=="\\stackrel"?a=Hh(n):a="mrel";var o={type:"op",mode:n.mode,limits:!0,alwaysHandleSupSub:!0,parentIsSupSub:!1,symbol:!1,suppressBaseShift:r!=="\\stackrel",body:rt(n)},l={type:"supsub",mode:s.mode,base:o,sup:r==="\\underset"?null:s,sub:r==="\\underset"?s:null};return{type:"mclass",mode:t.mode,mclass:a,body:[l],isCharacterBox:Ee.isCharacterBox(l)}},htmlBuilder:$4,mathmlBuilder:j4});Z({type:"pmb",names:["\\pmb"],props:{numArgs:1,allowedInText:!0},handler(i,e){var{parser:t}=i;return{type:"pmb",mode:t.mode,mclass:Hh(e[0]),body:rt(e[0])}},htmlBuilder(i,e){var t=mt(i.body,e,!0),r=z.makeSpan([i.mclass],t,e);return r.style.textShadow="0.02em 0.01em 0.04px",r},mathmlBuilder(i,e){var t=ri(i.body,e),r=new $.MathNode("mstyle",t);return r.setAttribute("style","text-shadow: 0.02em 0.01em 0.04px"),r}});FA={">":"\\\\cdrightarrow","<":"\\\\cdleftarrow","=":"\\\\cdlongequal",A:"\\uparrow",V:"\\downarrow","|":"\\Vert",".":"no arrow"},s4=()=>({type:"styling",body:[],mode:"math",style:"display"}),a4=i=>i.type==="textord"&&i.text==="@",qA=(i,e)=>(i.type==="mathord"||i.type==="atom")&&i.text===e;Z({type:"cdlabel",names:["\\\\cdleft","\\\\cdright"],props:{numArgs:1},handler(i,e){var{parser:t,funcName:r}=i;return{type:"cdlabel",mode:t.mode,side:r.slice(4),label:e[0]}},htmlBuilder(i,e){var t=e.havingStyle(e.style.sup()),r=z.wrapFragment(Te(i.label,t,e),e);return r.classes.push("cd-label-"+i.side),r.style.bottom=V(.8-r.depth),r.height=0,r.depth=0,r},mathmlBuilder(i,e){var t=new $.MathNode("mrow",[Re(i.label,e)]);return t=new $.MathNode("mpadded",[t]),t.setAttribute("width","0"),i.side==="left"&&t.setAttribute("lspace","-1width"),t.setAttribute("voffset","0.7em"),t=new $.MathNode("mstyle",[t]),t.setAttribute("displaystyle","false"),t.setAttribute("scriptlevel","1"),t}});Z({type:"cdlabelparent",names:["\\\\cdparent"],props:{numArgs:1},handler(i,e){var{parser:t}=i;return{type:"cdlabelparent",mode:t.mode,fragment:e[0]}},htmlBuilder(i,e){var t=z.wrapFragment(Te(i.fragment,e),e);return t.classes.push("cd-vert-arrow"),t},mathmlBuilder(i,e){return new $.MathNode("mrow",[Re(i.fragment,e)])}});Z({type:"textord",names:["\\@char"],props:{numArgs:1,allowedInText:!0},handler(i,e){for(var{parser:t}=i,r=me(e[0],"ordgroup"),n=r.body,s="",a=0;a=1114111)throw new G("\\@char with invalid code point "+s);return l<=65535?h=String.fromCharCode(l):(l-=65536,h=String.fromCharCode((l>>10)+55296,(l&1023)+56320)),{type:"textord",mode:t.mode,text:h}}});G4=(i,e)=>{var t=mt(i.body,e.withColor(i.color),!1);return z.makeFragment(t)},W4=(i,e)=>{var t=ri(i.body,e.withColor(i.color)),r=new $.MathNode("mstyle",t);return r.setAttribute("mathcolor",i.color),r};Z({type:"color",names:["\\textcolor"],props:{numArgs:2,allowedInText:!0,argTypes:["color","original"]},handler(i,e){var{parser:t}=i,r=me(e[0],"color-token").color,n=e[1];return{type:"color",mode:t.mode,color:r,body:rt(n)}},htmlBuilder:G4,mathmlBuilder:W4});Z({type:"color",names:["\\color"],props:{numArgs:1,allowedInText:!0,argTypes:["color"]},handler(i,e){var{parser:t,breakOnTokenText:r}=i,n=me(e[0],"color-token").color;t.gullet.macros.set("\\current@color",n);var s=t.parseExpression(!0,r);return{type:"color",mode:t.mode,color:n,body:s}},htmlBuilder:G4,mathmlBuilder:W4});Z({type:"cr",names:["\\\\"],props:{numArgs:0,numOptionalArgs:0,allowedInText:!0},handler(i,e,t){var{parser:r}=i,n=r.gullet.future().text==="["?r.parseSizeGroup(!0):null,s=!r.settings.displayMode||!r.settings.useStrictBehavior("newLineInDisplayMode","In LaTeX, \\\\ or \\newline does nothing in display mode");return{type:"cr",mode:r.mode,newLine:s,size:n&&me(n,"size").value}},htmlBuilder(i,e){var t=z.makeSpan(["mspace"],[],e);return i.newLine&&(t.classes.push("newline"),i.size&&(t.style.marginTop=V(Ke(i.size,e)))),t},mathmlBuilder(i,e){var t=new $.MathNode("mspace");return i.newLine&&(t.setAttribute("linebreak","newline"),i.size&&t.setAttribute("height",V(Ke(i.size,e)))),t}});k1={"\\global":"\\global","\\long":"\\\\globallong","\\\\globallong":"\\\\globallong","\\def":"\\gdef","\\gdef":"\\gdef","\\edef":"\\xdef","\\xdef":"\\xdef","\\let":"\\\\globallet","\\futurelet":"\\\\globalfuture"},V4=i=>{var e=i.text;if(/^(?:[\\{}$&#^_]|EOF)$/.test(e))throw new G("Expected a control sequence",i);return e},$A=i=>{var e=i.gullet.popToken();return e.text==="="&&(e=i.gullet.popToken(),e.text===" "&&(e=i.gullet.popToken())),e},Y4=(i,e,t,r)=>{var n=i.gullet.macros.get(t.text);n==null&&(t.noexpand=!0,n={tokens:[t],numArgs:0,unexpandable:!i.gullet.isExpandable(t.text)}),i.gullet.macros.set(e,n,r)};Z({type:"internal",names:["\\global","\\long","\\\\globallong"],props:{numArgs:0,allowedInText:!0},handler(i){var{parser:e,funcName:t}=i;e.consumeSpaces();var r=e.fetch();if(k1[r.text])return(t==="\\global"||t==="\\\\globallong")&&(r.text=k1[r.text]),me(e.parseFunction(),"internal");throw new G("Invalid token after macro prefix",r)}});Z({type:"internal",names:["\\def","\\gdef","\\edef","\\xdef"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(i){var{parser:e,funcName:t}=i,r=e.gullet.popToken(),n=r.text;if(/^(?:[\\{}$&#^_]|EOF)$/.test(n))throw new G("Expected a control sequence",r);for(var s=0,a,o=[[]];e.gullet.future().text!=="{";)if(r=e.gullet.popToken(),r.text==="#"){if(e.gullet.future().text==="{"){a=e.gullet.future(),o[s].push("{");break}if(r=e.gullet.popToken(),!/^[1-9]$/.test(r.text))throw new G('Invalid argument number "'+r.text+'"');if(parseInt(r.text)!==s+1)throw new G('Argument number "'+r.text+'" out of order');s++,o.push([])}else{if(r.text==="EOF")throw new G("Expected a macro definition");o[s].push(r.text)}var{tokens:l}=e.gullet.consumeArg();return a&&l.unshift(a),(t==="\\edef"||t==="\\xdef")&&(l=e.gullet.expandTokens(l),l.reverse()),e.gullet.macros.set(n,{tokens:l,numArgs:s,delimiters:o},t===k1[t]),{type:"internal",mode:e.mode}}});Z({type:"internal",names:["\\let","\\\\globallet"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(i){var{parser:e,funcName:t}=i,r=V4(e.gullet.popToken());e.gullet.consumeSpaces();var n=$A(e);return Y4(e,r,n,t==="\\\\globallet"),{type:"internal",mode:e.mode}}});Z({type:"internal",names:["\\futurelet","\\\\globalfuture"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(i){var{parser:e,funcName:t}=i,r=V4(e.gullet.popToken()),n=e.gullet.popToken(),s=e.gullet.popToken();return Y4(e,r,s,t==="\\\\globalfuture"),e.gullet.pushToken(s),e.gullet.pushToken(n),{type:"internal",mode:e.mode}}});il=function(e,t,r){var n=Be.math[e]&&Be.math[e].replace,s=R1(n||e,t,r);if(!s)throw new Error("Unsupported symbol "+e+" and font size "+t+".");return s},q1=function(e,t,r,n){var s=r.havingBaseStyle(t),a=z.makeSpan(n.concat(s.sizingClasses(r)),[e],r),o=s.sizeMultiplier/r.sizeMultiplier;return a.height*=o,a.depth*=o,a.maxFontSize=s.sizeMultiplier,a},X4=function(e,t,r){var n=t.havingBaseStyle(r),s=(1-t.sizeMultiplier/n.sizeMultiplier)*t.fontMetrics().axisHeight;e.classes.push("delimcenter"),e.style.top=V(s),e.height-=s,e.depth+=s},jA=function(e,t,r,n,s,a){var o=z.makeSymbol(e,"Main-Regular",s,n),l=q1(o,t,n,a);return r&&X4(l,n,t),l},GA=function(e,t,r,n){return z.makeSymbol(e,"Size"+t+"-Regular",r,n)},K4=function(e,t,r,n,s,a){var o=GA(e,t,s,n),l=q1(z.makeSpan(["delimsizing","size"+t],[o],n),ae.TEXT,n,a);return r&&X4(l,n,ae.TEXT),l},u1=function(e,t,r){var n;t==="Size1-Regular"?n="delim-size1":n="delim-size4";var s=z.makeSpan(["delimsizinginner",n],[z.makeSpan([],[z.makeSymbol(e,t,r)])]);return{type:"elem",elem:s}},f1=function(e,t,r){var n=lr["Size4-Regular"][e.charCodeAt(0)]?lr["Size4-Regular"][e.charCodeAt(0)][4]:lr["Size1-Regular"][e.charCodeAt(0)][4],s=new hr("inner",QS(e,Math.round(1e3*t))),a=new Yi([s],{width:V(n),height:V(t),style:"width:"+V(n),viewBox:"0 0 "+1e3*n+" "+Math.round(1e3*t),preserveAspectRatio:"xMinYMin"}),o=z.makeSvgSpan([],[a],r);return o.height=t,o.style.height=V(t),o.style.width=V(n),{type:"elem",elem:o}},C1=.008,Sh={type:"kern",size:-1*C1},WA=["|","\\lvert","\\rvert","\\vert"],VA=["\\|","\\lVert","\\rVert","\\Vert"],Z4=function(e,t,r,n,s,a){var o,l,h,d,c="",f=0;o=h=d=e,l=null;var m="Size1-Regular";e==="\\uparrow"?h=d="\u23D0":e==="\\Uparrow"?h=d="\u2016":e==="\\downarrow"?o=h="\u23D0":e==="\\Downarrow"?o=h="\u2016":e==="\\updownarrow"?(o="\\uparrow",h="\u23D0",d="\\downarrow"):e==="\\Updownarrow"?(o="\\Uparrow",h="\u2016",d="\\Downarrow"):WA.includes(e)?(h="\u2223",c="vert",f=333):VA.includes(e)?(h="\u2225",c="doublevert",f=556):e==="["||e==="\\lbrack"?(o="\u23A1",h="\u23A2",d="\u23A3",m="Size4-Regular",c="lbrack",f=667):e==="]"||e==="\\rbrack"?(o="\u23A4",h="\u23A5",d="\u23A6",m="Size4-Regular",c="rbrack",f=667):e==="\\lfloor"||e==="\u230A"?(h=o="\u23A2",d="\u23A3",m="Size4-Regular",c="lfloor",f=667):e==="\\lceil"||e==="\u2308"?(o="\u23A1",h=d="\u23A2",m="Size4-Regular",c="lceil",f=667):e==="\\rfloor"||e==="\u230B"?(h=o="\u23A5",d="\u23A6",m="Size4-Regular",c="rfloor",f=667):e==="\\rceil"||e==="\u2309"?(o="\u23A4",h=d="\u23A5",m="Size4-Regular",c="rceil",f=667):e==="("||e==="\\lparen"?(o="\u239B",h="\u239C",d="\u239D",m="Size4-Regular",c="lparen",f=875):e===")"||e==="\\rparen"?(o="\u239E",h="\u239F",d="\u23A0",m="Size4-Regular",c="rparen",f=875):e==="\\{"||e==="\\lbrace"?(o="\u23A7",l="\u23A8",d="\u23A9",h="\u23AA",m="Size4-Regular"):e==="\\}"||e==="\\rbrace"?(o="\u23AB",l="\u23AC",d="\u23AD",h="\u23AA",m="Size4-Regular"):e==="\\lgroup"||e==="\u27EE"?(o="\u23A7",d="\u23A9",h="\u23AA",m="Size4-Regular"):e==="\\rgroup"||e==="\u27EF"?(o="\u23AB",d="\u23AD",h="\u23AA",m="Size4-Regular"):e==="\\lmoustache"||e==="\u23B0"?(o="\u23A7",d="\u23AD",h="\u23AA",m="Size4-Regular"):(e==="\\rmoustache"||e==="\u23B1")&&(o="\u23AB",d="\u23A9",h="\u23AA",m="Size4-Regular");var g=il(o,m,s),x=g.height+g.depth,y=il(h,m,s),b=y.height+y.depth,E=il(d,m,s),k=E.height+E.depth,L=0,I=1;if(l!==null){var O=il(l,m,s);L=O.height+O.depth,I=2}var q=x+k+L,F=Math.max(0,Math.ceil((t-q)/(I*b))),W=q+F*I*b,ne=n.fontMetrics().axisHeight;r&&(ne*=n.sizeMultiplier);var re=W/2-ne,le=[];if(c.length>0){var Ce=W-x-k,te=Math.round(W*1e3),Q=JS(c,Math.round(Ce*1e3)),oe=new hr(c,Q),he=(f/1e3).toFixed(3)+"em",ui=(te/1e3).toFixed(3)+"em",Mi=new Yi([oe],{width:he,height:ui,viewBox:"0 0 "+f+" "+te}),ut=z.makeSvgSpan([],[Mi],n);ut.height=te/1e3,ut.style.width=he,ut.style.height=ui,le.push({type:"elem",elem:ut})}else{if(le.push(u1(d,m,s)),le.push(Sh),l===null){var at=W-x-k+2*C1;le.push(f1(h,at,n))}else{var ot=(W-x-k-L)/2+2*C1;le.push(f1(h,ot,n)),le.push(Sh),le.push(u1(l,m,s)),le.push(Sh),le.push(f1(h,ot,n))}le.push(Sh),le.push(u1(o,m,s))}var nn=n.havingBaseStyle(ae.TEXT),Bs=z.makeVList({positionType:"bottom",positionData:re,children:le},nn);return q1(z.makeSpan(["delimsizing","mult"],[Bs],nn),ae.TEXT,n,a)},m1=80,p1=.08,g1=function(e,t,r,n,s){var a=ZS(e,n,r),o=new hr(e,a),l=new Yi([o],{width:"400em",height:V(t),viewBox:"0 0 400000 "+r,preserveAspectRatio:"xMinYMin slice"});return z.makeSvgSpan(["hide-tail"],[l],s)},YA=function(e,t){var r=t.havingBaseSizing(),n=tg("\\surd",e*r.sizeMultiplier,eg,r),s=r.sizeMultiplier,a=Math.max(0,t.minRuleThickness-t.fontMetrics().sqrtRuleThickness),o,l=0,h=0,d=0,c;return n.type==="small"?(d=1e3+1e3*a+m1,e<1?s=1:e<1.4&&(s=.7),l=(1+a+p1)/s,h=(1+a)/s,o=g1("sqrtMain",l,d,a,t),o.style.minWidth="0.853em",c=.833/s):n.type==="large"?(d=(1e3+m1)*rl[n.size],h=(rl[n.size]+a)/s,l=(rl[n.size]+a+p1)/s,o=g1("sqrtSize"+n.size,l,d,a,t),o.style.minWidth="1.02em",c=1/s):(l=e+a+p1,h=e+a,d=Math.floor(1e3*e+a)+m1,o=g1("sqrtTall",l,d,a,t),o.style.minWidth="0.742em",c=1.056),o.height=h,o.style.height=V(l),{span:o,advanceWidth:c,ruleWidth:(t.fontMetrics().sqrtRuleThickness+a)*s}},Q4=["(","\\lparen",")","\\rparen","[","\\lbrack","]","\\rbrack","\\{","\\lbrace","\\}","\\rbrace","\\lfloor","\\rfloor","\u230A","\u230B","\\lceil","\\rceil","\u2308","\u2309","\\surd"],XA=["\\uparrow","\\downarrow","\\updownarrow","\\Uparrow","\\Downarrow","\\Updownarrow","|","\\|","\\vert","\\Vert","\\lvert","\\rvert","\\lVert","\\rVert","\\lgroup","\\rgroup","\u27EE","\u27EF","\\lmoustache","\\rmoustache","\u23B0","\u23B1"],J4=["<",">","\\langle","\\rangle","/","\\backslash","\\lt","\\gt"],rl=[0,1.2,1.8,2.4,3],KA=function(e,t,r,n,s){if(e==="<"||e==="\\lt"||e==="\u27E8"?e="\\langle":(e===">"||e==="\\gt"||e==="\u27E9")&&(e="\\rangle"),Q4.includes(e)||J4.includes(e))return K4(e,t,!1,r,n,s);if(XA.includes(e))return Z4(e,rl[t],!1,r,n,s);throw new G("Illegal delimiter: '"+e+"'")},ZA=[{type:"small",style:ae.SCRIPTSCRIPT},{type:"small",style:ae.SCRIPT},{type:"small",style:ae.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4}],QA=[{type:"small",style:ae.SCRIPTSCRIPT},{type:"small",style:ae.SCRIPT},{type:"small",style:ae.TEXT},{type:"stack"}],eg=[{type:"small",style:ae.SCRIPTSCRIPT},{type:"small",style:ae.SCRIPT},{type:"small",style:ae.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4},{type:"stack"}],JA=function(e){if(e.type==="small")return"Main-Regular";if(e.type==="large")return"Size"+e.size+"-Regular";if(e.type==="stack")return"Size4-Regular";throw new Error("Add support for delim type '"+e.type+"' here.")},tg=function(e,t,r,n){for(var s=Math.min(2,3-n.style.size),a=s;at)return r[a]}return r[r.length-1]},ig=function(e,t,r,n,s,a){e==="<"||e==="\\lt"||e==="\u27E8"?e="\\langle":(e===">"||e==="\\gt"||e==="\u27E9")&&(e="\\rangle");var o;J4.includes(e)?o=ZA:Q4.includes(e)?o=eg:o=QA;var l=tg(e,t,o,n);return l.type==="small"?jA(e,l.style,r,n,s,a):l.type==="large"?K4(e,l.size,r,n,s,a):Z4(e,t,r,n,s,a)},ek=function(e,t,r,n,s,a){var o=n.fontMetrics().axisHeight*n.sizeMultiplier,l=901,h=5/n.fontMetrics().ptPerEm,d=Math.max(t-o,r+o),c=Math.max(d/500*l,2*d-h);return ig(e,c,!0,n,s,a)},Ir={sqrtImage:YA,sizedDelim:KA,sizeToMaxHeight:rl,customSizedDelim:ig,leftRightDelim:ek},o4={"\\bigl":{mclass:"mopen",size:1},"\\Bigl":{mclass:"mopen",size:2},"\\biggl":{mclass:"mopen",size:3},"\\Biggl":{mclass:"mopen",size:4},"\\bigr":{mclass:"mclose",size:1},"\\Bigr":{mclass:"mclose",size:2},"\\biggr":{mclass:"mclose",size:3},"\\Biggr":{mclass:"mclose",size:4},"\\bigm":{mclass:"mrel",size:1},"\\Bigm":{mclass:"mrel",size:2},"\\biggm":{mclass:"mrel",size:3},"\\Biggm":{mclass:"mrel",size:4},"\\big":{mclass:"mord",size:1},"\\Big":{mclass:"mord",size:2},"\\bigg":{mclass:"mord",size:3},"\\Bigg":{mclass:"mord",size:4}},tk=["(","\\lparen",")","\\rparen","[","\\lbrack","]","\\rbrack","\\{","\\lbrace","\\}","\\rbrace","\\lfloor","\\rfloor","\u230A","\u230B","\\lceil","\\rceil","\u2308","\u2309","<",">","\\langle","\u27E8","\\rangle","\u27E9","\\lt","\\gt","\\lvert","\\rvert","\\lVert","\\rVert","\\lgroup","\\rgroup","\u27EE","\u27EF","\\lmoustache","\\rmoustache","\u23B0","\u23B1","/","\\backslash","|","\\vert","\\|","\\Vert","\\uparrow","\\Uparrow","\\downarrow","\\Downarrow","\\updownarrow","\\Updownarrow","."];Z({type:"delimsizing",names:["\\bigl","\\Bigl","\\biggl","\\Biggl","\\bigr","\\Bigr","\\biggr","\\Biggr","\\bigm","\\Bigm","\\biggm","\\Biggm","\\big","\\Big","\\bigg","\\Bigg"],props:{numArgs:1,argTypes:["primitive"]},handler:(i,e)=>{var t=Uh(e[0],i);return{type:"delimsizing",mode:i.parser.mode,size:o4[i.funcName].size,mclass:o4[i.funcName].mclass,delim:t.text}},htmlBuilder:(i,e)=>i.delim==="."?z.makeSpan([i.mclass]):Ir.sizedDelim(i.delim,i.size,e,i.mode,[i.mclass]),mathmlBuilder:i=>{var e=[];i.delim!=="."&&e.push(_i(i.delim,i.mode));var t=new $.MathNode("mo",e);i.mclass==="mopen"||i.mclass==="mclose"?t.setAttribute("fence","true"):t.setAttribute("fence","false"),t.setAttribute("stretchy","true");var r=V(Ir.sizeToMaxHeight[i.size]);return t.setAttribute("minsize",r),t.setAttribute("maxsize",r),t}});Z({type:"leftright-right",names:["\\right"],props:{numArgs:1,primitive:!0},handler:(i,e)=>{var t=i.parser.gullet.macros.get("\\current@color");if(t&&typeof t!="string")throw new G("\\current@color set to non-string in \\right");return{type:"leftright-right",mode:i.parser.mode,delim:Uh(e[0],i).text,color:t}}});Z({type:"leftright",names:["\\left"],props:{numArgs:1,primitive:!0},handler:(i,e)=>{var t=Uh(e[0],i),r=i.parser;++r.leftrightDepth;var n=r.parseExpression(!1);--r.leftrightDepth,r.expect("\\right",!1);var s=me(r.parseFunction(),"leftright-right");return{type:"leftright",mode:r.mode,body:n,left:t.text,right:s.delim,rightColor:s.color}},htmlBuilder:(i,e)=>{l4(i);for(var t=mt(i.body,e,!0,["mopen","mclose"]),r=0,n=0,s=!1,a=0;a{l4(i);var t=ri(i.body,e);if(i.left!=="."){var r=new $.MathNode("mo",[_i(i.left,i.mode)]);r.setAttribute("fence","true"),t.unshift(r)}if(i.right!=="."){var n=new $.MathNode("mo",[_i(i.right,i.mode)]);n.setAttribute("fence","true"),i.rightColor&&n.setAttribute("mathcolor",i.rightColor),t.push(n)}return O1(t)}});Z({type:"middle",names:["\\middle"],props:{numArgs:1,primitive:!0},handler:(i,e)=>{var t=Uh(e[0],i);if(!i.parser.leftrightDepth)throw new G("\\middle without preceding \\left",t);return{type:"middle",mode:i.parser.mode,delim:t.text}},htmlBuilder:(i,e)=>{var t;if(i.delim===".")t=ll(e,[]);else{t=Ir.sizedDelim(i.delim,1,e,i.mode,[]);var r={delim:i.delim,options:e};t.isMiddle=r}return t},mathmlBuilder:(i,e)=>{var t=i.delim==="\\vert"||i.delim==="|"?_i("|","text"):_i(i.delim,i.mode),r=new $.MathNode("mo",[t]);return r.setAttribute("fence","true"),r.setAttribute("lspace","0.05em"),r.setAttribute("rspace","0.05em"),r}});H1=(i,e)=>{var t=z.wrapFragment(Te(i.body,e),e),r=i.label.slice(1),n=e.sizeMultiplier,s,a=0,o=Ee.isCharacterBox(i.body);if(r==="sout")s=z.makeSpan(["stretchy","sout"]),s.height=e.fontMetrics().defaultRuleThickness/n,a=-.5*e.fontMetrics().xHeight;else if(r==="phase"){var l=Ke({number:.6,unit:"pt"},e),h=Ke({number:.35,unit:"ex"},e),d=e.havingBaseSizing();n=n/d.sizeMultiplier;var c=t.height+t.depth+l+h;t.style.paddingLeft=V(c/2+l);var f=Math.floor(1e3*c*n),m=XS(f),g=new Yi([new hr("phase",m)],{width:"400em",height:V(f/1e3),viewBox:"0 0 400000 "+f,preserveAspectRatio:"xMinYMin slice"});s=z.makeSvgSpan(["hide-tail"],[g],e),s.style.height=V(c),a=t.depth+l+h}else{/cancel/.test(r)?o||t.classes.push("cancel-pad"):r==="angl"?t.classes.push("anglpad"):t.classes.push("boxpad");var x=0,y=0,b=0;/box/.test(r)?(b=Math.max(e.fontMetrics().fboxrule,e.minRuleThickness),x=e.fontMetrics().fboxsep+(r==="colorbox"?0:b),y=x):r==="angl"?(b=Math.max(e.fontMetrics().defaultRuleThickness,e.minRuleThickness),x=4*b,y=Math.max(0,.25-t.depth)):(x=o?.2:0,y=x),s=Rr.encloseSpan(t,r,x,y,e),/fbox|boxed|fcolorbox/.test(r)?(s.style.borderStyle="solid",s.style.borderWidth=V(b)):r==="angl"&&b!==.049&&(s.style.borderTopWidth=V(b),s.style.borderRightWidth=V(b)),a=t.depth+y,i.backgroundColor&&(s.style.backgroundColor=i.backgroundColor,i.borderColor&&(s.style.borderColor=i.borderColor))}var E;if(i.backgroundColor)E=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:s,shift:a},{type:"elem",elem:t,shift:0}]},e);else{var k=/cancel|phase/.test(r)?["svg-align"]:[];E=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:t,shift:0},{type:"elem",elem:s,shift:a,wrapperClasses:k}]},e)}return/cancel/.test(r)&&(E.height=t.height,E.depth=t.depth),/cancel/.test(r)&&!o?z.makeSpan(["mord","cancel-lap"],[E],e):z.makeSpan(["mord"],[E],e)},U1=(i,e)=>{var t=0,r=new $.MathNode(i.label.indexOf("colorbox")>-1?"mpadded":"menclose",[Re(i.body,e)]);switch(i.label){case"\\cancel":r.setAttribute("notation","updiagonalstrike");break;case"\\bcancel":r.setAttribute("notation","downdiagonalstrike");break;case"\\phase":r.setAttribute("notation","phasorangle");break;case"\\sout":r.setAttribute("notation","horizontalstrike");break;case"\\fbox":r.setAttribute("notation","box");break;case"\\angl":r.setAttribute("notation","actuarial");break;case"\\fcolorbox":case"\\colorbox":if(t=e.fontMetrics().fboxsep*e.fontMetrics().ptPerEm,r.setAttribute("width","+"+2*t+"pt"),r.setAttribute("height","+"+2*t+"pt"),r.setAttribute("lspace",t+"pt"),r.setAttribute("voffset",t+"pt"),i.label==="\\fcolorbox"){var n=Math.max(e.fontMetrics().fboxrule,e.minRuleThickness);r.setAttribute("style","border: "+n+"em solid "+String(i.borderColor))}break;case"\\xcancel":r.setAttribute("notation","updiagonalstrike downdiagonalstrike");break}return i.backgroundColor&&r.setAttribute("mathbackground",i.backgroundColor),r};Z({type:"enclose",names:["\\colorbox"],props:{numArgs:2,allowedInText:!0,argTypes:["color","text"]},handler(i,e,t){var{parser:r,funcName:n}=i,s=me(e[0],"color-token").color,a=e[1];return{type:"enclose",mode:r.mode,label:n,backgroundColor:s,body:a}},htmlBuilder:H1,mathmlBuilder:U1});Z({type:"enclose",names:["\\fcolorbox"],props:{numArgs:3,allowedInText:!0,argTypes:["color","color","text"]},handler(i,e,t){var{parser:r,funcName:n}=i,s=me(e[0],"color-token").color,a=me(e[1],"color-token").color,o=e[2];return{type:"enclose",mode:r.mode,label:n,backgroundColor:a,borderColor:s,body:o}},htmlBuilder:H1,mathmlBuilder:U1});Z({type:"enclose",names:["\\fbox"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!0},handler(i,e){var{parser:t}=i;return{type:"enclose",mode:t.mode,label:"\\fbox",body:e[0]}}});Z({type:"enclose",names:["\\cancel","\\bcancel","\\xcancel","\\sout","\\phase"],props:{numArgs:1},handler(i,e){var{parser:t,funcName:r}=i,n=e[0];return{type:"enclose",mode:t.mode,label:r,body:n}},htmlBuilder:H1,mathmlBuilder:U1});Z({type:"enclose",names:["\\angl"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!1},handler(i,e){var{parser:t}=i;return{type:"enclose",mode:t.mode,label:"\\angl",body:e[0]}}});rg={};ng={};$h=i=>{var e=i.parser.settings;if(!e.displayMode)throw new G("{"+i.envName+"} can be used only in display mode.")};cr=function(e,t){var r,n,s=e.body.length,a=e.hLinesBeforeRow,o=0,l=new Array(s),h=[],d=Math.max(t.fontMetrics().arrayRuleWidth,t.minRuleThickness),c=1/t.fontMetrics().ptPerEm,f=5*c;if(e.colSeparationType&&e.colSeparationType==="small"){var m=t.havingStyle(ae.SCRIPT).sizeMultiplier;f=.2778*(m/t.sizeMultiplier)}var g=e.colSeparationType==="CD"?Ke({number:3,unit:"ex"},t):12*c,x=3*c,y=e.arraystretch*g,b=.7*y,E=.3*y,k=0;function L(D){for(var ft=0;ft0&&(k+=.25),h.push({pos:k,isDashed:D[ft]})}for(L(a[0]),r=0;r0&&(re+=E,qD))for(r=0;r=o)){var Hi=void 0;(n>0||e.hskipBeforeAndAfter)&&(Hi=Ee.deflt(ot.pregap,f),Hi!==0&&(Q=z.makeSpan(["arraycolsep"],[]),Q.style.width=V(Hi),te.push(Q)));var br=[];for(r=0;r0){for(var wr=z.makeLineSpan("hline",t,d),Ui=z.makeLineSpan("hdashline",t,d),Wt=[{type:"elem",elem:l,shift:0}];h.length>0;){var ge=h.pop(),ye=ge.pos-le;ge.isDashed?Wt.push({type:"elem",elem:Ui,shift:ye}):Wt.push({type:"elem",elem:wr,shift:ye})}l=z.makeVList({positionType:"individualShift",children:Wt},t)}if(he.length===0)return z.makeSpan(["mord"],[l],t);var Mr=z.makeVList({positionType:"individualShift",children:he},t);return Mr=z.makeSpan(["tag"],[Mr],t),z.makeFragment([l,Mr])},ik={c:"center ",l:"left ",r:"right "},ur=function(e,t){for(var r=[],n=new $.MathNode("mtd",[],["mtr-glue"]),s=new $.MathNode("mtd",[],["mml-eqn-num"]),a=0;a0){var g=e.cols,x="",y=!1,b=0,E=g.length;g[0].type==="separator"&&(f+="top ",b=1),g[g.length-1].type==="separator"&&(f+="bottom ",E-=1);for(var k=b;k0?"left ":"",f+=F[F.length-1].length>0?"right ":"";for(var W=1;W-1?"alignat":"align",s=e.envName==="split",a=vn(e.parser,{cols:r,addJot:!0,autoTag:s?void 0:$1(e.envName),emptySingleRow:!0,colSeparationType:n,maxNumCols:s?2:void 0,leqno:e.parser.settings.leqno},"display"),o,l=0,h={type:"ordgroup",mode:e.mode,body:[]};if(t[0]&&t[0].type==="ordgroup"){for(var d="",c=0;c0&&m&&(y=1),r[g]={type:"align",align:x,pregap:y,postgap:0}}return a.colSeparationType=m?"align":"alignat",a};dr({type:"array",names:["array","darray"],props:{numArgs:1},handler(i,e){var t=qh(e[0]),r=t?[e[0]]:me(e[0],"ordgroup").body,n=r.map(function(a){var o=P1(a),l=o.text;if("lcr".indexOf(l)!==-1)return{type:"align",align:l};if(l==="|")return{type:"separator",separator:"|"};if(l===":")return{type:"separator",separator:":"};throw new G("Unknown column alignment: "+l,a)}),s={cols:n,hskipBeforeAndAfter:!0,maxNumCols:n.length};return vn(i.parser,s,j1(i.envName))},htmlBuilder:cr,mathmlBuilder:ur});dr({type:"array",names:["matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix","matrix*","pmatrix*","bmatrix*","Bmatrix*","vmatrix*","Vmatrix*"],props:{numArgs:0},handler(i){var e={matrix:null,pmatrix:["(",")"],bmatrix:["[","]"],Bmatrix:["\\{","\\}"],vmatrix:["|","|"],Vmatrix:["\\Vert","\\Vert"]}[i.envName.replace("*","")],t="c",r={hskipBeforeAndAfter:!1,cols:[{type:"align",align:t}]};if(i.envName.charAt(i.envName.length-1)==="*"){var n=i.parser;if(n.consumeSpaces(),n.fetch().text==="["){if(n.consume(),n.consumeSpaces(),t=n.fetch().text,"lcr".indexOf(t)===-1)throw new G("Expected l or c or r",n.nextToken);n.consume(),n.consumeSpaces(),n.expect("]"),n.consume(),r.cols=[{type:"align",align:t}]}}var s=vn(i.parser,r,j1(i.envName)),a=Math.max(0,...s.body.map(o=>o.length));return s.cols=new Array(a).fill({type:"align",align:t}),e?{type:"leftright",mode:i.mode,body:[s],left:e[0],right:e[1],rightColor:void 0}:s},htmlBuilder:cr,mathmlBuilder:ur});dr({type:"array",names:["smallmatrix"],props:{numArgs:0},handler(i){var e={arraystretch:.5},t=vn(i.parser,e,"script");return t.colSeparationType="small",t},htmlBuilder:cr,mathmlBuilder:ur});dr({type:"array",names:["subarray"],props:{numArgs:1},handler(i,e){var t=qh(e[0]),r=t?[e[0]]:me(e[0],"ordgroup").body,n=r.map(function(a){var o=P1(a),l=o.text;if("lc".indexOf(l)!==-1)return{type:"align",align:l};throw new G("Unknown column alignment: "+l,a)});if(n.length>1)throw new G("{subarray} can contain only one column");var s={cols:n,hskipBeforeAndAfter:!1,arraystretch:.5};if(s=vn(i.parser,s,"script"),s.body.length>0&&s.body[0].length>1)throw new G("{subarray} can contain only one column");return s},htmlBuilder:cr,mathmlBuilder:ur});dr({type:"array",names:["cases","dcases","rcases","drcases"],props:{numArgs:0},handler(i){var e={arraystretch:1.2,cols:[{type:"align",align:"l",pregap:0,postgap:1},{type:"align",align:"l",pregap:0,postgap:0}]},t=vn(i.parser,e,j1(i.envName));return{type:"leftright",mode:i.mode,body:[t],left:i.envName.indexOf("r")>-1?".":"\\{",right:i.envName.indexOf("r")>-1?"\\}":".",rightColor:void 0}},htmlBuilder:cr,mathmlBuilder:ur});dr({type:"array",names:["align","align*","aligned","split"],props:{numArgs:0},handler:sg,htmlBuilder:cr,mathmlBuilder:ur});dr({type:"array",names:["gathered","gather","gather*"],props:{numArgs:0},handler(i){["gather","gather*"].includes(i.envName)&&$h(i);var e={cols:[{type:"align",align:"c"}],addJot:!0,colSeparationType:"gather",autoTag:$1(i.envName),emptySingleRow:!0,leqno:i.parser.settings.leqno};return vn(i.parser,e,"display")},htmlBuilder:cr,mathmlBuilder:ur});dr({type:"array",names:["alignat","alignat*","alignedat"],props:{numArgs:1},handler:sg,htmlBuilder:cr,mathmlBuilder:ur});dr({type:"array",names:["equation","equation*"],props:{numArgs:0},handler(i){$h(i);var e={autoTag:$1(i.envName),emptySingleRow:!0,singleRow:!0,maxNumCols:1,leqno:i.parser.settings.leqno};return vn(i.parser,e,"display")},htmlBuilder:cr,mathmlBuilder:ur});dr({type:"array",names:["CD"],props:{numArgs:0},handler(i){return $h(i),UA(i.parser)},htmlBuilder:cr,mathmlBuilder:ur});M("\\nonumber","\\gdef\\@eqnsw{0}");M("\\notag","\\nonumber");Z({type:"text",names:["\\hline","\\hdashline"],props:{numArgs:0,allowedInText:!0,allowedInMath:!0},handler(i,e){throw new G(i.funcName+" valid only within array environment")}});d4=rg;Z({type:"environment",names:["\\begin","\\end"],props:{numArgs:1,argTypes:["text"]},handler(i,e){var{parser:t,funcName:r}=i,n=e[0];if(n.type!=="ordgroup")throw new G("Invalid environment name",n);for(var s="",a=0;a{var t=i.font,r=e.withFont(t);return Te(i.body,r)},og=(i,e)=>{var t=i.font,r=e.withFont(t);return Re(i.body,r)},c4={"\\Bbb":"\\mathbb","\\bold":"\\mathbf","\\frak":"\\mathfrak","\\bm":"\\boldsymbol"};Z({type:"font",names:["\\mathrm","\\mathit","\\mathbf","\\mathnormal","\\mathsfit","\\mathbb","\\mathcal","\\mathfrak","\\mathscr","\\mathsf","\\mathtt","\\Bbb","\\bold","\\frak"],props:{numArgs:1,allowedInArgument:!0},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=Dh(e[0]),s=r;return s in c4&&(s=c4[s]),{type:"font",mode:t.mode,font:s.slice(1),body:n}},htmlBuilder:ag,mathmlBuilder:og});Z({type:"mclass",names:["\\boldsymbol","\\bm"],props:{numArgs:1},handler:(i,e)=>{var{parser:t}=i,r=e[0],n=Ee.isCharacterBox(r);return{type:"mclass",mode:t.mode,mclass:Hh(r),body:[{type:"font",mode:t.mode,font:"boldsymbol",body:r}],isCharacterBox:n}}});Z({type:"font",names:["\\rm","\\sf","\\tt","\\bf","\\it","\\cal"],props:{numArgs:0,allowedInText:!0},handler:(i,e)=>{var{parser:t,funcName:r,breakOnTokenText:n}=i,{mode:s}=t,a=t.parseExpression(!0,n),o="math"+r.slice(1);return{type:"font",mode:s,font:o,body:{type:"ordgroup",mode:t.mode,body:a}}},htmlBuilder:ag,mathmlBuilder:og});lg=(i,e)=>{var t=e;return i==="display"?t=t.id>=ae.SCRIPT.id?t.text():ae.DISPLAY:i==="text"&&t.size===ae.DISPLAY.size?t=ae.TEXT:i==="script"?t=ae.SCRIPT:i==="scriptscript"&&(t=ae.SCRIPTSCRIPT),t},G1=(i,e)=>{var t=lg(i.size,e.style),r=t.fracNum(),n=t.fracDen(),s;s=e.havingStyle(r);var a=Te(i.numer,s,e);if(i.continued){var o=8.5/e.fontMetrics().ptPerEm,l=3.5/e.fontMetrics().ptPerEm;a.height=a.height0?g=3*f:g=7*f,x=e.fontMetrics().denom1):(c>0?(m=e.fontMetrics().num2,g=f):(m=e.fontMetrics().num3,g=3*f),x=e.fontMetrics().denom2);var y;if(d){var E=e.fontMetrics().axisHeight;m-a.depth-(E+.5*c){var t=new $.MathNode("mfrac",[Re(i.numer,e),Re(i.denom,e)]);if(!i.hasBarLine)t.setAttribute("linethickness","0px");else if(i.barSize){var r=Ke(i.barSize,e);t.setAttribute("linethickness",V(r))}var n=lg(i.size,e.style);if(n.size!==e.style.size){t=new $.MathNode("mstyle",[t]);var s=n.size===ae.DISPLAY.size?"true":"false";t.setAttribute("displaystyle",s),t.setAttribute("scriptlevel","0")}if(i.leftDelim!=null||i.rightDelim!=null){var a=[];if(i.leftDelim!=null){var o=new $.MathNode("mo",[new $.TextNode(i.leftDelim.replace("\\",""))]);o.setAttribute("fence","true"),a.push(o)}if(a.push(t),i.rightDelim!=null){var l=new $.MathNode("mo",[new $.TextNode(i.rightDelim.replace("\\",""))]);l.setAttribute("fence","true"),a.push(l)}return O1(a)}return t};Z({type:"genfrac",names:["\\dfrac","\\frac","\\tfrac","\\dbinom","\\binom","\\tbinom","\\\\atopfrac","\\\\bracefrac","\\\\brackfrac"],props:{numArgs:2,allowedInArgument:!0},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0],s=e[1],a,o=null,l=null,h="auto";switch(r){case"\\dfrac":case"\\frac":case"\\tfrac":a=!0;break;case"\\\\atopfrac":a=!1;break;case"\\dbinom":case"\\binom":case"\\tbinom":a=!1,o="(",l=")";break;case"\\\\bracefrac":a=!1,o="\\{",l="\\}";break;case"\\\\brackfrac":a=!1,o="[",l="]";break;default:throw new Error("Unrecognized genfrac command")}switch(r){case"\\dfrac":case"\\dbinom":h="display";break;case"\\tfrac":case"\\tbinom":h="text";break}return{type:"genfrac",mode:t.mode,continued:!1,numer:n,denom:s,hasBarLine:a,leftDelim:o,rightDelim:l,size:h,barSize:null}},htmlBuilder:G1,mathmlBuilder:W1});Z({type:"genfrac",names:["\\cfrac"],props:{numArgs:2},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0],s=e[1];return{type:"genfrac",mode:t.mode,continued:!0,numer:n,denom:s,hasBarLine:!0,leftDelim:null,rightDelim:null,size:"display",barSize:null}}});Z({type:"infix",names:["\\over","\\choose","\\atop","\\brace","\\brack"],props:{numArgs:0,infix:!0},handler(i){var{parser:e,funcName:t,token:r}=i,n;switch(t){case"\\over":n="\\frac";break;case"\\choose":n="\\binom";break;case"\\atop":n="\\\\atopfrac";break;case"\\brace":n="\\\\bracefrac";break;case"\\brack":n="\\\\brackfrac";break;default:throw new Error("Unrecognized infix genfrac command")}return{type:"infix",mode:e.mode,replaceWith:n,token:r}}});u4=["display","text","script","scriptscript"],f4=function(e){var t=null;return e.length>0&&(t=e,t=t==="."?null:t),t};Z({type:"genfrac",names:["\\genfrac"],props:{numArgs:6,allowedInArgument:!0,argTypes:["math","math","size","text","math","math"]},handler(i,e){var{parser:t}=i,r=e[4],n=e[5],s=Dh(e[0]),a=s.type==="atom"&&s.family==="open"?f4(s.text):null,o=Dh(e[1]),l=o.type==="atom"&&o.family==="close"?f4(o.text):null,h=me(e[2],"size"),d,c=null;h.isBlank?d=!0:(c=h.value,d=c.number>0);var f="auto",m=e[3];if(m.type==="ordgroup"){if(m.body.length>0){var g=me(m.body[0],"textord");f=u4[Number(g.text)]}}else m=me(m,"textord"),f=u4[Number(m.text)];return{type:"genfrac",mode:t.mode,numer:r,denom:n,continued:!1,hasBarLine:d,barSize:c,leftDelim:a,rightDelim:l,size:f}},htmlBuilder:G1,mathmlBuilder:W1});Z({type:"infix",names:["\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler(i,e){var{parser:t,funcName:r,token:n}=i;return{type:"infix",mode:t.mode,replaceWith:"\\\\abovefrac",size:me(e[0],"size").value,token:n}}});Z({type:"genfrac",names:["\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0],s=RS(me(e[1],"infix").size),a=e[2],o=s.number>0;return{type:"genfrac",mode:t.mode,numer:n,denom:a,continued:!1,hasBarLine:o,barSize:s,leftDelim:null,rightDelim:null,size:"auto"}},htmlBuilder:G1,mathmlBuilder:W1});hg=(i,e)=>{var t=e.style,r,n;i.type==="supsub"?(r=i.sup?Te(i.sup,e.havingStyle(t.sup()),e):Te(i.sub,e.havingStyle(t.sub()),e),n=me(i.base,"horizBrace")):n=me(i,"horizBrace");var s=Te(n.base,e.havingBaseStyle(ae.DISPLAY)),a=Rr.svgSpan(n,e),o;if(n.isOver?(o=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"kern",size:.1},{type:"elem",elem:a}]},e),o.children[0].children[0].children[1].classes.push("svg-align")):(o=z.makeVList({positionType:"bottom",positionData:s.depth+.1+a.height,children:[{type:"elem",elem:a},{type:"kern",size:.1},{type:"elem",elem:s}]},e),o.children[0].children[0].children[0].classes.push("svg-align")),r){var l=z.makeSpan(["mord",n.isOver?"mover":"munder"],[o],e);n.isOver?o=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:l},{type:"kern",size:.2},{type:"elem",elem:r}]},e):o=z.makeVList({positionType:"bottom",positionData:l.depth+.2+r.height+r.depth,children:[{type:"elem",elem:r},{type:"kern",size:.2},{type:"elem",elem:l}]},e)}return z.makeSpan(["mord",n.isOver?"mover":"munder"],[o],e)},rk=(i,e)=>{var t=Rr.mathMLnode(i.label);return new $.MathNode(i.isOver?"mover":"munder",[Re(i.base,e),t])};Z({type:"horizBrace",names:["\\overbrace","\\underbrace"],props:{numArgs:1},handler(i,e){var{parser:t,funcName:r}=i;return{type:"horizBrace",mode:t.mode,label:r,isOver:/^\\over/.test(r),base:e[0]}},htmlBuilder:hg,mathmlBuilder:rk});Z({type:"href",names:["\\href"],props:{numArgs:2,argTypes:["url","original"],allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=e[1],n=me(e[0],"url").url;return t.settings.isTrusted({command:"\\href",url:n})?{type:"href",mode:t.mode,href:n,body:rt(r)}:t.formatUnsupportedCmd("\\href")},htmlBuilder:(i,e)=>{var t=mt(i.body,e,!1);return z.makeAnchor(i.href,[],t,e)},mathmlBuilder:(i,e)=>{var t=yn(i.body,e);return t instanceof Bt||(t=new Bt("mrow",[t])),t.setAttribute("href",i.href),t}});Z({type:"href",names:["\\url"],props:{numArgs:1,argTypes:["url"],allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=me(e[0],"url").url;if(!t.settings.isTrusted({command:"\\url",url:r}))return t.formatUnsupportedCmd("\\url");for(var n=[],s=0;s{var{parser:t,funcName:r,token:n}=i,s=me(e[0],"raw").string,a=e[1];t.settings.strict&&t.settings.reportNonstrict("htmlExtension","HTML extension is disabled on strict mode");var o,l={};switch(r){case"\\htmlClass":l.class=s,o={command:"\\htmlClass",class:s};break;case"\\htmlId":l.id=s,o={command:"\\htmlId",id:s};break;case"\\htmlStyle":l.style=s,o={command:"\\htmlStyle",style:s};break;case"\\htmlData":{for(var h=s.split(","),d=0;d{var t=mt(i.body,e,!1),r=["enclosing"];i.attributes.class&&r.push(...i.attributes.class.trim().split(/\s+/));var n=z.makeSpan(r,t,e);for(var s in i.attributes)s!=="class"&&i.attributes.hasOwnProperty(s)&&n.setAttribute(s,i.attributes[s]);return n},mathmlBuilder:(i,e)=>yn(i.body,e)});Z({type:"htmlmathml",names:["\\html@mathml"],props:{numArgs:2,allowedInText:!0},handler:(i,e)=>{var{parser:t}=i;return{type:"htmlmathml",mode:t.mode,html:rt(e[0]),mathml:rt(e[1])}},htmlBuilder:(i,e)=>{var t=mt(i.html,e,!1);return z.makeFragment(t)},mathmlBuilder:(i,e)=>yn(i.mathml,e)});x1=function(e){if(/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(e))return{number:+e,unit:"bp"};var t=/([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(e);if(!t)throw new G("Invalid size: '"+e+"' in \\includegraphics");var r={number:+(t[1]+t[2]),unit:t[3]};if(!k4(r))throw new G("Invalid unit: '"+r.unit+"' in \\includegraphics.");return r};Z({type:"includegraphics",names:["\\includegraphics"],props:{numArgs:1,numOptionalArgs:1,argTypes:["raw","url"],allowedInText:!1},handler:(i,e,t)=>{var{parser:r}=i,n={number:0,unit:"em"},s={number:.9,unit:"em"},a={number:0,unit:"em"},o="";if(t[0])for(var l=me(t[0],"raw").string,h=l.split(","),d=0;d{var t=Ke(i.height,e),r=0;i.totalheight.number>0&&(r=Ke(i.totalheight,e)-t);var n=0;i.width.number>0&&(n=Ke(i.width,e));var s={height:V(t+r)};n>0&&(s.width=V(n)),r>0&&(s.verticalAlign=V(-r));var a=new T1(i.src,i.alt,s);return a.height=t,a.depth=r,a},mathmlBuilder:(i,e)=>{var t=new $.MathNode("mglyph",[]);t.setAttribute("alt",i.alt);var r=Ke(i.height,e),n=0;if(i.totalheight.number>0&&(n=Ke(i.totalheight,e)-r,t.setAttribute("valign",V(-n))),t.setAttribute("height",V(r+n)),i.width.number>0){var s=Ke(i.width,e);t.setAttribute("width",V(s))}return t.setAttribute("src",i.src),t}});Z({type:"kern",names:["\\kern","\\mkern","\\hskip","\\mskip"],props:{numArgs:1,argTypes:["size"],primitive:!0,allowedInText:!0},handler(i,e){var{parser:t,funcName:r}=i,n=me(e[0],"size");if(t.settings.strict){var s=r[1]==="m",a=n.value.unit==="mu";s?(a||t.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+r+" supports only mu units, "+("not "+n.value.unit+" units")),t.mode!=="math"&&t.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+r+" works only in math mode")):a&&t.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+r+" doesn't support mu units")}return{type:"kern",mode:t.mode,dimension:n.value}},htmlBuilder(i,e){return z.makeGlue(i.dimension,e)},mathmlBuilder(i,e){var t=Ke(i.dimension,e);return new $.SpaceNode(t)}});Z({type:"lap",names:["\\mathllap","\\mathrlap","\\mathclap"],props:{numArgs:1,allowedInText:!0},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0];return{type:"lap",mode:t.mode,alignment:r.slice(5),body:n}},htmlBuilder:(i,e)=>{var t;i.alignment==="clap"?(t=z.makeSpan([],[Te(i.body,e)]),t=z.makeSpan(["inner"],[t],e)):t=z.makeSpan(["inner"],[Te(i.body,e)]);var r=z.makeSpan(["fix"],[]),n=z.makeSpan([i.alignment],[t,r],e),s=z.makeSpan(["strut"]);return s.style.height=V(n.height+n.depth),n.depth&&(s.style.verticalAlign=V(-n.depth)),n.children.unshift(s),n=z.makeSpan(["thinbox"],[n],e),z.makeSpan(["mord","vbox"],[n],e)},mathmlBuilder:(i,e)=>{var t=new $.MathNode("mpadded",[Re(i.body,e)]);if(i.alignment!=="rlap"){var r=i.alignment==="llap"?"-1":"-0.5";t.setAttribute("lspace",r+"width")}return t.setAttribute("width","0px"),t}});Z({type:"styling",names:["\\(","$"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(i,e){var{funcName:t,parser:r}=i,n=r.mode;r.switchMode("math");var s=t==="\\("?"\\)":"$",a=r.parseExpression(!1,s);return r.expect(s),r.switchMode(n),{type:"styling",mode:r.mode,style:"text",body:a}}});Z({type:"text",names:["\\)","\\]"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(i,e){throw new G("Mismatched "+i.funcName)}});m4=(i,e)=>{switch(e.style.size){case ae.DISPLAY.size:return i.display;case ae.TEXT.size:return i.text;case ae.SCRIPT.size:return i.script;case ae.SCRIPTSCRIPT.size:return i.scriptscript;default:return i.text}};Z({type:"mathchoice",names:["\\mathchoice"],props:{numArgs:4,primitive:!0},handler:(i,e)=>{var{parser:t}=i;return{type:"mathchoice",mode:t.mode,display:rt(e[0]),text:rt(e[1]),script:rt(e[2]),scriptscript:rt(e[3])}},htmlBuilder:(i,e)=>{var t=m4(i,e),r=mt(t,e,!1);return z.makeFragment(r)},mathmlBuilder:(i,e)=>{var t=m4(i,e);return yn(t,e)}});dg=(i,e,t,r,n,s,a)=>{i=z.makeSpan([],[i]);var o=t&&Ee.isCharacterBox(t),l,h;if(e){var d=Te(e,r.havingStyle(n.sup()),r);h={elem:d,kern:Math.max(r.fontMetrics().bigOpSpacing1,r.fontMetrics().bigOpSpacing3-d.depth)}}if(t){var c=Te(t,r.havingStyle(n.sub()),r);l={elem:c,kern:Math.max(r.fontMetrics().bigOpSpacing2,r.fontMetrics().bigOpSpacing4-c.height)}}var f;if(h&&l){var m=r.fontMetrics().bigOpSpacing5+l.elem.height+l.elem.depth+l.kern+i.depth+a;f=z.makeVList({positionType:"bottom",positionData:m,children:[{type:"kern",size:r.fontMetrics().bigOpSpacing5},{type:"elem",elem:l.elem,marginLeft:V(-s)},{type:"kern",size:l.kern},{type:"elem",elem:i},{type:"kern",size:h.kern},{type:"elem",elem:h.elem,marginLeft:V(s)},{type:"kern",size:r.fontMetrics().bigOpSpacing5}]},r)}else if(l){var g=i.height-a;f=z.makeVList({positionType:"top",positionData:g,children:[{type:"kern",size:r.fontMetrics().bigOpSpacing5},{type:"elem",elem:l.elem,marginLeft:V(-s)},{type:"kern",size:l.kern},{type:"elem",elem:i}]},r)}else if(h){var x=i.depth+a;f=z.makeVList({positionType:"bottom",positionData:x,children:[{type:"elem",elem:i},{type:"kern",size:h.kern},{type:"elem",elem:h.elem,marginLeft:V(s)},{type:"kern",size:r.fontMetrics().bigOpSpacing5}]},r)}else return i;var y=[f];if(l&&s!==0&&!o){var b=z.makeSpan(["mspace"],[],r);b.style.marginRight=V(s),y.unshift(b)}return z.makeSpan(["mop","op-limits"],y,r)},cg=["\\smallint"],ga=(i,e)=>{var t,r,n=!1,s;i.type==="supsub"?(t=i.sup,r=i.sub,s=me(i.base,"op"),n=!0):s=me(i,"op");var a=e.style,o=!1;a.size===ae.DISPLAY.size&&s.symbol&&!cg.includes(s.name)&&(o=!0);var l;if(s.symbol){var h=o?"Size2-Regular":"Size1-Regular",d="";if((s.name==="\\oiint"||s.name==="\\oiiint")&&(d=s.name.slice(1),s.name=d==="oiint"?"\\iint":"\\iiint"),l=z.makeSymbol(s.name,h,"math",e,["mop","op-symbol",o?"large-op":"small-op"]),d.length>0){var c=l.italic,f=z.staticSvg(d+"Size"+(o?"2":"1"),e);l=z.makeVList({positionType:"individualShift",children:[{type:"elem",elem:l,shift:0},{type:"elem",elem:f,shift:o?.08:0}]},e),s.name="\\"+d,l.classes.unshift("mop"),l.italic=c}}else if(s.body){var m=mt(s.body,e,!0);m.length===1&&m[0]instanceof ii?(l=m[0],l.classes[0]="mop"):l=z.makeSpan(["mop"],m,e)}else{for(var g=[],x=1;x{var t;if(i.symbol)t=new Bt("mo",[_i(i.name,i.mode)]),cg.includes(i.name)&&t.setAttribute("largeop","false");else if(i.body)t=new Bt("mo",ri(i.body,e));else{t=new Bt("mi",[new Ci(i.name.slice(1))]);var r=new Bt("mo",[_i("\u2061","text")]);i.parentIsSupSub?t=new Bt("mrow",[t,r]):t=F4([t,r])}return t},nk={"\u220F":"\\prod","\u2210":"\\coprod","\u2211":"\\sum","\u22C0":"\\bigwedge","\u22C1":"\\bigvee","\u22C2":"\\bigcap","\u22C3":"\\bigcup","\u2A00":"\\bigodot","\u2A01":"\\bigoplus","\u2A02":"\\bigotimes","\u2A04":"\\biguplus","\u2A06":"\\bigsqcup"};Z({type:"op",names:["\\coprod","\\bigvee","\\bigwedge","\\biguplus","\\bigcap","\\bigcup","\\intop","\\prod","\\sum","\\bigotimes","\\bigoplus","\\bigodot","\\bigsqcup","\\smallint","\u220F","\u2210","\u2211","\u22C0","\u22C1","\u22C2","\u22C3","\u2A00","\u2A01","\u2A02","\u2A04","\u2A06"],props:{numArgs:0},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=r;return n.length===1&&(n=nk[n]),{type:"op",mode:t.mode,limits:!0,parentIsSupSub:!1,symbol:!0,name:n}},htmlBuilder:ga,mathmlBuilder:hl});Z({type:"op",names:["\\mathop"],props:{numArgs:1,primitive:!0},handler:(i,e)=>{var{parser:t}=i,r=e[0];return{type:"op",mode:t.mode,limits:!1,parentIsSupSub:!1,symbol:!1,body:rt(r)}},htmlBuilder:ga,mathmlBuilder:hl});sk={"\u222B":"\\int","\u222C":"\\iint","\u222D":"\\iiint","\u222E":"\\oint","\u222F":"\\oiint","\u2230":"\\oiiint"};Z({type:"op",names:["\\arcsin","\\arccos","\\arctan","\\arctg","\\arcctg","\\arg","\\ch","\\cos","\\cosec","\\cosh","\\cot","\\cotg","\\coth","\\csc","\\ctg","\\cth","\\deg","\\dim","\\exp","\\hom","\\ker","\\lg","\\ln","\\log","\\sec","\\sin","\\sinh","\\sh","\\tan","\\tanh","\\tg","\\th"],props:{numArgs:0},handler(i){var{parser:e,funcName:t}=i;return{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!1,name:t}},htmlBuilder:ga,mathmlBuilder:hl});Z({type:"op",names:["\\det","\\gcd","\\inf","\\lim","\\max","\\min","\\Pr","\\sup"],props:{numArgs:0},handler(i){var{parser:e,funcName:t}=i;return{type:"op",mode:e.mode,limits:!0,parentIsSupSub:!1,symbol:!1,name:t}},htmlBuilder:ga,mathmlBuilder:hl});Z({type:"op",names:["\\int","\\iint","\\iiint","\\oint","\\oiint","\\oiiint","\u222B","\u222C","\u222D","\u222E","\u222F","\u2230"],props:{numArgs:0,allowedInArgument:!0},handler(i){var{parser:e,funcName:t}=i,r=t;return r.length===1&&(r=sk[r]),{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!0,name:r}},htmlBuilder:ga,mathmlBuilder:hl});ug=(i,e)=>{var t,r,n=!1,s;i.type==="supsub"?(t=i.sup,r=i.sub,s=me(i.base,"operatorname"),n=!0):s=me(i,"operatorname");var a;if(s.body.length>0){for(var o=s.body.map(c=>{var f=c.text;return typeof f=="string"?{type:"textord",mode:c.mode,text:f}:c}),l=mt(o,e.withFont("mathrm"),!0),h=0;h{for(var t=ri(i.body,e.withFont("mathrm")),r=!0,n=0;nd.toText()).join("");t=[new $.TextNode(o)]}var l=new $.MathNode("mi",t);l.setAttribute("mathvariant","normal");var h=new $.MathNode("mo",[_i("\u2061","text")]);return i.parentIsSupSub?new $.MathNode("mrow",[l,h]):$.newDocumentFragment([l,h])};Z({type:"operatorname",names:["\\operatorname@","\\operatornamewithlimits"],props:{numArgs:1},handler:(i,e)=>{var{parser:t,funcName:r}=i,n=e[0];return{type:"operatorname",mode:t.mode,body:rt(n),alwaysHandleSupSub:r==="\\operatornamewithlimits",limits:!1,parentIsSupSub:!1}},htmlBuilder:ug,mathmlBuilder:ak});M("\\operatorname","\\@ifstar\\operatornamewithlimits\\operatorname@");gs({type:"ordgroup",htmlBuilder(i,e){return i.semisimple?z.makeFragment(mt(i.body,e,!1)):z.makeSpan(["mord"],mt(i.body,e,!0),e)},mathmlBuilder(i,e){return yn(i.body,e,!0)}});Z({type:"overline",names:["\\overline"],props:{numArgs:1},handler(i,e){var{parser:t}=i,r=e[0];return{type:"overline",mode:t.mode,body:r}},htmlBuilder(i,e){var t=Te(i.body,e.havingCrampedStyle()),r=z.makeLineSpan("overline-line",e),n=e.fontMetrics().defaultRuleThickness,s=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:t},{type:"kern",size:3*n},{type:"elem",elem:r},{type:"kern",size:n}]},e);return z.makeSpan(["mord","overline"],[s],e)},mathmlBuilder(i,e){var t=new $.MathNode("mo",[new $.TextNode("\u203E")]);t.setAttribute("stretchy","true");var r=new $.MathNode("mover",[Re(i.body,e),t]);return r.setAttribute("accent","true"),r}});Z({type:"phantom",names:["\\phantom"],props:{numArgs:1,allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=e[0];return{type:"phantom",mode:t.mode,body:rt(r)}},htmlBuilder:(i,e)=>{var t=mt(i.body,e.withPhantom(),!1);return z.makeFragment(t)},mathmlBuilder:(i,e)=>{var t=ri(i.body,e);return new $.MathNode("mphantom",t)}});Z({type:"hphantom",names:["\\hphantom"],props:{numArgs:1,allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=e[0];return{type:"hphantom",mode:t.mode,body:r}},htmlBuilder:(i,e)=>{var t=z.makeSpan([],[Te(i.body,e.withPhantom())]);if(t.height=0,t.depth=0,t.children)for(var r=0;r{var t=ri(rt(i.body),e),r=new $.MathNode("mphantom",t),n=new $.MathNode("mpadded",[r]);return n.setAttribute("height","0px"),n.setAttribute("depth","0px"),n}});Z({type:"vphantom",names:["\\vphantom"],props:{numArgs:1,allowedInText:!0},handler:(i,e)=>{var{parser:t}=i,r=e[0];return{type:"vphantom",mode:t.mode,body:r}},htmlBuilder:(i,e)=>{var t=z.makeSpan(["inner"],[Te(i.body,e.withPhantom())]),r=z.makeSpan(["fix"],[]);return z.makeSpan(["mord","rlap"],[t,r],e)},mathmlBuilder:(i,e)=>{var t=ri(rt(i.body),e),r=new $.MathNode("mphantom",t),n=new $.MathNode("mpadded",[r]);return n.setAttribute("width","0px"),n}});Z({type:"raisebox",names:["\\raisebox"],props:{numArgs:2,argTypes:["size","hbox"],allowedInText:!0},handler(i,e){var{parser:t}=i,r=me(e[0],"size").value,n=e[1];return{type:"raisebox",mode:t.mode,dy:r,body:n}},htmlBuilder(i,e){var t=Te(i.body,e),r=Ke(i.dy,e);return z.makeVList({positionType:"shift",positionData:-r,children:[{type:"elem",elem:t}]},e)},mathmlBuilder(i,e){var t=new $.MathNode("mpadded",[Re(i.body,e)]),r=i.dy.number+i.dy.unit;return t.setAttribute("voffset",r),t}});Z({type:"internal",names:["\\relax"],props:{numArgs:0,allowedInText:!0,allowedInArgument:!0},handler(i){var{parser:e}=i;return{type:"internal",mode:e.mode}}});Z({type:"rule",names:["\\rule"],props:{numArgs:2,numOptionalArgs:1,allowedInText:!0,allowedInMath:!0,argTypes:["size","size","size"]},handler(i,e,t){var{parser:r}=i,n=t[0],s=me(e[0],"size"),a=me(e[1],"size");return{type:"rule",mode:r.mode,shift:n&&me(n,"size").value,width:s.value,height:a.value}},htmlBuilder(i,e){var t=z.makeSpan(["mord","rule"],[],e),r=Ke(i.width,e),n=Ke(i.height,e),s=i.shift?Ke(i.shift,e):0;return t.style.borderRightWidth=V(r),t.style.borderTopWidth=V(n),t.style.bottom=V(s),t.width=r,t.height=n+s,t.depth=-s,t.maxFontSize=n*1.125*e.sizeMultiplier,t},mathmlBuilder(i,e){var t=Ke(i.width,e),r=Ke(i.height,e),n=i.shift?Ke(i.shift,e):0,s=e.color&&e.getColor()||"black",a=new $.MathNode("mspace");a.setAttribute("mathbackground",s),a.setAttribute("width",V(t)),a.setAttribute("height",V(r));var o=new $.MathNode("mpadded",[a]);return n>=0?o.setAttribute("height",V(n)):(o.setAttribute("height",V(n)),o.setAttribute("depth",V(-n))),o.setAttribute("voffset",V(n)),o}});p4=["\\tiny","\\sixptsize","\\scriptsize","\\footnotesize","\\small","\\normalsize","\\large","\\Large","\\LARGE","\\huge","\\Huge"],ok=(i,e)=>{var t=e.havingSize(i.size);return fg(i.body,t,e)};Z({type:"sizing",names:p4,props:{numArgs:0,allowedInText:!0},handler:(i,e)=>{var{breakOnTokenText:t,funcName:r,parser:n}=i,s=n.parseExpression(!1,t);return{type:"sizing",mode:n.mode,size:p4.indexOf(r)+1,body:s}},htmlBuilder:ok,mathmlBuilder:(i,e)=>{var t=e.havingSize(i.size),r=ri(i.body,t),n=new $.MathNode("mstyle",r);return n.setAttribute("mathsize",V(t.sizeMultiplier)),n}});Z({type:"smash",names:["\\smash"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0},handler:(i,e,t)=>{var{parser:r}=i,n=!1,s=!1,a=t[0]&&me(t[0],"ordgroup");if(a)for(var o="",l=0;l{var t=z.makeSpan([],[Te(i.body,e)]);if(!i.smashHeight&&!i.smashDepth)return t;if(i.smashHeight&&(t.height=0,t.children))for(var r=0;r{var t=new $.MathNode("mpadded",[Re(i.body,e)]);return i.smashHeight&&t.setAttribute("height","0px"),i.smashDepth&&t.setAttribute("depth","0px"),t}});Z({type:"sqrt",names:["\\sqrt"],props:{numArgs:1,numOptionalArgs:1},handler(i,e,t){var{parser:r}=i,n=t[0],s=e[0];return{type:"sqrt",mode:r.mode,body:s,index:n}},htmlBuilder(i,e){var t=Te(i.body,e.havingCrampedStyle());t.height===0&&(t.height=e.fontMetrics().xHeight),t=z.wrapFragment(t,e);var r=e.fontMetrics(),n=r.defaultRuleThickness,s=n;e.style.idt.height+t.depth+a&&(a=(a+c-t.height-t.depth)/2);var f=l.height-t.height-a-h;t.style.paddingLeft=V(d);var m=z.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:t,wrapperClasses:["svg-align"]},{type:"kern",size:-(t.height+f)},{type:"elem",elem:l},{type:"kern",size:h}]},e);if(i.index){var g=e.havingStyle(ae.SCRIPTSCRIPT),x=Te(i.index,g,e),y=.6*(m.height-m.depth),b=z.makeVList({positionType:"shift",positionData:-y,children:[{type:"elem",elem:x}]},e),E=z.makeSpan(["root"],[b]);return z.makeSpan(["mord","sqrt"],[E,m],e)}else return z.makeSpan(["mord","sqrt"],[m],e)},mathmlBuilder(i,e){var{body:t,index:r}=i;return r?new $.MathNode("mroot",[Re(t,e),Re(r,e)]):new $.MathNode("msqrt",[Re(t,e)])}});g4={display:ae.DISPLAY,text:ae.TEXT,script:ae.SCRIPT,scriptscript:ae.SCRIPTSCRIPT};Z({type:"styling",names:["\\displaystyle","\\textstyle","\\scriptstyle","\\scriptscriptstyle"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(i,e){var{breakOnTokenText:t,funcName:r,parser:n}=i,s=n.parseExpression(!0,t),a=r.slice(1,r.length-5);return{type:"styling",mode:n.mode,style:a,body:s}},htmlBuilder(i,e){var t=g4[i.style],r=e.havingStyle(t).withFont("");return fg(i.body,r,e)},mathmlBuilder(i,e){var t=g4[i.style],r=e.havingStyle(t),n=ri(i.body,r),s=new $.MathNode("mstyle",n),a={display:["0","true"],text:["0","false"],script:["1","false"],scriptscript:["2","false"]},o=a[i.style];return s.setAttribute("scriptlevel",o[0]),s.setAttribute("displaystyle",o[1]),s}});lk=function(e,t){var r=e.base;if(r)if(r.type==="op"){var n=r.limits&&(t.style.size===ae.DISPLAY.size||r.alwaysHandleSupSub);return n?ga:null}else if(r.type==="operatorname"){var s=r.alwaysHandleSupSub&&(t.style.size===ae.DISPLAY.size||r.limits);return s?ug:null}else{if(r.type==="accent")return Ee.isCharacterBox(r.base)?F1:null;if(r.type==="horizBrace"){var a=!e.sub;return a===r.isOver?hg:null}else return null}else return null};gs({type:"supsub",htmlBuilder(i,e){var t=lk(i,e);if(t)return t(i,e);var{base:r,sup:n,sub:s}=i,a=Te(r,e),o,l,h=e.fontMetrics(),d=0,c=0,f=r&&Ee.isCharacterBox(r);if(n){var m=e.havingStyle(e.style.sup());o=Te(n,m,e),f||(d=a.height-m.fontMetrics().supDrop*m.sizeMultiplier/e.sizeMultiplier)}if(s){var g=e.havingStyle(e.style.sub());l=Te(s,g,e),f||(c=a.depth+g.fontMetrics().subDrop*g.sizeMultiplier/e.sizeMultiplier)}var x;e.style===ae.DISPLAY?x=h.sup1:e.style.cramped?x=h.sup3:x=h.sup2;var y=e.sizeMultiplier,b=V(.5/h.ptPerEm/y),E=null;if(l){var k=i.base&&i.base.type==="op"&&i.base.name&&(i.base.name==="\\oiint"||i.base.name==="\\oiiint");(a instanceof ii||k)&&(E=V(-a.italic))}var L;if(o&&l){d=Math.max(d,x,o.depth+.25*h.xHeight),c=Math.max(c,h.sub2);var I=h.defaultRuleThickness,O=4*I;if(d-o.depth-(l.height-c)0&&(d+=q,c-=q)}var F=[{type:"elem",elem:l,shift:c,marginRight:b,marginLeft:E},{type:"elem",elem:o,shift:-d,marginRight:b}];L=z.makeVList({positionType:"individualShift",children:F},e)}else if(l){c=Math.max(c,h.sub1,l.height-.8*h.xHeight);var W=[{type:"elem",elem:l,marginLeft:E,marginRight:b}];L=z.makeVList({positionType:"shift",positionData:c,children:W},e)}else if(o)d=Math.max(d,x,o.depth+.25*h.xHeight),L=z.makeVList({positionType:"shift",positionData:-d,children:[{type:"elem",elem:o,marginRight:b}]},e);else throw new Error("supsub must have either sup or sub.");var ne=E1(a,"right")||"mord";return z.makeSpan([ne],[a,z.makeSpan(["msupsub"],[L])],e)},mathmlBuilder(i,e){var t=!1,r,n;i.base&&i.base.type==="horizBrace"&&(n=!!i.sup,n===i.base.isOver&&(t=!0,r=i.base.isOver)),i.base&&(i.base.type==="op"||i.base.type==="operatorname")&&(i.base.parentIsSupSub=!0);var s=[Re(i.base,e)];i.sub&&s.push(Re(i.sub,e)),i.sup&&s.push(Re(i.sup,e));var a;if(t)a=r?"mover":"munder";else if(i.sub)if(i.sup){var h=i.base;h&&h.type==="op"&&h.limits&&e.style===ae.DISPLAY||h&&h.type==="operatorname"&&h.alwaysHandleSupSub&&(e.style===ae.DISPLAY||h.limits)?a="munderover":a="msubsup"}else{var l=i.base;l&&l.type==="op"&&l.limits&&(e.style===ae.DISPLAY||l.alwaysHandleSupSub)||l&&l.type==="operatorname"&&l.alwaysHandleSupSub&&(l.limits||e.style===ae.DISPLAY)?a="munder":a="msub"}else{var o=i.base;o&&o.type==="op"&&o.limits&&(e.style===ae.DISPLAY||o.alwaysHandleSupSub)||o&&o.type==="operatorname"&&o.alwaysHandleSupSub&&(o.limits||e.style===ae.DISPLAY)?a="mover":a="msup"}return new $.MathNode(a,s)}});gs({type:"atom",htmlBuilder(i,e){return z.mathsym(i.text,i.mode,e,["m"+i.family])},mathmlBuilder(i,e){var t=new $.MathNode("mo",[_i(i.text,i.mode)]);if(i.family==="bin"){var r=B1(i,e);r==="bold-italic"&&t.setAttribute("mathvariant",r)}else i.family==="punct"?t.setAttribute("separator","true"):(i.family==="open"||i.family==="close")&&t.setAttribute("stretchy","false");return t}});mg={mi:"italic",mn:"normal",mtext:"normal"};gs({type:"mathord",htmlBuilder(i,e){return z.makeOrd(i,e,"mathord")},mathmlBuilder(i,e){var t=new $.MathNode("mi",[_i(i.text,i.mode,e)]),r=B1(i,e)||"italic";return r!==mg[t.type]&&t.setAttribute("mathvariant",r),t}});gs({type:"textord",htmlBuilder(i,e){return z.makeOrd(i,e,"textord")},mathmlBuilder(i,e){var t=_i(i.text,i.mode,e),r=B1(i,e)||"normal",n;return i.mode==="text"?n=new $.MathNode("mtext",[t]):/[0-9]/.test(i.text)?n=new $.MathNode("mn",[t]):i.text==="\\prime"?n=new $.MathNode("mo",[t]):n=new $.MathNode("mi",[t]),r!==mg[n.type]&&n.setAttribute("mathvariant",r),n}});y1={"\\nobreak":"nobreak","\\allowbreak":"allowbreak"},v1={" ":{},"\\ ":{},"~":{className:"nobreak"},"\\space":{},"\\nobreakspace":{className:"nobreak"}};gs({type:"spacing",htmlBuilder(i,e){if(v1.hasOwnProperty(i.text)){var t=v1[i.text].className||"";if(i.mode==="text"){var r=z.makeOrd(i,e,"textord");return r.classes.push(t),r}else return z.makeSpan(["mspace",t],[z.mathsym(i.text,i.mode,e)],e)}else{if(y1.hasOwnProperty(i.text))return z.makeSpan(["mspace",y1[i.text]],[],e);throw new G('Unknown type of space "'+i.text+'"')}},mathmlBuilder(i,e){var t;if(v1.hasOwnProperty(i.text))t=new $.MathNode("mtext",[new $.TextNode("\xA0")]);else{if(y1.hasOwnProperty(i.text))return new $.MathNode("mspace");throw new G('Unknown type of space "'+i.text+'"')}return t}});x4=()=>{var i=new $.MathNode("mtd",[]);return i.setAttribute("width","50%"),i};gs({type:"tag",mathmlBuilder(i,e){var t=new $.MathNode("mtable",[new $.MathNode("mtr",[x4(),new $.MathNode("mtd",[yn(i.body,e)]),x4(),new $.MathNode("mtd",[yn(i.tag,e)])])]);return t.setAttribute("width","100%"),t}});y4={"\\text":void 0,"\\textrm":"textrm","\\textsf":"textsf","\\texttt":"texttt","\\textnormal":"textrm"},v4={"\\textbf":"textbf","\\textmd":"textmd"},hk={"\\textit":"textit","\\textup":"textup"},b4=(i,e)=>{var t=i.font;if(t){if(y4[t])return e.withTextFontFamily(y4[t]);if(v4[t])return e.withTextFontWeight(v4[t]);if(t==="\\emph")return e.fontShape==="textit"?e.withTextFontShape("textup"):e.withTextFontShape("textit")}else return e;return e.withTextFontShape(hk[t])};Z({type:"text",names:["\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup","\\emph"],props:{numArgs:1,argTypes:["text"],allowedInArgument:!0,allowedInText:!0},handler(i,e){var{parser:t,funcName:r}=i,n=e[0];return{type:"text",mode:t.mode,body:rt(n),font:r}},htmlBuilder(i,e){var t=b4(i,e),r=mt(i.body,t,!0);return z.makeSpan(["mord","text"],r,t)},mathmlBuilder(i,e){var t=b4(i,e);return yn(i.body,t)}});Z({type:"underline",names:["\\underline"],props:{numArgs:1,allowedInText:!0},handler(i,e){var{parser:t}=i;return{type:"underline",mode:t.mode,body:e[0]}},htmlBuilder(i,e){var t=Te(i.body,e),r=z.makeLineSpan("underline-line",e),n=e.fontMetrics().defaultRuleThickness,s=z.makeVList({positionType:"top",positionData:t.height,children:[{type:"kern",size:n},{type:"elem",elem:r},{type:"kern",size:3*n},{type:"elem",elem:t}]},e);return z.makeSpan(["mord","underline"],[s],e)},mathmlBuilder(i,e){var t=new $.MathNode("mo",[new $.TextNode("\u203E")]);t.setAttribute("stretchy","true");var r=new $.MathNode("munder",[Re(i.body,e),t]);return r.setAttribute("accentunder","true"),r}});Z({type:"vcenter",names:["\\vcenter"],props:{numArgs:1,argTypes:["original"],allowedInText:!1},handler(i,e){var{parser:t}=i;return{type:"vcenter",mode:t.mode,body:e[0]}},htmlBuilder(i,e){var t=Te(i.body,e),r=e.fontMetrics().axisHeight,n=.5*(t.height-r-(t.depth+r));return z.makeVList({positionType:"shift",positionData:n,children:[{type:"elem",elem:t}]},e)},mathmlBuilder(i,e){return new $.MathNode("mpadded",[Re(i.body,e)],["vcenter"])}});Z({type:"verb",names:["\\verb"],props:{numArgs:0,allowedInText:!0},handler(i,e,t){throw new G("\\verb ended by end of line instead of matching delimiter")},htmlBuilder(i,e){for(var t=w4(i),r=[],n=e.havingStyle(e.style.text()),s=0;si.body.replace(/ /g,i.star?"\u2423":"\xA0"),gn=B4,pg=`[ \r + ]`,dk="\\\\[a-zA-Z@]+",ck="\\\\[^\uD800-\uDFFF]",uk="("+dk+")"+pg+"*",fk=`\\\\( |[ \r ]+ -?)[ \r ]*`,v1="[\u0300-\u036F]",jA=new RegExp(v1+"+$"),GA="("+tg+"+)|"+($A+"|")+"([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]"+(v1+"*")+"|[\uD800-\uDBFF][\uDC00-\uDFFF]"+(v1+"*")+"|\\\\verb\\*([^]).*?\\4|\\\\verb([^*a-zA-Z]).*?\\5"+("|"+UA)+("|"+HA+")"),kh=class{constructor(e,t){this.input=void 0,this.settings=void 0,this.tokenRegex=void 0,this.catcodes=void 0,this.input=e,this.settings=t,this.tokenRegex=new RegExp(GA,"g"),this.catcodes={"%":14,"~":13}}setCatcode(e,t){this.catcodes[e]=t}lex(){var e=this.input,t=this.tokenRegex.lastIndex;if(t===e.length)return new ui("EOF",new Jt(this,t,t));var r=this.tokenRegex.exec(e);if(r===null||r.index!==t)throw new G("Unexpected character: '"+e[t]+"'",new ui(e[t],new Jt(this,t,t+1)));var n=r[6]||r[3]||(r[2]?"\\ ":" ");if(this.catcodes[n]===14){var s=e.indexOf(` -`,this.tokenRegex.lastIndex);return s===-1?(this.tokenRegex.lastIndex=e.length,this.settings.reportNonstrict("commentAtEnd","% comment has no terminating newline; LaTeX would fail because of commenting the end of math mode (e.g. $)")):this.tokenRegex.lastIndex=s+1,this.lex()}return new ui(n,new Jt(this,t,this.tokenRegex.lastIndex))}},b1=class{constructor(e,t){e===void 0&&(e={}),t===void 0&&(t={}),this.current=void 0,this.builtins=void 0,this.undefStack=void 0,this.current=t,this.builtins=e,this.undefStack=[]}beginGroup(){this.undefStack.push({})}endGroup(){if(this.undefStack.length===0)throw new G("Unbalanced namespace destruction: attempt to pop global namespace; please report this as a bug");var e=this.undefStack.pop();for(var t in e)e.hasOwnProperty(t)&&(e[t]==null?delete this.current[t]:this.current[t]=e[t])}endGroups(){for(;this.undefStack.length>0;)this.endGroup()}has(e){return this.current.hasOwnProperty(e)||this.builtins.hasOwnProperty(e)}get(e){return this.current.hasOwnProperty(e)?this.current[e]:this.builtins[e]}set(e,t,r){if(r===void 0&&(r=!1),r){for(var n=0;n0&&(this.undefStack[this.undefStack.length-1][e]=t)}else{var s=this.undefStack[this.undefStack.length-1];s&&!s.hasOwnProperty(e)&&(s[e]=this.current[e])}t==null?delete this.current[e]:this.current[e]=t}},VA=j4;w("\\noexpand",function(i){var e=i.popToken();return i.isExpandable(e.text)&&(e.noexpand=!0,e.treatAsRelax=!0),{tokens:[e],numArgs:0}});w("\\expandafter",function(i){var e=i.popToken();return i.expandOnce(!0),{tokens:[e],numArgs:0}});w("\\@firstoftwo",function(i){var e=i.consumeArgs(2);return{tokens:e[0],numArgs:0}});w("\\@secondoftwo",function(i){var e=i.consumeArgs(2);return{tokens:e[1],numArgs:0}});w("\\@ifnextchar",function(i){var e=i.consumeArgs(3);i.consumeSpaces();var t=i.future();return e[0].length===1&&e[0][0].text===t.text?{tokens:e[1],numArgs:0}:{tokens:e[2],numArgs:0}});w("\\@ifstar","\\@ifnextchar *{\\@firstoftwo{#1}}");w("\\TextOrMath",function(i){var e=i.consumeArgs(2);return i.mode==="text"?{tokens:e[0],numArgs:0}:{tokens:e[1],numArgs:0}});l4={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,e:14,E:14,f:15,F:15};w("\\char",function(i){var e=i.popToken(),t,r="";if(e.text==="'")t=8,e=i.popToken();else if(e.text==='"')t=16,e=i.popToken();else if(e.text==="`")if(e=i.popToken(),e.text[0]==="\\")r=e.text.charCodeAt(1);else{if(e.text==="EOF")throw new G("\\char` missing argument");r=e.text.charCodeAt(0)}else t=10;if(t){if(r=l4[e.text],r==null||r>=t)throw new G("Invalid base-"+t+" digit "+e.text);for(var n;(n=l4[i.future().text])!=null&&n{var n=i.consumeArg().tokens;if(n.length!==1)throw new G("\\newcommand's first argument must be a macro name");var s=n[0].text,a=i.isDefined(s);if(a&&!e)throw new G("\\newcommand{"+s+"} attempting to redefine "+(s+"; use \\renewcommand"));if(!a&&!t)throw new G("\\renewcommand{"+s+"} when command "+s+" does not yet exist; use \\newcommand");var o=0;if(n=i.consumeArg().tokens,n.length===1&&n[0].text==="["){for(var l="",h=i.expandNextToken();h.text!=="]"&&h.text!=="EOF";)l+=h.text,h=i.expandNextToken();if(!l.match(/^\s*[0-9]+\s*$/))throw new G("Invalid number of arguments: "+l);o=parseInt(l),n=i.consumeArg().tokens}return a&&r||i.macros.set(s,{tokens:n,numArgs:o}),""};w("\\newcommand",i=>O1(i,!1,!0,!1));w("\\renewcommand",i=>O1(i,!0,!1,!1));w("\\providecommand",i=>O1(i,!0,!0,!0));w("\\message",i=>{var e=i.consumeArgs(1)[0];return console.log(e.reverse().map(t=>t.text).join("")),""});w("\\errmessage",i=>{var e=i.consumeArgs(1)[0];return console.error(e.reverse().map(t=>t.text).join("")),""});w("\\show",i=>{var e=i.popToken(),t=e.text;return console.log(e,i.macros.get(t),xn[t],Oe.math[t],Oe.text[t]),""});w("\\bgroup","{");w("\\egroup","}");w("~","\\nobreakspace");w("\\lq","`");w("\\rq","'");w("\\aa","\\r a");w("\\AA","\\r A");w("\\textcopyright","\\html@mathml{\\textcircled{c}}{\\char`\xA9}");w("\\copyright","\\TextOrMath{\\textcopyright}{\\text{\\textcopyright}}");w("\\textregistered","\\html@mathml{\\textcircled{\\scriptsize R}}{\\char`\xAE}");w("\u212C","\\mathscr{B}");w("\u2130","\\mathscr{E}");w("\u2131","\\mathscr{F}");w("\u210B","\\mathscr{H}");w("\u2110","\\mathscr{I}");w("\u2112","\\mathscr{L}");w("\u2133","\\mathscr{M}");w("\u211B","\\mathscr{R}");w("\u212D","\\mathfrak{C}");w("\u210C","\\mathfrak{H}");w("\u2128","\\mathfrak{Z}");w("\\Bbbk","\\Bbb{k}");w("\xB7","\\cdotp");w("\\llap","\\mathllap{\\textrm{#1}}");w("\\rlap","\\mathrlap{\\textrm{#1}}");w("\\clap","\\mathclap{\\textrm{#1}}");w("\\mathstrut","\\vphantom{(}");w("\\underbar","\\underline{\\text{#1}}");w("\\not",'\\html@mathml{\\mathrel{\\mathrlap\\@not}}{\\char"338}');w("\\neq","\\html@mathml{\\mathrel{\\not=}}{\\mathrel{\\char`\u2260}}");w("\\ne","\\neq");w("\u2260","\\neq");w("\\notin","\\html@mathml{\\mathrel{{\\in}\\mathllap{/\\mskip1mu}}}{\\mathrel{\\char`\u2209}}");w("\u2209","\\notin");w("\u2258","\\html@mathml{\\mathrel{=\\kern{-1em}\\raisebox{0.4em}{$\\scriptsize\\frown$}}}{\\mathrel{\\char`\u2258}}");w("\u2259","\\html@mathml{\\stackrel{\\tiny\\wedge}{=}}{\\mathrel{\\char`\u2258}}");w("\u225A","\\html@mathml{\\stackrel{\\tiny\\vee}{=}}{\\mathrel{\\char`\u225A}}");w("\u225B","\\html@mathml{\\stackrel{\\scriptsize\\star}{=}}{\\mathrel{\\char`\u225B}}");w("\u225D","\\html@mathml{\\stackrel{\\tiny\\mathrm{def}}{=}}{\\mathrel{\\char`\u225D}}");w("\u225E","\\html@mathml{\\stackrel{\\tiny\\mathrm{m}}{=}}{\\mathrel{\\char`\u225E}}");w("\u225F","\\html@mathml{\\stackrel{\\tiny?}{=}}{\\mathrel{\\char`\u225F}}");w("\u27C2","\\perp");w("\u203C","\\mathclose{!\\mkern-0.8mu!}");w("\u220C","\\notni");w("\u231C","\\ulcorner");w("\u231D","\\urcorner");w("\u231E","\\llcorner");w("\u231F","\\lrcorner");w("\xA9","\\copyright");w("\xAE","\\textregistered");w("\uFE0F","\\textregistered");w("\\ulcorner",'\\html@mathml{\\@ulcorner}{\\mathop{\\char"231c}}');w("\\urcorner",'\\html@mathml{\\@urcorner}{\\mathop{\\char"231d}}');w("\\llcorner",'\\html@mathml{\\@llcorner}{\\mathop{\\char"231e}}');w("\\lrcorner",'\\html@mathml{\\@lrcorner}{\\mathop{\\char"231f}}');w("\\vdots","{\\varvdots\\rule{0pt}{15pt}}");w("\u22EE","\\vdots");w("\\varGamma","\\mathit{\\Gamma}");w("\\varDelta","\\mathit{\\Delta}");w("\\varTheta","\\mathit{\\Theta}");w("\\varLambda","\\mathit{\\Lambda}");w("\\varXi","\\mathit{\\Xi}");w("\\varPi","\\mathit{\\Pi}");w("\\varSigma","\\mathit{\\Sigma}");w("\\varUpsilon","\\mathit{\\Upsilon}");w("\\varPhi","\\mathit{\\Phi}");w("\\varPsi","\\mathit{\\Psi}");w("\\varOmega","\\mathit{\\Omega}");w("\\substack","\\begin{subarray}{c}#1\\end{subarray}");w("\\colon","\\nobreak\\mskip2mu\\mathpunct{}\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu\\relax");w("\\boxed","\\fbox{$\\displaystyle{#1}$}");w("\\iff","\\DOTSB\\;\\Longleftrightarrow\\;");w("\\implies","\\DOTSB\\;\\Longrightarrow\\;");w("\\impliedby","\\DOTSB\\;\\Longleftarrow\\;");w("\\dddot","{\\overset{\\raisebox{-0.1ex}{\\normalsize ...}}{#1}}");w("\\ddddot","{\\overset{\\raisebox{-0.1ex}{\\normalsize ....}}{#1}}");h4={",":"\\dotsc","\\not":"\\dotsb","+":"\\dotsb","=":"\\dotsb","<":"\\dotsb",">":"\\dotsb","-":"\\dotsb","*":"\\dotsb",":":"\\dotsb","\\DOTSB":"\\dotsb","\\coprod":"\\dotsb","\\bigvee":"\\dotsb","\\bigwedge":"\\dotsb","\\biguplus":"\\dotsb","\\bigcap":"\\dotsb","\\bigcup":"\\dotsb","\\prod":"\\dotsb","\\sum":"\\dotsb","\\bigotimes":"\\dotsb","\\bigoplus":"\\dotsb","\\bigodot":"\\dotsb","\\bigsqcup":"\\dotsb","\\And":"\\dotsb","\\longrightarrow":"\\dotsb","\\Longrightarrow":"\\dotsb","\\longleftarrow":"\\dotsb","\\Longleftarrow":"\\dotsb","\\longleftrightarrow":"\\dotsb","\\Longleftrightarrow":"\\dotsb","\\mapsto":"\\dotsb","\\longmapsto":"\\dotsb","\\hookrightarrow":"\\dotsb","\\doteq":"\\dotsb","\\mathbin":"\\dotsb","\\mathrel":"\\dotsb","\\relbar":"\\dotsb","\\Relbar":"\\dotsb","\\xrightarrow":"\\dotsb","\\xleftarrow":"\\dotsb","\\DOTSI":"\\dotsi","\\int":"\\dotsi","\\oint":"\\dotsi","\\iint":"\\dotsi","\\iiint":"\\dotsi","\\iiiint":"\\dotsi","\\idotsint":"\\dotsi","\\DOTSX":"\\dotsx"};w("\\dots",function(i){var e="\\dotso",t=i.expandAfterFuture().text;return t in h4?e=h4[t]:(t.slice(0,4)==="\\not"||t in Oe.math&&["bin","rel"].includes(Oe.math[t].group))&&(e="\\dotsb"),e});B1={")":!0,"]":!0,"\\rbrack":!0,"\\}":!0,"\\rbrace":!0,"\\rangle":!0,"\\rceil":!0,"\\rfloor":!0,"\\rgroup":!0,"\\rmoustache":!0,"\\right":!0,"\\bigr":!0,"\\biggr":!0,"\\Bigr":!0,"\\Biggr":!0,$:!0,";":!0,".":!0,",":!0};w("\\dotso",function(i){var e=i.future().text;return e in B1?"\\ldots\\,":"\\ldots"});w("\\dotsc",function(i){var e=i.future().text;return e in B1&&e!==","?"\\ldots\\,":"\\ldots"});w("\\cdots",function(i){var e=i.future().text;return e in B1?"\\@cdots\\,":"\\@cdots"});w("\\dotsb","\\cdots");w("\\dotsm","\\cdots");w("\\dotsi","\\!\\cdots");w("\\dotsx","\\ldots\\,");w("\\DOTSI","\\relax");w("\\DOTSB","\\relax");w("\\DOTSX","\\relax");w("\\tmspace","\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax");w("\\,","\\tmspace+{3mu}{.1667em}");w("\\thinspace","\\,");w("\\>","\\mskip{4mu}");w("\\:","\\tmspace+{4mu}{.2222em}");w("\\medspace","\\:");w("\\;","\\tmspace+{5mu}{.2777em}");w("\\thickspace","\\;");w("\\!","\\tmspace-{3mu}{.1667em}");w("\\negthinspace","\\!");w("\\negmedspace","\\tmspace-{4mu}{.2222em}");w("\\negthickspace","\\tmspace-{5mu}{.277em}");w("\\enspace","\\kern.5em ");w("\\enskip","\\hskip.5em\\relax");w("\\quad","\\hskip1em\\relax");w("\\qquad","\\hskip2em\\relax");w("\\tag","\\@ifstar\\tag@literal\\tag@paren");w("\\tag@paren","\\tag@literal{({#1})}");w("\\tag@literal",i=>{if(i.macros.get("\\df@tag"))throw new G("Multiple \\tag");return"\\gdef\\df@tag{\\text{#1}}"});w("\\bmod","\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}");w("\\pod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)");w("\\pmod","\\pod{{\\rm mod}\\mkern6mu#1}");w("\\mod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1");w("\\newline","\\\\\\relax");w("\\TeX","\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}");ig=V(dr["Main-Regular"][84][1]-.7*dr["Main-Regular"][65][1]);w("\\LaTeX","\\textrm{\\html@mathml{"+("L\\kern-.36em\\raisebox{"+ig+"}{\\scriptstyle A}")+"\\kern-.15em\\TeX}{LaTeX}}");w("\\KaTeX","\\textrm{\\html@mathml{"+("K\\kern-.17em\\raisebox{"+ig+"}{\\scriptstyle A}")+"\\kern-.15em\\TeX}{KaTeX}}");w("\\hspace","\\@ifstar\\@hspacer\\@hspace");w("\\@hspace","\\hskip #1\\relax");w("\\@hspacer","\\rule{0pt}{0pt}\\hskip #1\\relax");w("\\ordinarycolon",":");w("\\vcentcolon","\\mathrel{\\mathop\\ordinarycolon}");w("\\dblcolon",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char"2237}}');w("\\coloneqq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2254}}');w("\\Coloneqq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2237\\char"3d}}');w("\\coloneq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"3a\\char"2212}}');w("\\Coloneq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"2237\\char"2212}}');w("\\eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2255}}');w("\\Eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"3d\\char"2237}}');w("\\eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2239}}');w("\\Eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"2212\\char"2237}}');w("\\colonapprox",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"3a\\char"2248}}');w("\\Colonapprox",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"2237\\char"2248}}');w("\\colonsim",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"3a\\char"223c}}');w("\\Colonsim",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"2237\\char"223c}}');w("\u2237","\\dblcolon");w("\u2239","\\eqcolon");w("\u2254","\\coloneqq");w("\u2255","\\eqqcolon");w("\u2A74","\\Coloneqq");w("\\ratio","\\vcentcolon");w("\\coloncolon","\\dblcolon");w("\\colonequals","\\coloneqq");w("\\coloncolonequals","\\Coloneqq");w("\\equalscolon","\\eqqcolon");w("\\equalscoloncolon","\\Eqqcolon");w("\\colonminus","\\coloneq");w("\\coloncolonminus","\\Coloneq");w("\\minuscolon","\\eqcolon");w("\\minuscoloncolon","\\Eqcolon");w("\\coloncolonapprox","\\Colonapprox");w("\\coloncolonsim","\\Colonsim");w("\\simcolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}");w("\\simcoloncolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}");w("\\approxcolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}");w("\\approxcoloncolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}");w("\\notni","\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u220C}}");w("\\limsup","\\DOTSB\\operatorname*{lim\\,sup}");w("\\liminf","\\DOTSB\\operatorname*{lim\\,inf}");w("\\injlim","\\DOTSB\\operatorname*{inj\\,lim}");w("\\projlim","\\DOTSB\\operatorname*{proj\\,lim}");w("\\varlimsup","\\DOTSB\\operatorname*{\\overline{lim}}");w("\\varliminf","\\DOTSB\\operatorname*{\\underline{lim}}");w("\\varinjlim","\\DOTSB\\operatorname*{\\underrightarrow{lim}}");w("\\varprojlim","\\DOTSB\\operatorname*{\\underleftarrow{lim}}");w("\\gvertneqq","\\html@mathml{\\@gvertneqq}{\u2269}");w("\\lvertneqq","\\html@mathml{\\@lvertneqq}{\u2268}");w("\\ngeqq","\\html@mathml{\\@ngeqq}{\u2271}");w("\\ngeqslant","\\html@mathml{\\@ngeqslant}{\u2271}");w("\\nleqq","\\html@mathml{\\@nleqq}{\u2270}");w("\\nleqslant","\\html@mathml{\\@nleqslant}{\u2270}");w("\\nshortmid","\\html@mathml{\\@nshortmid}{\u2224}");w("\\nshortparallel","\\html@mathml{\\@nshortparallel}{\u2226}");w("\\nsubseteqq","\\html@mathml{\\@nsubseteqq}{\u2288}");w("\\nsupseteqq","\\html@mathml{\\@nsupseteqq}{\u2289}");w("\\varsubsetneq","\\html@mathml{\\@varsubsetneq}{\u228A}");w("\\varsubsetneqq","\\html@mathml{\\@varsubsetneqq}{\u2ACB}");w("\\varsupsetneq","\\html@mathml{\\@varsupsetneq}{\u228B}");w("\\varsupsetneqq","\\html@mathml{\\@varsupsetneqq}{\u2ACC}");w("\\imath","\\html@mathml{\\@imath}{\u0131}");w("\\jmath","\\html@mathml{\\@jmath}{\u0237}");w("\\llbracket","\\html@mathml{\\mathopen{[\\mkern-3.2mu[}}{\\mathopen{\\char`\u27E6}}");w("\\rrbracket","\\html@mathml{\\mathclose{]\\mkern-3.2mu]}}{\\mathclose{\\char`\u27E7}}");w("\u27E6","\\llbracket");w("\u27E7","\\rrbracket");w("\\lBrace","\\html@mathml{\\mathopen{\\{\\mkern-3.2mu[}}{\\mathopen{\\char`\u2983}}");w("\\rBrace","\\html@mathml{\\mathclose{]\\mkern-3.2mu\\}}}{\\mathclose{\\char`\u2984}}");w("\u2983","\\lBrace");w("\u2984","\\rBrace");w("\\minuso","\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`\u29B5}}");w("\u29B5","\\minuso");w("\\darr","\\downarrow");w("\\dArr","\\Downarrow");w("\\Darr","\\Downarrow");w("\\lang","\\langle");w("\\rang","\\rangle");w("\\uarr","\\uparrow");w("\\uArr","\\Uparrow");w("\\Uarr","\\Uparrow");w("\\N","\\mathbb{N}");w("\\R","\\mathbb{R}");w("\\Z","\\mathbb{Z}");w("\\alef","\\aleph");w("\\alefsym","\\aleph");w("\\Alpha","\\mathrm{A}");w("\\Beta","\\mathrm{B}");w("\\bull","\\bullet");w("\\Chi","\\mathrm{X}");w("\\clubs","\\clubsuit");w("\\cnums","\\mathbb{C}");w("\\Complex","\\mathbb{C}");w("\\Dagger","\\ddagger");w("\\diamonds","\\diamondsuit");w("\\empty","\\emptyset");w("\\Epsilon","\\mathrm{E}");w("\\Eta","\\mathrm{H}");w("\\exist","\\exists");w("\\harr","\\leftrightarrow");w("\\hArr","\\Leftrightarrow");w("\\Harr","\\Leftrightarrow");w("\\hearts","\\heartsuit");w("\\image","\\Im");w("\\infin","\\infty");w("\\Iota","\\mathrm{I}");w("\\isin","\\in");w("\\Kappa","\\mathrm{K}");w("\\larr","\\leftarrow");w("\\lArr","\\Leftarrow");w("\\Larr","\\Leftarrow");w("\\lrarr","\\leftrightarrow");w("\\lrArr","\\Leftrightarrow");w("\\Lrarr","\\Leftrightarrow");w("\\Mu","\\mathrm{M}");w("\\natnums","\\mathbb{N}");w("\\Nu","\\mathrm{N}");w("\\Omicron","\\mathrm{O}");w("\\plusmn","\\pm");w("\\rarr","\\rightarrow");w("\\rArr","\\Rightarrow");w("\\Rarr","\\Rightarrow");w("\\real","\\Re");w("\\reals","\\mathbb{R}");w("\\Reals","\\mathbb{R}");w("\\Rho","\\mathrm{P}");w("\\sdot","\\cdot");w("\\sect","\\S");w("\\spades","\\spadesuit");w("\\sub","\\subset");w("\\sube","\\subseteq");w("\\supe","\\supseteq");w("\\Tau","\\mathrm{T}");w("\\thetasym","\\vartheta");w("\\weierp","\\wp");w("\\Zeta","\\mathrm{Z}");w("\\argmin","\\DOTSB\\operatorname*{arg\\,min}");w("\\argmax","\\DOTSB\\operatorname*{arg\\,max}");w("\\plim","\\DOTSB\\mathop{\\operatorname{plim}}\\limits");w("\\bra","\\mathinner{\\langle{#1}|}");w("\\ket","\\mathinner{|{#1}\\rangle}");w("\\braket","\\mathinner{\\langle{#1}\\rangle}");w("\\Bra","\\left\\langle#1\\right|");w("\\Ket","\\left|#1\\right\\rangle");rg=i=>e=>{var t=e.consumeArg().tokens,r=e.consumeArg().tokens,n=e.consumeArg().tokens,s=e.consumeArg().tokens,a=e.macros.get("|"),o=e.macros.get("\\|");e.macros.beginGroup();var l=c=>f=>{i&&(f.macros.set("|",a),n.length&&f.macros.set("\\|",o));var m=c;if(!c&&n.length){var g=f.future();g.text==="|"&&(f.popToken(),m=!0)}return{tokens:m?n:r,numArgs:0}};e.macros.set("|",l(!1)),n.length&&e.macros.set("\\|",l(!0));var h=e.consumeArg().tokens,d=e.expandTokens([...s,...h,...t]);return e.macros.endGroup(),{tokens:d.reverse(),numArgs:0}};w("\\bra@ket",rg(!1));w("\\bra@set",rg(!0));w("\\Braket","\\bra@ket{\\left\\langle}{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}");w("\\Set","\\bra@set{\\left\\{\\:}{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}");w("\\set","\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}");w("\\angln","{\\angl n}");w("\\blue","\\textcolor{##6495ed}{#1}");w("\\orange","\\textcolor{##ffa500}{#1}");w("\\pink","\\textcolor{##ff00af}{#1}");w("\\red","\\textcolor{##df0030}{#1}");w("\\green","\\textcolor{##28ae7b}{#1}");w("\\gray","\\textcolor{gray}{#1}");w("\\purple","\\textcolor{##9d38bd}{#1}");w("\\blueA","\\textcolor{##ccfaff}{#1}");w("\\blueB","\\textcolor{##80f6ff}{#1}");w("\\blueC","\\textcolor{##63d9ea}{#1}");w("\\blueD","\\textcolor{##11accd}{#1}");w("\\blueE","\\textcolor{##0c7f99}{#1}");w("\\tealA","\\textcolor{##94fff5}{#1}");w("\\tealB","\\textcolor{##26edd5}{#1}");w("\\tealC","\\textcolor{##01d1c1}{#1}");w("\\tealD","\\textcolor{##01a995}{#1}");w("\\tealE","\\textcolor{##208170}{#1}");w("\\greenA","\\textcolor{##b6ffb0}{#1}");w("\\greenB","\\textcolor{##8af281}{#1}");w("\\greenC","\\textcolor{##74cf70}{#1}");w("\\greenD","\\textcolor{##1fab54}{#1}");w("\\greenE","\\textcolor{##0d923f}{#1}");w("\\goldA","\\textcolor{##ffd0a9}{#1}");w("\\goldB","\\textcolor{##ffbb71}{#1}");w("\\goldC","\\textcolor{##ff9c39}{#1}");w("\\goldD","\\textcolor{##e07d10}{#1}");w("\\goldE","\\textcolor{##a75a05}{#1}");w("\\redA","\\textcolor{##fca9a9}{#1}");w("\\redB","\\textcolor{##ff8482}{#1}");w("\\redC","\\textcolor{##f9685d}{#1}");w("\\redD","\\textcolor{##e84d39}{#1}");w("\\redE","\\textcolor{##bc2612}{#1}");w("\\maroonA","\\textcolor{##ffbde0}{#1}");w("\\maroonB","\\textcolor{##ff92c6}{#1}");w("\\maroonC","\\textcolor{##ed5fa6}{#1}");w("\\maroonD","\\textcolor{##ca337c}{#1}");w("\\maroonE","\\textcolor{##9e034e}{#1}");w("\\purpleA","\\textcolor{##ddd7ff}{#1}");w("\\purpleB","\\textcolor{##c6b9fc}{#1}");w("\\purpleC","\\textcolor{##aa87ff}{#1}");w("\\purpleD","\\textcolor{##7854ab}{#1}");w("\\purpleE","\\textcolor{##543b78}{#1}");w("\\mintA","\\textcolor{##f5f9e8}{#1}");w("\\mintB","\\textcolor{##edf2df}{#1}");w("\\mintC","\\textcolor{##e0e5cc}{#1}");w("\\grayA","\\textcolor{##f6f7f7}{#1}");w("\\grayB","\\textcolor{##f0f1f2}{#1}");w("\\grayC","\\textcolor{##e3e5e6}{#1}");w("\\grayD","\\textcolor{##d6d8da}{#1}");w("\\grayE","\\textcolor{##babec2}{#1}");w("\\grayF","\\textcolor{##888d93}{#1}");w("\\grayG","\\textcolor{##626569}{#1}");w("\\grayH","\\textcolor{##3b3e40}{#1}");w("\\grayI","\\textcolor{##21242c}{#1}");w("\\kaBlue","\\textcolor{##314453}{#1}");w("\\kaGreen","\\textcolor{##71B307}{#1}");ng={"^":!0,_:!0,"\\limits":!0,"\\nolimits":!0},w1=class{constructor(e,t,r){this.settings=void 0,this.expansionCount=void 0,this.lexer=void 0,this.macros=void 0,this.stack=void 0,this.mode=void 0,this.settings=t,this.expansionCount=0,this.feed(e),this.macros=new b1(VA,t.macros),this.mode=r,this.stack=[]}feed(e){this.lexer=new kh(e,this.settings)}switchMode(e){this.mode=e}beginGroup(){this.macros.beginGroup()}endGroup(){this.macros.endGroup()}endGroups(){this.macros.endGroups()}future(){return this.stack.length===0&&this.pushToken(this.lexer.lex()),this.stack[this.stack.length-1]}popToken(){return this.future(),this.stack.pop()}pushToken(e){this.stack.push(e)}pushTokens(e){this.stack.push(...e)}scanArgument(e){var t,r,n;if(e){if(this.consumeSpaces(),this.future().text!=="[")return null;t=this.popToken(),{tokens:n,end:r}=this.consumeArg(["]"])}else({tokens:n,start:t,end:r}=this.consumeArg());return this.pushToken(new ui("EOF",r.loc)),this.pushTokens(n),new ui("",Jt.range(t,r))}consumeSpaces(){for(;;){var e=this.future();if(e.text===" ")this.stack.pop();else break}}consumeArg(e){var t=[],r=e&&e.length>0;r||this.consumeSpaces();var n=this.future(),s,a=0,o=0;do{if(s=this.popToken(),t.push(s),s.text==="{")++a;else if(s.text==="}"){if(--a,a===-1)throw new G("Extra }",s)}else if(s.text==="EOF")throw new G("Unexpected end of input in a macro argument, expected '"+(e&&r?e[o]:"}")+"'",s);if(e&&r)if((a===0||a===1&&e[o]==="{")&&s.text===e[o]){if(++o,o===e.length){t.splice(-o,o);break}}else o=0}while(a!==0||r);return n.text==="{"&&t[t.length-1].text==="}"&&(t.pop(),t.shift()),t.reverse(),{tokens:t,start:n,end:s}}consumeArgs(e,t){if(t){if(t.length!==e+1)throw new G("The length of delimiters doesn't match the number of args!");for(var r=t[0],n=0;nthis.settings.maxExpand)throw new G("Too many expansions: infinite loop or need to increase maxExpand setting")}expandOnce(e){var t=this.popToken(),r=t.text,n=t.noexpand?null:this._getExpansion(r);if(n==null||e&&n.unexpandable){if(e&&n==null&&r[0]==="\\"&&!this.isDefined(r))throw new G("Undefined control sequence: "+r);return this.pushToken(t),!1}this.countExpansion(1);var s=n.tokens,a=this.consumeArgs(n.numArgs,n.delimiters);if(n.numArgs){s=s.slice();for(var o=s.length-1;o>=0;--o){var l=s[o];if(l.text==="#"){if(o===0)throw new G("Incomplete placeholder at end of macro body",l);if(l=s[--o],l.text==="#")s.splice(o+1,1);else if(/^[1-9]$/.test(l.text))s.splice(o,2,...a[+l.text-1]);else throw new G("Not a valid argument number",l)}}}return this.pushTokens(s),s.length}expandAfterFuture(){return this.expandOnce(),this.future()}expandNextToken(){for(;;)if(this.expandOnce()===!1){var e=this.stack.pop();return e.treatAsRelax&&(e.text="\\relax"),e}throw new Error}expandMacro(e){return this.macros.has(e)?this.expandTokens([new ui(e)]):void 0}expandTokens(e){var t=[],r=this.stack.length;for(this.pushTokens(e);this.stack.length>r;)if(this.expandOnce(!0)===!1){var n=this.stack.pop();n.treatAsRelax&&(n.noexpand=!1,n.treatAsRelax=!1),t.push(n)}return this.countExpansion(t.length),t}expandMacroAsText(e){var t=this.expandMacro(e);return t&&t.map(r=>r.text).join("")}_getExpansion(e){var t=this.macros.get(e);if(t==null)return t;if(e.length===1){var r=this.lexer.catcodes[e];if(r!=null&&r!==13)return}var n=typeof t=="function"?t(this):t;if(typeof n=="string"){var s=0;if(n.indexOf("#")!==-1)for(var a=n.replace(/##/g,"");a.indexOf("#"+(s+1))!==-1;)++s;for(var o=new kh(n,this.settings),l=[],h=o.lex();h.text!=="EOF";)l.push(h),h=o.lex();l.reverse();var d={tokens:l,numArgs:s};return d}return n}isDefined(e){return this.macros.has(e)||xn.hasOwnProperty(e)||Oe.math.hasOwnProperty(e)||Oe.text.hasOwnProperty(e)||ng.hasOwnProperty(e)}isExpandable(e){var t=this.macros.get(e);return t!=null?typeof t=="string"||typeof t=="function"||!t.unexpandable:xn.hasOwnProperty(e)&&!xn[e].primitive}},d4=/^[₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ]/,vh=Object.freeze({"\u208A":"+","\u208B":"-","\u208C":"=","\u208D":"(","\u208E":")","\u2080":"0","\u2081":"1","\u2082":"2","\u2083":"3","\u2084":"4","\u2085":"5","\u2086":"6","\u2087":"7","\u2088":"8","\u2089":"9","\u2090":"a","\u2091":"e","\u2095":"h","\u1D62":"i","\u2C7C":"j","\u2096":"k","\u2097":"l","\u2098":"m","\u2099":"n","\u2092":"o","\u209A":"p","\u1D63":"r","\u209B":"s","\u209C":"t","\u1D64":"u","\u1D65":"v","\u2093":"x","\u1D66":"\u03B2","\u1D67":"\u03B3","\u1D68":"\u03C1","\u1D69":"\u03D5","\u1D6A":"\u03C7","\u207A":"+","\u207B":"-","\u207C":"=","\u207D":"(","\u207E":")","\u2070":"0","\xB9":"1","\xB2":"2","\xB3":"3","\u2074":"4","\u2075":"5","\u2076":"6","\u2077":"7","\u2078":"8","\u2079":"9","\u1D2C":"A","\u1D2E":"B","\u1D30":"D","\u1D31":"E","\u1D33":"G","\u1D34":"H","\u1D35":"I","\u1D36":"J","\u1D37":"K","\u1D38":"L","\u1D39":"M","\u1D3A":"N","\u1D3C":"O","\u1D3E":"P","\u1D3F":"R","\u1D40":"T","\u1D41":"U","\u2C7D":"V","\u1D42":"W","\u1D43":"a","\u1D47":"b","\u1D9C":"c","\u1D48":"d","\u1D49":"e","\u1DA0":"f","\u1D4D":"g",\u02B0:"h","\u2071":"i",\u02B2:"j","\u1D4F":"k",\u02E1:"l","\u1D50":"m",\u207F:"n","\u1D52":"o","\u1D56":"p",\u02B3:"r",\u02E2:"s","\u1D57":"t","\u1D58":"u","\u1D5B":"v",\u02B7:"w",\u02E3:"x",\u02B8:"y","\u1DBB":"z","\u1D5D":"\u03B2","\u1D5E":"\u03B3","\u1D5F":"\u03B4","\u1D60":"\u03D5","\u1D61":"\u03C7","\u1DBF":"\u03B8"}),h1={"\u0301":{text:"\\'",math:"\\acute"},"\u0300":{text:"\\`",math:"\\grave"},"\u0308":{text:'\\"',math:"\\ddot"},"\u0303":{text:"\\~",math:"\\tilde"},"\u0304":{text:"\\=",math:"\\bar"},"\u0306":{text:"\\u",math:"\\breve"},"\u030C":{text:"\\v",math:"\\check"},"\u0302":{text:"\\^",math:"\\hat"},"\u0307":{text:"\\.",math:"\\dot"},"\u030A":{text:"\\r",math:"\\mathring"},"\u030B":{text:"\\H"},"\u0327":{text:"\\c"}},c4={\u00E1:"a\u0301",\u00E0:"a\u0300",\u00E4:"a\u0308",\u01DF:"a\u0308\u0304",\u00E3:"a\u0303",\u0101:"a\u0304",\u0103:"a\u0306",\u1EAF:"a\u0306\u0301",\u1EB1:"a\u0306\u0300",\u1EB5:"a\u0306\u0303",\u01CE:"a\u030C",\u00E2:"a\u0302",\u1EA5:"a\u0302\u0301",\u1EA7:"a\u0302\u0300",\u1EAB:"a\u0302\u0303",\u0227:"a\u0307",\u01E1:"a\u0307\u0304",\u00E5:"a\u030A",\u01FB:"a\u030A\u0301",\u1E03:"b\u0307",\u0107:"c\u0301",\u1E09:"c\u0327\u0301",\u010D:"c\u030C",\u0109:"c\u0302",\u010B:"c\u0307",\u00E7:"c\u0327",\u010F:"d\u030C",\u1E0B:"d\u0307",\u1E11:"d\u0327",\u00E9:"e\u0301",\u00E8:"e\u0300",\u00EB:"e\u0308",\u1EBD:"e\u0303",\u0113:"e\u0304",\u1E17:"e\u0304\u0301",\u1E15:"e\u0304\u0300",\u0115:"e\u0306",\u1E1D:"e\u0327\u0306",\u011B:"e\u030C",\u00EA:"e\u0302",\u1EBF:"e\u0302\u0301",\u1EC1:"e\u0302\u0300",\u1EC5:"e\u0302\u0303",\u0117:"e\u0307",\u0229:"e\u0327",\u1E1F:"f\u0307",\u01F5:"g\u0301",\u1E21:"g\u0304",\u011F:"g\u0306",\u01E7:"g\u030C",\u011D:"g\u0302",\u0121:"g\u0307",\u0123:"g\u0327",\u1E27:"h\u0308",\u021F:"h\u030C",\u0125:"h\u0302",\u1E23:"h\u0307",\u1E29:"h\u0327",\u00ED:"i\u0301",\u00EC:"i\u0300",\u00EF:"i\u0308",\u1E2F:"i\u0308\u0301",\u0129:"i\u0303",\u012B:"i\u0304",\u012D:"i\u0306",\u01D0:"i\u030C",\u00EE:"i\u0302",\u01F0:"j\u030C",\u0135:"j\u0302",\u1E31:"k\u0301",\u01E9:"k\u030C",\u0137:"k\u0327",\u013A:"l\u0301",\u013E:"l\u030C",\u013C:"l\u0327",\u1E3F:"m\u0301",\u1E41:"m\u0307",\u0144:"n\u0301",\u01F9:"n\u0300",\u00F1:"n\u0303",\u0148:"n\u030C",\u1E45:"n\u0307",\u0146:"n\u0327",\u00F3:"o\u0301",\u00F2:"o\u0300",\u00F6:"o\u0308",\u022B:"o\u0308\u0304",\u00F5:"o\u0303",\u1E4D:"o\u0303\u0301",\u1E4F:"o\u0303\u0308",\u022D:"o\u0303\u0304",\u014D:"o\u0304",\u1E53:"o\u0304\u0301",\u1E51:"o\u0304\u0300",\u014F:"o\u0306",\u01D2:"o\u030C",\u00F4:"o\u0302",\u1ED1:"o\u0302\u0301",\u1ED3:"o\u0302\u0300",\u1ED7:"o\u0302\u0303",\u022F:"o\u0307",\u0231:"o\u0307\u0304",\u0151:"o\u030B",\u1E55:"p\u0301",\u1E57:"p\u0307",\u0155:"r\u0301",\u0159:"r\u030C",\u1E59:"r\u0307",\u0157:"r\u0327",\u015B:"s\u0301",\u1E65:"s\u0301\u0307",\u0161:"s\u030C",\u1E67:"s\u030C\u0307",\u015D:"s\u0302",\u1E61:"s\u0307",\u015F:"s\u0327",\u1E97:"t\u0308",\u0165:"t\u030C",\u1E6B:"t\u0307",\u0163:"t\u0327",\u00FA:"u\u0301",\u00F9:"u\u0300",\u00FC:"u\u0308",\u01D8:"u\u0308\u0301",\u01DC:"u\u0308\u0300",\u01D6:"u\u0308\u0304",\u01DA:"u\u0308\u030C",\u0169:"u\u0303",\u1E79:"u\u0303\u0301",\u016B:"u\u0304",\u1E7B:"u\u0304\u0308",\u016D:"u\u0306",\u01D4:"u\u030C",\u00FB:"u\u0302",\u016F:"u\u030A",\u0171:"u\u030B",\u1E7D:"v\u0303",\u1E83:"w\u0301",\u1E81:"w\u0300",\u1E85:"w\u0308",\u0175:"w\u0302",\u1E87:"w\u0307",\u1E98:"w\u030A",\u1E8D:"x\u0308",\u1E8B:"x\u0307",\u00FD:"y\u0301",\u1EF3:"y\u0300",\u00FF:"y\u0308",\u1EF9:"y\u0303",\u0233:"y\u0304",\u0177:"y\u0302",\u1E8F:"y\u0307",\u1E99:"y\u030A",\u017A:"z\u0301",\u017E:"z\u030C",\u1E91:"z\u0302",\u017C:"z\u0307",\u00C1:"A\u0301",\u00C0:"A\u0300",\u00C4:"A\u0308",\u01DE:"A\u0308\u0304",\u00C3:"A\u0303",\u0100:"A\u0304",\u0102:"A\u0306",\u1EAE:"A\u0306\u0301",\u1EB0:"A\u0306\u0300",\u1EB4:"A\u0306\u0303",\u01CD:"A\u030C",\u00C2:"A\u0302",\u1EA4:"A\u0302\u0301",\u1EA6:"A\u0302\u0300",\u1EAA:"A\u0302\u0303",\u0226:"A\u0307",\u01E0:"A\u0307\u0304",\u00C5:"A\u030A",\u01FA:"A\u030A\u0301",\u1E02:"B\u0307",\u0106:"C\u0301",\u1E08:"C\u0327\u0301",\u010C:"C\u030C",\u0108:"C\u0302",\u010A:"C\u0307",\u00C7:"C\u0327",\u010E:"D\u030C",\u1E0A:"D\u0307",\u1E10:"D\u0327",\u00C9:"E\u0301",\u00C8:"E\u0300",\u00CB:"E\u0308",\u1EBC:"E\u0303",\u0112:"E\u0304",\u1E16:"E\u0304\u0301",\u1E14:"E\u0304\u0300",\u0114:"E\u0306",\u1E1C:"E\u0327\u0306",\u011A:"E\u030C",\u00CA:"E\u0302",\u1EBE:"E\u0302\u0301",\u1EC0:"E\u0302\u0300",\u1EC4:"E\u0302\u0303",\u0116:"E\u0307",\u0228:"E\u0327",\u1E1E:"F\u0307",\u01F4:"G\u0301",\u1E20:"G\u0304",\u011E:"G\u0306",\u01E6:"G\u030C",\u011C:"G\u0302",\u0120:"G\u0307",\u0122:"G\u0327",\u1E26:"H\u0308",\u021E:"H\u030C",\u0124:"H\u0302",\u1E22:"H\u0307",\u1E28:"H\u0327",\u00CD:"I\u0301",\u00CC:"I\u0300",\u00CF:"I\u0308",\u1E2E:"I\u0308\u0301",\u0128:"I\u0303",\u012A:"I\u0304",\u012C:"I\u0306",\u01CF:"I\u030C",\u00CE:"I\u0302",\u0130:"I\u0307",\u0134:"J\u0302",\u1E30:"K\u0301",\u01E8:"K\u030C",\u0136:"K\u0327",\u0139:"L\u0301",\u013D:"L\u030C",\u013B:"L\u0327",\u1E3E:"M\u0301",\u1E40:"M\u0307",\u0143:"N\u0301",\u01F8:"N\u0300",\u00D1:"N\u0303",\u0147:"N\u030C",\u1E44:"N\u0307",\u0145:"N\u0327",\u00D3:"O\u0301",\u00D2:"O\u0300",\u00D6:"O\u0308",\u022A:"O\u0308\u0304",\u00D5:"O\u0303",\u1E4C:"O\u0303\u0301",\u1E4E:"O\u0303\u0308",\u022C:"O\u0303\u0304",\u014C:"O\u0304",\u1E52:"O\u0304\u0301",\u1E50:"O\u0304\u0300",\u014E:"O\u0306",\u01D1:"O\u030C",\u00D4:"O\u0302",\u1ED0:"O\u0302\u0301",\u1ED2:"O\u0302\u0300",\u1ED6:"O\u0302\u0303",\u022E:"O\u0307",\u0230:"O\u0307\u0304",\u0150:"O\u030B",\u1E54:"P\u0301",\u1E56:"P\u0307",\u0154:"R\u0301",\u0158:"R\u030C",\u1E58:"R\u0307",\u0156:"R\u0327",\u015A:"S\u0301",\u1E64:"S\u0301\u0307",\u0160:"S\u030C",\u1E66:"S\u030C\u0307",\u015C:"S\u0302",\u1E60:"S\u0307",\u015E:"S\u0327",\u0164:"T\u030C",\u1E6A:"T\u0307",\u0162:"T\u0327",\u00DA:"U\u0301",\u00D9:"U\u0300",\u00DC:"U\u0308",\u01D7:"U\u0308\u0301",\u01DB:"U\u0308\u0300",\u01D5:"U\u0308\u0304",\u01D9:"U\u0308\u030C",\u0168:"U\u0303",\u1E78:"U\u0303\u0301",\u016A:"U\u0304",\u1E7A:"U\u0304\u0308",\u016C:"U\u0306",\u01D3:"U\u030C",\u00DB:"U\u0302",\u016E:"U\u030A",\u0170:"U\u030B",\u1E7C:"V\u0303",\u1E82:"W\u0301",\u1E80:"W\u0300",\u1E84:"W\u0308",\u0174:"W\u0302",\u1E86:"W\u0307",\u1E8C:"X\u0308",\u1E8A:"X\u0307",\u00DD:"Y\u0301",\u1EF2:"Y\u0300",\u0178:"Y\u0308",\u1EF8:"Y\u0303",\u0232:"Y\u0304",\u0176:"Y\u0302",\u1E8E:"Y\u0307",\u0179:"Z\u0301",\u017D:"Z\u030C",\u1E90:"Z\u0302",\u017B:"Z\u0307",\u03AC:"\u03B1\u0301",\u1F70:"\u03B1\u0300",\u1FB1:"\u03B1\u0304",\u1FB0:"\u03B1\u0306",\u03AD:"\u03B5\u0301",\u1F72:"\u03B5\u0300",\u03AE:"\u03B7\u0301",\u1F74:"\u03B7\u0300",\u03AF:"\u03B9\u0301",\u1F76:"\u03B9\u0300",\u03CA:"\u03B9\u0308",\u0390:"\u03B9\u0308\u0301",\u1FD2:"\u03B9\u0308\u0300",\u1FD1:"\u03B9\u0304",\u1FD0:"\u03B9\u0306",\u03CC:"\u03BF\u0301",\u1F78:"\u03BF\u0300",\u03CD:"\u03C5\u0301",\u1F7A:"\u03C5\u0300",\u03CB:"\u03C5\u0308",\u03B0:"\u03C5\u0308\u0301",\u1FE2:"\u03C5\u0308\u0300",\u1FE1:"\u03C5\u0304",\u1FE0:"\u03C5\u0306",\u03CE:"\u03C9\u0301",\u1F7C:"\u03C9\u0300",\u038E:"\u03A5\u0301",\u1FEA:"\u03A5\u0300",\u03AB:"\u03A5\u0308",\u1FE9:"\u03A5\u0304",\u1FE8:"\u03A5\u0306",\u038F:"\u03A9\u0301",\u1FFA:"\u03A9\u0300"},Ch=class i{constructor(e,t){this.mode=void 0,this.gullet=void 0,this.settings=void 0,this.leftrightDepth=void 0,this.nextToken=void 0,this.mode="math",this.gullet=new w1(e,t,this.mode),this.settings=t,this.leftrightDepth=0}expect(e,t){if(t===void 0&&(t=!0),this.fetch().text!==e)throw new G("Expected '"+e+"', got '"+this.fetch().text+"'",this.fetch());t&&this.consume()}consume(){this.nextToken=null}fetch(){return this.nextToken==null&&(this.nextToken=this.gullet.expandNextToken()),this.nextToken}switchMode(e){this.mode=e,this.gullet.switchMode(e)}parse(){this.settings.globalGroup||this.gullet.beginGroup(),this.settings.colorIsTextColor&&this.gullet.macros.set("\\color","\\textcolor");try{var e=this.parseExpression(!1);return this.expect("EOF"),this.settings.globalGroup||this.gullet.endGroup(),e}finally{this.gullet.endGroups()}}subparse(e){var t=this.nextToken;this.consume(),this.gullet.pushToken(new ui("}")),this.gullet.pushTokens(e);var r=this.parseExpression(!1);return this.expect("}"),this.nextToken=t,r}parseExpression(e,t){for(var r=[];;){this.mode==="math"&&this.consumeSpaces();var n=this.fetch();if(i.endOfExpression.indexOf(n.text)!==-1||t&&n.text===t||e&&xn[n.text]&&xn[n.text].infix)break;var s=this.parseAtom(t);if(s){if(s.type==="internal")continue}else break;r.push(s)}return this.mode==="text"&&this.formLigatures(r),this.handleInfixNodes(r)}handleInfixNodes(e){for(var t=-1,r,n=0;n=0&&this.settings.reportNonstrict("unicodeTextInMathMode",'Latin-1/Unicode text character "'+t[0]+'" used in math mode',e);var o=Oe[this.mode][t].group,l=Jt.range(e),h;if(BS.hasOwnProperty(o)){var d=o;h={type:"atom",mode:this.mode,family:d,loc:l,text:t}}else h={type:o,mode:this.mode,loc:l,text:t};a=h}else if(t.charCodeAt(0)>=128)this.settings.strict&&(f4(t.charCodeAt(0))?this.mode==="math"&&this.settings.reportNonstrict("unicodeTextInMathMode",'Unicode text character "'+t[0]+'" used in math mode',e):this.settings.reportNonstrict("unknownSymbol",'Unrecognized Unicode character "'+t[0]+'"'+(" ("+t.charCodeAt(0)+")"),e)),a={type:"textord",mode:"text",loc:Jt.range(e),text:t};else return null;if(this.consume(),s)for(var c=0;c{QA=typeof global=="object"&&global&&global.Object===Object&&global,Bh=QA});var JA,ek,lt,Ki=T(()=>{q1();JA=typeof self=="object"&&self&&self.Object===Object&&self,ek=Bh||JA||Function("return this")(),lt=ek});var tk,_i,nl=T(()=>{Ki();tk=lt.Symbol,_i=tk});function nk(i){var e=ik.call(i,sl),t=i[sl];try{i[sl]=void 0;var r=!0}catch{}var n=rk.call(i);return r&&(e?i[sl]=t:delete i[sl]),n}var lg,ik,rk,sl,hg,dg=T(()=>{nl();lg=Object.prototype,ik=lg.hasOwnProperty,rk=lg.toString,sl=_i?_i.toStringTag:void 0;hg=nk});function ok(i){return ak.call(i)}var sk,ak,cg,ug=T(()=>{sk=Object.prototype,ak=sk.toString;cg=ok});function dk(i){return i==null?i===void 0?hk:lk:fg&&fg in Object(i)?hg(i):cg(i)}var lk,hk,fg,Zi,ga=T(()=>{nl();dg();ug();lk="[object Null]",hk="[object Undefined]",fg=_i?_i.toStringTag:void 0;Zi=dk});function ck(i){return i!=null&&typeof i=="object"}var vt,Or=T(()=>{vt=ck});var uk,Li,xa=T(()=>{uk=Array.isArray,Li=uk});function fk(i){var e=typeof i;return i!=null&&(e=="object"||e=="function")}var Nt,Br=T(()=>{Nt=fk});function mk(i){return i}var Ph,H1=T(()=>{Ph=mk});function vk(i){if(!Nt(i))return!1;var e=Zi(i);return e==gk||e==xk||e==pk||e==yk}var pk,gk,xk,yk,ya,Fh=T(()=>{ga();Br();pk="[object AsyncFunction]",gk="[object Function]",xk="[object GeneratorFunction]",yk="[object Proxy]";ya=vk});var bk,qh,mg=T(()=>{Ki();bk=lt["__core-js_shared__"],qh=bk});function wk(i){return!!pg&&pg in i}var pg,gg,xg=T(()=>{mg();pg=(function(){var i=/[^.]+$/.exec(qh&&qh.keys&&qh.keys.IE_PROTO||"");return i?"Symbol(src)_1."+i:""})();gg=wk});function Nk(i){if(i!=null){try{return Tk.call(i)}catch{}try{return i+""}catch{}}return""}var Mk,Tk,Pr,U1=T(()=>{Mk=Function.prototype,Tk=Mk.toString;Pr=Nk});function Ik(i){if(!Nt(i)||gg(i))return!1;var e=ya(i)?Lk:Sk;return e.test(Pr(i))}var Ek,Sk,Ak,kk,Ck,_k,Lk,yg,vg=T(()=>{Fh();xg();Br();U1();Ek=/[\\^$.*+?()[\]{}|]/g,Sk=/^\[object .+?Constructor\]$/,Ak=Function.prototype,kk=Object.prototype,Ck=Ak.toString,_k=kk.hasOwnProperty,Lk=RegExp("^"+Ck.call(_k).replace(Ek,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");yg=Ik});function zk(i,e){return i?.[e]}var bg,wg=T(()=>{bg=zk});function Rk(i,e){var t=bg(i,e);return yg(t)?t:void 0}var ii,wn=T(()=>{vg();wg();ii=Rk});var Dk,Hh,Mg=T(()=>{wn();Ki();Dk=ii(lt,"WeakMap"),Hh=Dk});var Tg,Ok,Ng,Eg=T(()=>{Br();Tg=Object.create,Ok=(function(){function i(){}return function(e){if(!Nt(e))return{};if(Tg)return Tg(e);i.prototype=e;var t=new i;return i.prototype=void 0,t}})(),Ng=Ok});function Bk(i,e,t){switch(t.length){case 0:return i.call(e);case 1:return i.call(e,t[0]);case 2:return i.call(e,t[0],t[1]);case 3:return i.call(e,t[0],t[1],t[2])}return i.apply(e,t)}var Sg,Ag=T(()=>{Sg=Bk});function Pk(i,e){var t=-1,r=i.length;for(e||(e=Array(r));++t{Uh=Pk});function Uk(i){var e=0,t=0;return function(){var r=Hk(),n=qk-(r-t);if(t=r,n>0){if(++e>=Fk)return arguments[0]}else e=0;return i.apply(void 0,arguments)}}var Fk,qk,Hk,kg,Cg=T(()=>{Fk=800,qk=16,Hk=Date.now;kg=Uk});function $k(i){return function(){return i}}var _g,Lg=T(()=>{_g=$k});var jk,va,j1=T(()=>{wn();jk=(function(){try{var i=ii(Object,"defineProperty");return i({},"",{}),i}catch{}})(),va=jk});var Gk,Ig,zg=T(()=>{Lg();j1();H1();Gk=va?function(i,e){return va(i,"toString",{configurable:!0,enumerable:!1,value:_g(e),writable:!0})}:Ph,Ig=Gk});var Vk,Rg,Dg=T(()=>{zg();Cg();Vk=kg(Ig),Rg=Vk});function Wk(i,e){for(var t=-1,r=i==null?0:i.length;++t{Og=Wk});function Kk(i,e){var t=typeof i;return e=e??Yk,!!e&&(t=="number"||t!="symbol"&&Xk.test(i))&&i>-1&&i%1==0&&i{Yk=9007199254740991,Xk=/^(?:0|[1-9]\d*)$/;$h=Kk});function Zk(i,e,t){e=="__proto__"&&va?va(i,e,{configurable:!0,enumerable:!0,value:t,writable:!0}):i[e]=t}var ba,jh=T(()=>{j1();ba=Zk});function Qk(i,e){return i===e||i!==i&&e!==e}var pr,wa=T(()=>{pr=Qk});function tC(i,e,t){var r=i[e];(!(eC.call(i,e)&&pr(r,t))||t===void 0&&!(e in i))&&ba(i,e,t)}var Jk,eC,Gh,V1=T(()=>{jh();wa();Jk=Object.prototype,eC=Jk.hasOwnProperty;Gh=tC});function iC(i,e,t,r){var n=!t;t||(t={});for(var s=-1,a=e.length;++s{V1();jh();gr=iC});function rC(i,e,t){return e=Pg(e===void 0?i.length-1:e,0),function(){for(var r=arguments,n=-1,s=Pg(r.length-e,0),a=Array(s);++n{Ag();Pg=Math.max;Fg=rC});function nC(i,e){return Rg(Fg(i,e,Ph),i+"")}var Hg,Ug=T(()=>{H1();qg();Dg();Hg=nC});function aC(i){return typeof i=="number"&&i>-1&&i%1==0&&i<=sC}var sC,Vh,W1=T(()=>{sC=9007199254740991;Vh=aC});function oC(i){return i!=null&&Vh(i.length)&&!ya(i)}var Mn,al=T(()=>{Fh();W1();Mn=oC});function lC(i,e,t){if(!Nt(t))return!1;var r=typeof e;return(r=="number"?Mn(t)&&$h(e,t.length):r=="string"&&e in t)?pr(t[e],i):!1}var $g,jg=T(()=>{wa();al();G1();Br();$g=lC});function hC(i){return Hg(function(e,t){var r=-1,n=t.length,s=n>1?t[n-1]:void 0,a=n>2?t[2]:void 0;for(s=i.length>3&&typeof s=="function"?(n--,s):void 0,a&&$g(t[0],t[1],a)&&(s=n<3?void 0:s,n=1),e=Object(e);++r{Ug();jg();Gg=hC});function cC(i){var e=i&&i.constructor,t=typeof e=="function"&&e.prototype||dC;return i===t}var dC,Ta,Wh=T(()=>{dC=Object.prototype;Ta=cC});function uC(i,e){for(var t=-1,r=Array(i);++t{Wg=uC});function mC(i){return vt(i)&&Zi(i)==fC}var fC,Y1,Xg=T(()=>{ga();Or();fC="[object Arguments]";Y1=mC});var Kg,pC,gC,xC,ol,X1=T(()=>{Xg();Or();Kg=Object.prototype,pC=Kg.hasOwnProperty,gC=Kg.propertyIsEnumerable,xC=Y1((function(){return arguments})())?Y1:function(i){return vt(i)&&pC.call(i,"callee")&&!gC.call(i,"callee")},ol=xC});function yC(){return!1}var Zg,Qg=T(()=>{Zg=yC});var t5,Jg,vC,e5,bC,wC,Fr,ll=T(()=>{Ki();Qg();t5=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Jg=t5&&typeof module=="object"&&module&&!module.nodeType&&module,vC=Jg&&Jg.exports===t5,e5=vC?lt.Buffer:void 0,bC=e5?e5.isBuffer:void 0,wC=bC||Zg,Fr=wC});function VC(i){return vt(i)&&Vh(i.length)&&!!He[Zi(i)]}var MC,TC,NC,EC,SC,AC,kC,CC,_C,LC,IC,zC,RC,DC,OC,BC,PC,FC,qC,HC,UC,$C,jC,GC,He,i5,r5=T(()=>{ga();W1();Or();MC="[object Arguments]",TC="[object Array]",NC="[object Boolean]",EC="[object Date]",SC="[object Error]",AC="[object Function]",kC="[object Map]",CC="[object Number]",_C="[object Object]",LC="[object RegExp]",IC="[object Set]",zC="[object String]",RC="[object WeakMap]",DC="[object ArrayBuffer]",OC="[object DataView]",BC="[object Float32Array]",PC="[object Float64Array]",FC="[object Int8Array]",qC="[object Int16Array]",HC="[object Int32Array]",UC="[object Uint8Array]",$C="[object Uint8ClampedArray]",jC="[object Uint16Array]",GC="[object Uint32Array]",He={};He[BC]=He[PC]=He[FC]=He[qC]=He[HC]=He[UC]=He[$C]=He[jC]=He[GC]=!0;He[MC]=He[TC]=He[DC]=He[NC]=He[OC]=He[EC]=He[SC]=He[AC]=He[kC]=He[CC]=He[_C]=He[LC]=He[IC]=He[zC]=He[RC]=!1;i5=VC});function WC(i){return function(e){return i(e)}}var Na,Yh=T(()=>{Na=WC});var n5,hl,YC,K1,XC,qr,Xh=T(()=>{q1();n5=typeof exports=="object"&&exports&&!exports.nodeType&&exports,hl=n5&&typeof module=="object"&&module&&!module.nodeType&&module,YC=hl&&hl.exports===n5,K1=YC&&Bh.process,XC=(function(){try{var i=hl&&hl.require&&hl.require("util").types;return i||K1&&K1.binding&&K1.binding("util")}catch{}})(),qr=XC});var s5,KC,Ea,Kh=T(()=>{r5();Yh();Xh();s5=qr&&qr.isTypedArray,KC=s5?Na(s5):i5,Ea=KC});function JC(i,e){var t=Li(i),r=!t&&ol(i),n=!t&&!r&&Fr(i),s=!t&&!r&&!n&&Ea(i),a=t||r||n||s,o=a?Wg(i.length,String):[],l=o.length;for(var h in i)(e||QC.call(i,h))&&!(a&&(h=="length"||n&&(h=="offset"||h=="parent")||s&&(h=="buffer"||h=="byteLength"||h=="byteOffset")||$h(h,l)))&&o.push(h);return o}var ZC,QC,Zh,Z1=T(()=>{Yg();X1();xa();ll();G1();Kh();ZC=Object.prototype,QC=ZC.hasOwnProperty;Zh=JC});function e_(i,e){return function(t){return i(e(t))}}var Qh,Q1=T(()=>{Qh=e_});var t_,a5,o5=T(()=>{Q1();t_=Qh(Object.keys,Object),a5=t_});function n_(i){if(!Ta(i))return a5(i);var e=[];for(var t in Object(i))r_.call(i,t)&&t!="constructor"&&e.push(t);return e}var i_,r_,l5,h5=T(()=>{Wh();o5();i_=Object.prototype,r_=i_.hasOwnProperty;l5=n_});function s_(i){return Mn(i)?Zh(i):l5(i)}var Sa,Jh=T(()=>{Z1();h5();al();Sa=s_});function a_(i){var e=[];if(i!=null)for(var t in Object(i))e.push(t);return e}var d5,c5=T(()=>{d5=a_});function h_(i){if(!Nt(i))return d5(i);var e=Ta(i),t=[];for(var r in i)r=="constructor"&&(e||!l_.call(i,r))||t.push(r);return t}var o_,l_,u5,f5=T(()=>{Br();Wh();c5();o_=Object.prototype,l_=o_.hasOwnProperty;u5=h_});function d_(i){return Mn(i)?Zh(i,!0):u5(i)}var xr,Aa=T(()=>{Z1();f5();al();xr=d_});var c_,Hr,dl=T(()=>{wn();c_=ii(Object,"create"),Hr=c_});function u_(){this.__data__=Hr?Hr(null):{},this.size=0}var m5,p5=T(()=>{dl();m5=u_});function f_(i){var e=this.has(i)&&delete this.__data__[i];return this.size-=e?1:0,e}var g5,x5=T(()=>{g5=f_});function x_(i){var e=this.__data__;if(Hr){var t=e[i];return t===m_?void 0:t}return g_.call(e,i)?e[i]:void 0}var m_,p_,g_,y5,v5=T(()=>{dl();m_="__lodash_hash_undefined__",p_=Object.prototype,g_=p_.hasOwnProperty;y5=x_});function b_(i){var e=this.__data__;return Hr?e[i]!==void 0:v_.call(e,i)}var y_,v_,b5,w5=T(()=>{dl();y_=Object.prototype,v_=y_.hasOwnProperty;b5=b_});function M_(i,e){var t=this.__data__;return this.size+=this.has(i)?0:1,t[i]=Hr&&e===void 0?w_:e,this}var w_,M5,T5=T(()=>{dl();w_="__lodash_hash_undefined__";M5=M_});function ka(i){var e=-1,t=i==null?0:i.length;for(this.clear();++e{p5();x5();v5();w5();T5();ka.prototype.clear=m5;ka.prototype.delete=g5;ka.prototype.get=y5;ka.prototype.has=b5;ka.prototype.set=M5;J1=ka});function T_(){this.__data__=[],this.size=0}var E5,S5=T(()=>{E5=T_});function N_(i,e){for(var t=i.length;t--;)if(pr(i[t][0],e))return t;return-1}var Tn,cl=T(()=>{wa();Tn=N_});function A_(i){var e=this.__data__,t=Tn(e,i);if(t<0)return!1;var r=e.length-1;return t==r?e.pop():S_.call(e,t,1),--this.size,!0}var E_,S_,A5,k5=T(()=>{cl();E_=Array.prototype,S_=E_.splice;A5=A_});function k_(i){var e=this.__data__,t=Tn(e,i);return t<0?void 0:e[t][1]}var C5,_5=T(()=>{cl();C5=k_});function C_(i){return Tn(this.__data__,i)>-1}var L5,I5=T(()=>{cl();L5=C_});function __(i,e){var t=this.__data__,r=Tn(t,i);return r<0?(++this.size,t.push([i,e])):t[r][1]=e,this}var z5,R5=T(()=>{cl();z5=__});function Ca(i){var e=-1,t=i==null?0:i.length;for(this.clear();++e{S5();k5();_5();I5();R5();Ca.prototype.clear=E5;Ca.prototype.delete=A5;Ca.prototype.get=C5;Ca.prototype.has=L5;Ca.prototype.set=z5;Nn=Ca});var L_,En,ed=T(()=>{wn();Ki();L_=ii(lt,"Map"),En=L_});function I_(){this.size=0,this.__data__={hash:new J1,map:new(En||Nn),string:new J1}}var D5,O5=T(()=>{N5();ul();ed();D5=I_});function z_(i){var e=typeof i;return e=="string"||e=="number"||e=="symbol"||e=="boolean"?i!=="__proto__":i===null}var B5,P5=T(()=>{B5=z_});function R_(i,e){var t=i.__data__;return B5(e)?t[typeof e=="string"?"string":"hash"]:t.map}var Sn,fl=T(()=>{P5();Sn=R_});function D_(i){var e=Sn(this,i).delete(i);return this.size-=e?1:0,e}var F5,q5=T(()=>{fl();F5=D_});function O_(i){return Sn(this,i).get(i)}var H5,U5=T(()=>{fl();H5=O_});function B_(i){return Sn(this,i).has(i)}var $5,j5=T(()=>{fl();$5=B_});function P_(i,e){var t=Sn(this,i),r=t.size;return t.set(i,e),this.size+=t.size==r?0:1,this}var G5,V5=T(()=>{fl();G5=P_});function _a(i){var e=-1,t=i==null?0:i.length;for(this.clear();++e{O5();q5();U5();j5();V5();_a.prototype.clear=D5;_a.prototype.delete=F5;_a.prototype.get=H5;_a.prototype.has=$5;_a.prototype.set=G5;td=_a});function F_(i,e){for(var t=-1,r=e.length,n=i.length;++t{id=F_});var q_,La,rd=T(()=>{Q1();q_=Qh(Object.getPrototypeOf,Object),La=q_});function V_(i){if(!vt(i)||Zi(i)!=H_)return!1;var e=La(i);if(e===null)return!0;var t=j_.call(e,"constructor")&&e.constructor;return typeof t=="function"&&t instanceof t&&W5.call(t)==G_}var H_,U_,$_,W5,j_,G_,Y5,X5=T(()=>{ga();rd();Or();H_="[object Object]",U_=Function.prototype,$_=Object.prototype,W5=U_.toString,j_=$_.hasOwnProperty,G_=W5.call(Object);Y5=V_});function W_(){this.__data__=new Nn,this.size=0}var K5,Z5=T(()=>{ul();K5=W_});function Y_(i){var e=this.__data__,t=e.delete(i);return this.size=e.size,t}var Q5,J5=T(()=>{Q5=Y_});function X_(i){return this.__data__.get(i)}var e7,t7=T(()=>{e7=X_});function K_(i){return this.__data__.has(i)}var i7,r7=T(()=>{i7=K_});function Q_(i,e){var t=this.__data__;if(t instanceof Nn){var r=t.__data__;if(!En||r.length{ul();ed();ef();Z_=200;n7=Q_});function Ia(i){var e=this.__data__=new Nn(i);this.size=e.size}var An,nd=T(()=>{ul();Z5();J5();t7();r7();s7();Ia.prototype.clear=K5;Ia.prototype.delete=Q5;Ia.prototype.get=e7;Ia.prototype.has=i7;Ia.prototype.set=n7;An=Ia});function J_(i,e){return i&&gr(e,Sa(e),i)}var a7,o7=T(()=>{Ma();Jh();a7=J_});function eL(i,e){return i&&gr(e,xr(e),i)}var l7,h7=T(()=>{Ma();Aa();l7=eL});function iL(i,e){if(e)return i.slice();var t=i.length,r=u7?u7(t):new i.constructor(t);return i.copy(r),r}var f7,d7,tL,c7,u7,sd,rf=T(()=>{Ki();f7=typeof exports=="object"&&exports&&!exports.nodeType&&exports,d7=f7&&typeof module=="object"&&module&&!module.nodeType&&module,tL=d7&&d7.exports===f7,c7=tL?lt.Buffer:void 0,u7=c7?c7.allocUnsafe:void 0;sd=iL});function rL(i,e){for(var t=-1,r=i==null?0:i.length,n=0,s=[];++t{m7=rL});function nL(){return[]}var ad,nf=T(()=>{ad=nL});var sL,aL,g7,oL,za,od=T(()=>{p7();nf();sL=Object.prototype,aL=sL.propertyIsEnumerable,g7=Object.getOwnPropertySymbols,oL=g7?function(i){return i==null?[]:(i=Object(i),m7(g7(i),function(e){return aL.call(i,e)}))}:ad,za=oL});function lL(i,e){return gr(i,za(i),e)}var x7,y7=T(()=>{Ma();od();x7=lL});var hL,dL,ld,sf=T(()=>{tf();rd();od();nf();hL=Object.getOwnPropertySymbols,dL=hL?function(i){for(var e=[];i;)id(e,za(i)),i=La(i);return e}:ad,ld=dL});function cL(i,e){return gr(i,ld(i),e)}var v7,b7=T(()=>{Ma();sf();v7=cL});function uL(i,e,t){var r=e(i);return Li(i)?r:id(r,t(i))}var hd,af=T(()=>{tf();xa();hd=uL});function fL(i){return hd(i,Sa,za)}var ml,of=T(()=>{af();od();Jh();ml=fL});function mL(i){return hd(i,xr,ld)}var w7,M7=T(()=>{af();sf();Aa();w7=mL});var pL,dd,T7=T(()=>{wn();Ki();pL=ii(lt,"DataView"),dd=pL});var gL,cd,N7=T(()=>{wn();Ki();gL=ii(lt,"Promise"),cd=gL});var xL,ud,E7=T(()=>{wn();Ki();xL=ii(lt,"Set"),ud=xL});var S7,yL,A7,k7,C7,_7,vL,bL,wL,ML,TL,xs,Ur,pl=T(()=>{T7();ed();N7();E7();Mg();ga();U1();S7="[object Map]",yL="[object Object]",A7="[object Promise]",k7="[object Set]",C7="[object WeakMap]",_7="[object DataView]",vL=Pr(dd),bL=Pr(En),wL=Pr(cd),ML=Pr(ud),TL=Pr(Hh),xs=Zi;(dd&&xs(new dd(new ArrayBuffer(1)))!=_7||En&&xs(new En)!=S7||cd&&xs(cd.resolve())!=A7||ud&&xs(new ud)!=k7||Hh&&xs(new Hh)!=C7)&&(xs=function(i){var e=Zi(i),t=e==yL?i.constructor:void 0,r=t?Pr(t):"";if(r)switch(r){case vL:return _7;case bL:return S7;case wL:return A7;case ML:return k7;case TL:return C7}return e});Ur=xs});function SL(i){var e=i.length,t=new i.constructor(e);return e&&typeof i[0]=="string"&&EL.call(i,"index")&&(t.index=i.index,t.input=i.input),t}var NL,EL,L7,I7=T(()=>{NL=Object.prototype,EL=NL.hasOwnProperty;L7=SL});var AL,Ra,lf=T(()=>{Ki();AL=lt.Uint8Array,Ra=AL});function kL(i){var e=new i.constructor(i.byteLength);return new Ra(e).set(new Ra(i)),e}var Da,fd=T(()=>{lf();Da=kL});function CL(i,e){var t=e?Da(i.buffer):i.buffer;return new i.constructor(t,i.byteOffset,i.byteLength)}var z7,R7=T(()=>{fd();z7=CL});function LL(i){var e=new i.constructor(i.source,_L.exec(i));return e.lastIndex=i.lastIndex,e}var _L,D7,O7=T(()=>{_L=/\w*$/;D7=LL});function IL(i){return P7?Object(P7.call(i)):{}}var B7,P7,F7,q7=T(()=>{nl();B7=_i?_i.prototype:void 0,P7=B7?B7.valueOf:void 0;F7=IL});function zL(i,e){var t=e?Da(i.buffer):i.buffer;return new i.constructor(t,i.byteOffset,i.length)}var md,hf=T(()=>{fd();md=zL});function JL(i,e,t){var r=i.constructor;switch(e){case UL:return Da(i);case RL:case DL:return new r(+i);case $L:return z7(i,t);case jL:case GL:case VL:case WL:case YL:case XL:case KL:case ZL:case QL:return md(i,t);case OL:return new r;case BL:case qL:return new r(i);case PL:return D7(i);case FL:return new r;case HL:return F7(i)}}var RL,DL,OL,BL,PL,FL,qL,HL,UL,$L,jL,GL,VL,WL,YL,XL,KL,ZL,QL,H7,U7=T(()=>{fd();R7();O7();q7();hf();RL="[object Boolean]",DL="[object Date]",OL="[object Map]",BL="[object Number]",PL="[object RegExp]",FL="[object Set]",qL="[object String]",HL="[object Symbol]",UL="[object ArrayBuffer]",$L="[object DataView]",jL="[object Float32Array]",GL="[object Float64Array]",VL="[object Int8Array]",WL="[object Int16Array]",YL="[object Int32Array]",XL="[object Uint8Array]",KL="[object Uint8ClampedArray]",ZL="[object Uint16Array]",QL="[object Uint32Array]";H7=JL});function eI(i){return typeof i.constructor=="function"&&!Ta(i)?Ng(La(i)):{}}var pd,df=T(()=>{Eg();rd();Wh();pd=eI});function iI(i){return vt(i)&&Ur(i)==tI}var tI,$7,j7=T(()=>{pl();Or();tI="[object Map]";$7=iI});var G7,rI,V7,W7=T(()=>{j7();Yh();Xh();G7=qr&&qr.isMap,rI=G7?Na(G7):$7,V7=rI});function sI(i){return vt(i)&&Ur(i)==nI}var nI,Y7,X7=T(()=>{pl();Or();nI="[object Set]";Y7=sI});var K7,aI,Z7,Q7=T(()=>{X7();Yh();Xh();K7=qr&&qr.isSet,aI=K7?Na(K7):Y7,Z7=aI});function gd(i,e,t,r,n,s){var a,o=e&oI,l=e&lI,h=e&hI;if(t&&(a=n?t(i,r,n,s):t(i)),a!==void 0)return a;if(!Nt(i))return i;var d=Li(i);if(d){if(a=L7(i),!o)return Uh(i,a)}else{var c=Ur(i),f=c==e8||c==mI;if(Fr(i))return sd(i,o);if(c==t8||c==J7||f&&!n){if(a=l||f?{}:pd(i),!o)return l?v7(i,l7(a,i)):x7(i,a7(a,i))}else{if(!Be[c])return n?i:{};a=H7(i,c,o)}}s||(s=new An);var m=s.get(i);if(m)return m;s.set(i,a),Z7(i)?i.forEach(function(y){a.add(gd(y,e,t,y,i,s))}):V7(i)&&i.forEach(function(y,b){a.set(b,gd(y,e,t,b,i,s))});var g=h?l?w7:ml:l?xr:Sa,x=d?void 0:g(i);return Og(x||i,function(y,b){x&&(b=y,y=i[b]),Gh(a,b,gd(y,e,t,b,i,s))}),a}var oI,lI,hI,J7,dI,cI,uI,fI,e8,mI,pI,gI,t8,xI,yI,vI,bI,wI,MI,TI,NI,EI,SI,AI,kI,CI,_I,LI,II,Be,i8,r8=T(()=>{nd();Bg();V1();o7();h7();rf();$1();y7();b7();of();M7();pl();I7();U7();df();xa();ll();W7();Br();Q7();Jh();Aa();oI=1,lI=2,hI=4,J7="[object Arguments]",dI="[object Array]",cI="[object Boolean]",uI="[object Date]",fI="[object Error]",e8="[object Function]",mI="[object GeneratorFunction]",pI="[object Map]",gI="[object Number]",t8="[object Object]",xI="[object RegExp]",yI="[object Set]",vI="[object String]",bI="[object Symbol]",wI="[object WeakMap]",MI="[object ArrayBuffer]",TI="[object DataView]",NI="[object Float32Array]",EI="[object Float64Array]",SI="[object Int8Array]",AI="[object Int16Array]",kI="[object Int32Array]",CI="[object Uint8Array]",_I="[object Uint8ClampedArray]",LI="[object Uint16Array]",II="[object Uint32Array]",Be={};Be[J7]=Be[dI]=Be[MI]=Be[TI]=Be[cI]=Be[uI]=Be[NI]=Be[EI]=Be[SI]=Be[AI]=Be[kI]=Be[pI]=Be[gI]=Be[t8]=Be[xI]=Be[yI]=Be[vI]=Be[bI]=Be[CI]=Be[_I]=Be[LI]=Be[II]=!0;Be[fI]=Be[e8]=Be[wI]=!1;i8=gd});function DI(i){return i8(i,zI|RI)}var zI,RI,yr,n8=T(()=>{r8();zI=1,RI=4;yr=DI});function BI(i){return this.__data__.set(i,OI),this}var OI,s8,a8=T(()=>{OI="__lodash_hash_undefined__";s8=BI});function PI(i){return this.__data__.has(i)}var o8,l8=T(()=>{o8=PI});function xd(i){var e=-1,t=i==null?0:i.length;for(this.__data__=new td;++e{ef();a8();l8();xd.prototype.add=xd.prototype.push=s8;xd.prototype.has=o8;h8=xd});function FI(i,e){for(var t=-1,r=i==null?0:i.length;++t{c8=FI});function qI(i,e){return i.has(e)}var f8,m8=T(()=>{f8=qI});function $I(i,e,t,r,n,s){var a=t&HI,o=i.length,l=e.length;if(o!=l&&!(a&&l>o))return!1;var h=s.get(i),d=s.get(e);if(h&&d)return h==e&&d==i;var c=-1,f=!0,m=t&UI?new h8:void 0;for(s.set(i,e),s.set(e,i);++c{d8();u8();m8();HI=1,UI=2;yd=$I});function jI(i){var e=-1,t=Array(i.size);return i.forEach(function(r,n){t[++e]=[n,r]}),t}var p8,g8=T(()=>{p8=jI});function GI(i){var e=-1,t=Array(i.size);return i.forEach(function(r){t[++e]=r}),t}var x8,y8=T(()=>{x8=GI});function sz(i,e,t,r,n,s,a){switch(t){case nz:if(i.byteLength!=e.byteLength||i.byteOffset!=e.byteOffset)return!1;i=i.buffer,e=e.buffer;case rz:return!(i.byteLength!=e.byteLength||!s(new Ra(i),new Ra(e)));case YI:case XI:case QI:return pr(+i,+e);case KI:return i.name==e.name&&i.message==e.message;case JI:case tz:return i==e+"";case ZI:var o=p8;case ez:var l=r&VI;if(o||(o=x8),i.size!=e.size&&!l)return!1;var h=a.get(i);if(h)return h==e;r|=WI,a.set(i,e);var d=yd(o(i),o(e),r,n,s,a);return a.delete(i),d;case iz:if(uf)return uf.call(i)==uf.call(e)}return!1}var VI,WI,YI,XI,KI,ZI,QI,JI,ez,tz,iz,rz,nz,v8,uf,b8,w8=T(()=>{nl();lf();wa();cf();g8();y8();VI=1,WI=2,YI="[object Boolean]",XI="[object Date]",KI="[object Error]",ZI="[object Map]",QI="[object Number]",JI="[object RegExp]",ez="[object Set]",tz="[object String]",iz="[object Symbol]",rz="[object ArrayBuffer]",nz="[object DataView]",v8=_i?_i.prototype:void 0,uf=v8?v8.valueOf:void 0;b8=sz});function hz(i,e,t,r,n,s){var a=t&az,o=ml(i),l=o.length,h=ml(e),d=h.length;if(l!=d&&!a)return!1;for(var c=l;c--;){var f=o[c];if(!(a?f in e:lz.call(e,f)))return!1}var m=s.get(i),g=s.get(e);if(m&&g)return m==e&&g==i;var x=!0;s.set(i,e),s.set(e,i);for(var y=a;++c{of();az=1,oz=Object.prototype,lz=oz.hasOwnProperty;M8=hz});function uz(i,e,t,r,n,s){var a=Li(i),o=Li(e),l=a?E8:Ur(i),h=o?E8:Ur(e);l=l==N8?vd:l,h=h==N8?vd:h;var d=l==vd,c=h==vd,f=l==h;if(f&&Fr(i)){if(!Fr(e))return!1;a=!0,d=!1}if(f&&!d)return s||(s=new An),a||Ea(i)?yd(i,e,t,r,n,s):b8(i,e,l,t,r,n,s);if(!(t&dz)){var m=d&&S8.call(i,"__wrapped__"),g=c&&S8.call(e,"__wrapped__");if(m||g){var x=m?i.value():i,y=g?e.value():e;return s||(s=new An),n(x,y,t,r,s)}}return f?(s||(s=new An),M8(i,e,t,r,n,s)):!1}var dz,N8,E8,vd,cz,S8,A8,k8=T(()=>{nd();cf();w8();T8();pl();xa();ll();Kh();dz=1,N8="[object Arguments]",E8="[object Array]",vd="[object Object]",cz=Object.prototype,S8=cz.hasOwnProperty;A8=uz});function C8(i,e,t,r,n){return i===e?!0:i==null||e==null||!vt(i)&&!vt(e)?i!==i&&e!==e:A8(i,e,t,r,C8,n)}var _8,L8=T(()=>{k8();Or();_8=C8});function fz(i){return function(e,t,r){for(var n=-1,s=Object(e),a=r(e),o=a.length;o--;){var l=a[i?o:++n];if(t(s[l],l,s)===!1)break}return e}}var I8,z8=T(()=>{I8=fz});var mz,R8,D8=T(()=>{z8();mz=I8(),R8=mz});function pz(i,e,t){(t!==void 0&&!pr(i[e],t)||t===void 0&&!(e in i))&&ba(i,e,t)}var gl,ff=T(()=>{jh();wa();gl=pz});function gz(i){return vt(i)&&Mn(i)}var O8,B8=T(()=>{al();Or();O8=gz});function xz(i,e){if(!(e==="constructor"&&typeof i[e]=="function")&&e!="__proto__")return i[e]}var xl,mf=T(()=>{xl=xz});function yz(i){return gr(i,xr(i))}var P8,F8=T(()=>{Ma();Aa();P8=yz});function vz(i,e,t,r,n,s,a){var o=xl(i,t),l=xl(e,t),h=a.get(l);if(h){gl(i,t,h);return}var d=s?s(o,l,t+"",i,e,a):void 0,c=d===void 0;if(c){var f=Li(l),m=!f&&Fr(l),g=!f&&!m&&Ea(l);d=l,f||m||g?Li(o)?d=o:O8(o)?d=Uh(o):m?(c=!1,d=sd(l,!0)):g?(c=!1,d=md(l,!0)):d=[]:Y5(l)||ol(l)?(d=o,ol(o)?d=P8(o):(!Nt(o)||ya(o))&&(d=pd(l))):c=!1}c&&(a.set(l,d),n(d,l,r,s,a),a.delete(l)),gl(i,t,d)}var q8,H8=T(()=>{ff();rf();hf();$1();df();X1();xa();B8();ll();Fh();Br();X5();Kh();mf();F8();q8=vz});function U8(i,e,t,r,n){i!==e&&R8(e,function(s,a){if(n||(n=new An),Nt(s))q8(i,e,a,t,U8,r,n);else{var o=r?r(xl(i,a),s,a+"",i,e,n):void 0;o===void 0&&(o=s),gl(i,a,o)}},xr)}var $8,j8=T(()=>{nd();ff();D8();H8();Br();Aa();mf();$8=U8});function bz(i,e){return _8(i,e)}var ys,G8=T(()=>{L8();ys=bz});var wz,ri,V8=T(()=>{j8();Vg();wz=Gg(function(i,e,t){$8(i,e,t)}),ri=wz});var kn=T(()=>{n8();G8();V8();});var bl={};tt(bl,{Attributor:()=>qt,AttributorStore:()=>yl,BlockBlot:()=>vs,ClassAttributor:()=>ht,ContainerBlot:()=>qa,EmbedBlot:()=>Ke,InlineBlot:()=>bd,LeafBlot:()=>dt,ParentBlot:()=>ni,Registry:()=>Ln,Scope:()=>X,ScrollBlot:()=>vl,StyleAttributor:()=>si,TextBlot:()=>Ha});function W8(i,e){return(i.getAttribute("class")||"").split(/\s+/).filter(t=>t.indexOf(`${e}-`)===0)}function pf(i){let e=i.split("-"),t=e.slice(1).map(r=>r[0].toUpperCase()+r.slice(1)).join("");return e[0]+t}function Y8(i,e){let t=e.find(i);if(t)return t;try{return e.create(i)}catch{let r=e.create(X.INLINE);return Array.from(i.childNodes).forEach(n=>{r.domNode.appendChild(n)}),i.parentNode&&i.parentNode.replaceChild(r.domNode,i),r.attach(),r}}function Nz(i,e){if(Object.keys(i).length!==Object.keys(e).length)return!1;for(let t in i)if(i[t]!==e[t])return!1;return!0}var X,qt,_n,X8,Ln,xf,ht,yf,si,vf,yl,K8,Z8,Q8,Mz,dt,bf,J8,Tz,ni,Oa,Ez,bd,Pa,Sz,vs,Mf,Az,qa,Tf,Ke,kz,Cz,Fa,_z,vl,Nf,Lz,Ha,Re=T(()=>{X=(i=>(i[i.TYPE=3]="TYPE",i[i.LEVEL=12]="LEVEL",i[i.ATTRIBUTE=13]="ATTRIBUTE",i[i.BLOT=14]="BLOT",i[i.INLINE=7]="INLINE",i[i.BLOCK=11]="BLOCK",i[i.BLOCK_BLOT=10]="BLOCK_BLOT",i[i.INLINE_BLOT=6]="INLINE_BLOT",i[i.BLOCK_ATTRIBUTE=9]="BLOCK_ATTRIBUTE",i[i.INLINE_ATTRIBUTE=5]="INLINE_ATTRIBUTE",i[i.ANY=15]="ANY",i))(X||{}),qt=class{constructor(e,t,r={}){this.attrName=e,this.keyName=t;let n=X.TYPE&X.ATTRIBUTE;this.scope=r.scope!=null?r.scope&X.LEVEL|n:X.ATTRIBUTE,r.whitelist!=null&&(this.whitelist=r.whitelist)}static keys(e){return Array.from(e.attributes).map(t=>t.name)}add(e,t){return this.canAdd(e,t)?(e.setAttribute(this.keyName,t),!0):!1}canAdd(e,t){return this.whitelist==null?!0:typeof t=="string"?this.whitelist.indexOf(t.replace(/["']/g,""))>-1:this.whitelist.indexOf(t)>-1}remove(e){e.removeAttribute(this.keyName)}value(e){let t=e.getAttribute(this.keyName);return this.canAdd(e,t)&&t?t:""}},_n=class extends Error{constructor(e){e="[Parchment] "+e,super(e),this.message=e,this.name=this.constructor.name}},X8=class gf{constructor(){this.attributes={},this.classes={},this.tags={},this.types={}}static find(e,t=!1){if(e==null)return null;if(this.blots.has(e))return this.blots.get(e)||null;if(t){let r=null;try{r=e.parentNode}catch{return null}return this.find(r,t)}return null}create(e,t,r){let n=this.query(t);if(n==null)throw new _n(`Unable to create ${t} blot`);let s=n,a=t instanceof Node||t.nodeType===Node.TEXT_NODE?t:s.create(r),o=new s(e,a,r);return gf.blots.set(o.domNode,o),o}find(e,t=!1){return gf.find(e,t)}query(e,t=X.ANY){let r;return typeof e=="string"?r=this.types[e]||this.attributes[e]:e instanceof Text||e.nodeType===Node.TEXT_NODE?r=this.types.text:typeof e=="number"?e&X.LEVEL&X.BLOCK?r=this.types.block:e&X.LEVEL&X.INLINE&&(r=this.types.inline):e instanceof Element&&((e.getAttribute("class")||"").split(/\s+/).some(n=>(r=this.classes[n],!!r)),r=r||this.tags[e.tagName]),r==null?null:"scope"in r&&t&X.LEVEL&r.scope&&t&X.TYPE&r.scope?r:null}register(...e){return e.map(t=>{let r="blotName"in t,n="attrName"in t;if(!r&&!n)throw new _n("Invalid definition");if(r&&t.blotName==="abstract")throw new _n("Cannot register abstract class");let s=r?t.blotName:n?t.attrName:void 0;return this.types[s]=t,n?typeof t.keyName=="string"&&(this.attributes[t.keyName]=t):r&&(t.className&&(this.classes[t.className]=t),t.tagName&&(Array.isArray(t.tagName)?t.tagName=t.tagName.map(a=>a.toUpperCase()):t.tagName=t.tagName.toUpperCase(),(Array.isArray(t.tagName)?t.tagName:[t.tagName]).forEach(a=>{(this.tags[a]==null||t.className==null)&&(this.tags[a]=t)}))),t})}};X8.blots=new WeakMap;Ln=X8;xf=class extends qt{static keys(e){return(e.getAttribute("class")||"").split(/\s+/).map(t=>t.split("-").slice(0,-1).join("-"))}add(e,t){return this.canAdd(e,t)?(this.remove(e),e.classList.add(`${this.keyName}-${t}`),!0):!1}remove(e){W8(e,this.keyName).forEach(t=>{e.classList.remove(t)}),e.classList.length===0&&e.removeAttribute("class")}value(e){let t=(W8(e,this.keyName)[0]||"").slice(this.keyName.length+1);return this.canAdd(e,t)?t:""}},ht=xf;yf=class extends qt{static keys(e){return(e.getAttribute("style")||"").split(";").map(t=>t.split(":")[0].trim())}add(e,t){return this.canAdd(e,t)?(e.style[pf(this.keyName)]=t,!0):!1}remove(e){e.style[pf(this.keyName)]="",e.getAttribute("style")||e.removeAttribute("style")}value(e){let t=e.style[pf(this.keyName)];return this.canAdd(e,t)?t:""}},si=yf,vf=class{constructor(e){this.attributes={},this.domNode=e,this.build()}attribute(e,t){t?e.add(this.domNode,t)&&(e.value(this.domNode)!=null?this.attributes[e.attrName]=e:delete this.attributes[e.attrName]):(e.remove(this.domNode),delete this.attributes[e.attrName])}build(){this.attributes={};let e=Ln.find(this.domNode);if(e==null)return;let t=qt.keys(this.domNode),r=ht.keys(this.domNode),n=si.keys(this.domNode);t.concat(r).concat(n).forEach(s=>{let a=e.scroll.query(s,X.ATTRIBUTE);a instanceof qt&&(this.attributes[a.attrName]=a)})}copy(e){Object.keys(this.attributes).forEach(t=>{let r=this.attributes[t].value(this.domNode);e.format(t,r)})}move(e){this.copy(e),Object.keys(this.attributes).forEach(t=>{this.attributes[t].remove(this.domNode)}),this.attributes={}}values(){return Object.keys(this.attributes).reduce((e,t)=>(e[t]=this.attributes[t].value(this.domNode),e),{})}},yl=vf,K8=class{constructor(e,t){this.scroll=e,this.domNode=t,Ln.blots.set(t,this),this.prev=null,this.next=null}static create(e){if(this.tagName==null)throw new _n("Blot definition missing tagName");let t,r;return Array.isArray(this.tagName)?(typeof e=="string"?(r=e.toUpperCase(),parseInt(r,10).toString()===r&&(r=parseInt(r,10))):typeof e=="number"&&(r=e),typeof r=="number"?t=document.createElement(this.tagName[r-1]):r&&this.tagName.indexOf(r)>-1?t=document.createElement(r):t=document.createElement(this.tagName[0])):t=document.createElement(this.tagName),this.className&&t.classList.add(this.className),t}get statics(){return this.constructor}attach(){}clone(){let e=this.domNode.cloneNode(!1);return this.scroll.create(e)}detach(){this.parent!=null&&this.parent.removeChild(this),Ln.blots.delete(this.domNode)}deleteAt(e,t){this.isolate(e,t).remove()}formatAt(e,t,r,n){let s=this.isolate(e,t);if(this.scroll.query(r,X.BLOT)!=null&&n)s.wrap(r,n);else if(this.scroll.query(r,X.ATTRIBUTE)!=null){let a=this.scroll.create(this.statics.scope);s.wrap(a),a.format(r,n)}}insertAt(e,t,r){let n=r==null?this.scroll.create("text",t):this.scroll.create(t,r),s=this.split(e);this.parent.insertBefore(n,s||void 0)}isolate(e,t){let r=this.split(e);if(r==null)throw new Error("Attempt to isolate at end");return r.split(t),r}length(){return 1}offset(e=this.parent){return this.parent==null||this===e?0:this.parent.children.offset(this)+this.parent.offset(e)}optimize(e){this.statics.requiredContainer&&!(this.parent instanceof this.statics.requiredContainer)&&this.wrap(this.statics.requiredContainer.blotName)}remove(){this.domNode.parentNode!=null&&this.domNode.parentNode.removeChild(this.domNode),this.detach()}replaceWith(e,t){let r=typeof e=="string"?this.scroll.create(e,t):e;return this.parent!=null&&(this.parent.insertBefore(r,this.next||void 0),this.remove()),r}split(e,t){return e===0?this:this.next}update(e,t){}wrap(e,t){let r=typeof e=="string"?this.scroll.create(e,t):e;if(this.parent!=null&&this.parent.insertBefore(r,this.next||void 0),typeof r.appendChild!="function")throw new _n(`Cannot wrap ${e}`);return r.appendChild(this),r}};K8.blotName="abstract";Z8=K8,Q8=class extends Z8{static value(e){return!0}index(e,t){return this.domNode===e||this.domNode.compareDocumentPosition(e)&Node.DOCUMENT_POSITION_CONTAINED_BY?Math.min(t,1):-1}position(e,t){let r=Array.from(this.parent.domNode.childNodes).indexOf(this.domNode);return e>0&&(r+=1),[this.parent.domNode,r]}value(){return{[this.statics.blotName]:this.statics.value(this.domNode)||!0}}};Q8.scope=X.INLINE_BLOT;Mz=Q8,dt=Mz,bf=class{constructor(){this.head=null,this.tail=null,this.length=0}append(...e){if(this.insertBefore(e[0],null),e.length>1){let t=e.slice(1);this.append(...t)}}at(e){let t=this.iterator(),r=t();for(;r&&e>0;)e-=1,r=t();return r}contains(e){let t=this.iterator(),r=t();for(;r;){if(r===e)return!0;r=t()}return!1}indexOf(e){let t=this.iterator(),r=t(),n=0;for(;r;){if(r===e)return n;n+=1,r=t()}return-1}insertBefore(e,t){e!=null&&(this.remove(e),e.next=t,t!=null?(e.prev=t.prev,t.prev!=null&&(t.prev.next=e),t.prev=e,t===this.head&&(this.head=e)):this.tail!=null?(this.tail.next=e,e.prev=this.tail,this.tail=e):(e.prev=null,this.head=this.tail=e),this.length+=1)}offset(e){let t=0,r=this.head;for(;r!=null;){if(r===e)return t;t+=r.length(),r=r.next}return-1}remove(e){this.contains(e)&&(e.prev!=null&&(e.prev.next=e.next),e.next!=null&&(e.next.prev=e.prev),e===this.head&&(this.head=e.next),e===this.tail&&(this.tail=e.prev),this.length-=1)}iterator(e=this.head){return()=>{let t=e;return e!=null&&(e=e.next),t}}find(e,t=!1){let r=this.iterator(),n=r();for(;n;){let s=n.length();if(ea?r(l,e-a,Math.min(t,a+h-e)):r(l,0,Math.min(h,e+t-a)),a+=h,l=o()}}map(e){return this.reduce((t,r)=>(t.push(e(r)),t),[])}reduce(e,t){let r=this.iterator(),n=r();for(;n;)t=e(t,n),n=r();return t}};J8=class Cn extends Z8{constructor(e,t){super(e,t),this.uiNode=null,this.build()}appendChild(e){this.insertBefore(e)}attach(){super.attach(),this.children.forEach(e=>{e.attach()})}attachUI(e){this.uiNode!=null&&this.uiNode.remove(),this.uiNode=e,Cn.uiClass&&this.uiNode.classList.add(Cn.uiClass),this.uiNode.setAttribute("contenteditable","false"),this.domNode.insertBefore(this.uiNode,this.domNode.firstChild)}build(){this.children=new bf,Array.from(this.domNode.childNodes).filter(e=>e!==this.uiNode).reverse().forEach(e=>{try{let t=Y8(e,this.scroll);this.insertBefore(t,this.children.head||void 0)}catch(t){if(t instanceof _n)return;throw t}})}deleteAt(e,t){if(e===0&&t===this.length())return this.remove();this.children.forEachAt(e,t,(r,n,s)=>{r.deleteAt(n,s)})}descendant(e,t=0){let[r,n]=this.children.find(t);return e.blotName==null&&e(r)||e.blotName!=null&&r instanceof e?[r,n]:r instanceof Cn?r.descendant(e,n):[null,-1]}descendants(e,t=0,r=Number.MAX_VALUE){let n=[],s=r;return this.children.forEachAt(t,r,(a,o,l)=>{(e.blotName==null&&e(a)||e.blotName!=null&&a instanceof e)&&n.push(a),a instanceof Cn&&(n=n.concat(a.descendants(e,o,s))),s-=l}),n}detach(){this.children.forEach(e=>{e.detach()}),super.detach()}enforceAllowedChildren(){let e=!1;this.children.forEach(t=>{e||this.statics.allowedChildren.some(r=>t instanceof r)||(t.statics.scope===X.BLOCK_BLOT?(t.next!=null&&this.splitAfter(t),t.prev!=null&&this.splitAfter(t.prev),t.parent.unwrap(),e=!0):t instanceof Cn?t.unwrap():t.remove())})}formatAt(e,t,r,n){this.children.forEachAt(e,t,(s,a,o)=>{s.formatAt(a,o,r,n)})}insertAt(e,t,r){let[n,s]=this.children.find(e);if(n)n.insertAt(s,t,r);else{let a=r==null?this.scroll.create("text",t):this.scroll.create(t,r);this.appendChild(a)}}insertBefore(e,t){e.parent!=null&&e.parent.children.remove(e);let r=null;this.children.insertBefore(e,t||null),e.parent=this,t!=null&&(r=t.domNode),(this.domNode.parentNode!==e.domNode||this.domNode.nextSibling!==r)&&this.domNode.insertBefore(e.domNode,r),e.attach()}length(){return this.children.reduce((e,t)=>e+t.length(),0)}moveChildren(e,t){this.children.forEach(r=>{e.insertBefore(r,t)})}optimize(e){if(super.optimize(e),this.enforceAllowedChildren(),this.uiNode!=null&&this.uiNode!==this.domNode.firstChild&&this.domNode.insertBefore(this.uiNode,this.domNode.firstChild),this.children.length===0)if(this.statics.defaultChild!=null){let t=this.scroll.create(this.statics.defaultChild.blotName);this.appendChild(t)}else this.remove()}path(e,t=!1){let[r,n]=this.children.find(e,t),s=[[this,e]];return r instanceof Cn?s.concat(r.path(n,t)):(r!=null&&s.push([r,n]),s)}removeChild(e){this.children.remove(e)}replaceWith(e,t){let r=typeof e=="string"?this.scroll.create(e,t):e;return r instanceof Cn&&this.moveChildren(r),super.replaceWith(r)}split(e,t=!1){if(!t){if(e===0)return this;if(e===this.length())return this.next}let r=this.clone();return this.parent&&this.parent.insertBefore(r,this.next||void 0),this.children.forEachAt(e,this.length(),(n,s,a)=>{let o=n.split(s,t);o!=null&&r.appendChild(o)}),r}splitAfter(e){let t=this.clone();for(;e.next!=null;)t.appendChild(e.next);return this.parent&&this.parent.insertBefore(t,this.next||void 0),t}unwrap(){this.parent&&this.moveChildren(this.parent,this.next||void 0),this.remove()}update(e,t){let r=[],n=[];e.forEach(s=>{s.target===this.domNode&&s.type==="childList"&&(r.push(...s.addedNodes),n.push(...s.removedNodes))}),n.forEach(s=>{if(s.parentNode!=null&&s.tagName!=="IFRAME"&&document.body.compareDocumentPosition(s)&Node.DOCUMENT_POSITION_CONTAINED_BY)return;let a=this.scroll.find(s);a!=null&&(a.domNode.parentNode==null||a.domNode.parentNode===this.domNode)&&a.detach()}),r.filter(s=>s.parentNode===this.domNode&&s!==this.uiNode).sort((s,a)=>s===a?0:s.compareDocumentPosition(a)&Node.DOCUMENT_POSITION_FOLLOWING?1:-1).forEach(s=>{let a=null;s.nextSibling!=null&&(a=this.scroll.find(s.nextSibling));let o=Y8(s,this.scroll);(o.next!==a||o.next==null)&&(o.parent!=null&&o.parent.removeChild(this),this.insertBefore(o,a||void 0))}),this.enforceAllowedChildren()}};J8.uiClass="";Tz=J8,ni=Tz;Oa=class Ba extends ni{static create(e){return super.create(e)}static formats(e,t){let r=t.query(Ba.blotName);if(!(r!=null&&e.tagName===r.tagName)){if(typeof this.tagName=="string")return!0;if(Array.isArray(this.tagName))return e.tagName.toLowerCase()}}constructor(e,t){super(e,t),this.attributes=new yl(this.domNode)}format(e,t){if(e===this.statics.blotName&&!t)this.children.forEach(r=>{r instanceof Ba||(r=r.wrap(Ba.blotName,!0)),this.attributes.copy(r)}),this.unwrap();else{let r=this.scroll.query(e,X.INLINE);if(r==null)return;r instanceof qt?this.attributes.attribute(r,t):t&&(e!==this.statics.blotName||this.formats()[e]!==t)&&this.replaceWith(e,t)}}formats(){let e=this.attributes.values(),t=this.statics.formats(this.domNode,this.scroll);return t!=null&&(e[this.statics.blotName]=t),e}formatAt(e,t,r,n){this.formats()[r]!=null||this.scroll.query(r,X.ATTRIBUTE)?this.isolate(e,t).format(r,n):super.formatAt(e,t,r,n)}optimize(e){super.optimize(e);let t=this.formats();if(Object.keys(t).length===0)return this.unwrap();let r=this.next;r instanceof Ba&&r.prev===this&&Nz(t,r.formats())&&(r.moveChildren(this),r.remove())}replaceWith(e,t){let r=super.replaceWith(e,t);return this.attributes.copy(r),r}update(e,t){super.update(e,t),e.some(r=>r.target===this.domNode&&r.type==="attributes")&&this.attributes.build()}wrap(e,t){let r=super.wrap(e,t);return r instanceof Ba&&this.attributes.move(r),r}};Oa.allowedChildren=[Oa,dt],Oa.blotName="inline",Oa.scope=X.INLINE_BLOT,Oa.tagName="SPAN";Ez=Oa,bd=Ez,Pa=class wf extends ni{static create(e){return super.create(e)}static formats(e,t){let r=t.query(wf.blotName);if(!(r!=null&&e.tagName===r.tagName)){if(typeof this.tagName=="string")return!0;if(Array.isArray(this.tagName))return e.tagName.toLowerCase()}}constructor(e,t){super(e,t),this.attributes=new yl(this.domNode)}format(e,t){let r=this.scroll.query(e,X.BLOCK);r!=null&&(r instanceof qt?this.attributes.attribute(r,t):e===this.statics.blotName&&!t?this.replaceWith(wf.blotName):t&&(e!==this.statics.blotName||this.formats()[e]!==t)&&this.replaceWith(e,t))}formats(){let e=this.attributes.values(),t=this.statics.formats(this.domNode,this.scroll);return t!=null&&(e[this.statics.blotName]=t),e}formatAt(e,t,r,n){this.scroll.query(r,X.BLOCK)!=null?this.format(r,n):super.formatAt(e,t,r,n)}insertAt(e,t,r){if(r==null||this.scroll.query(t,X.INLINE)!=null)super.insertAt(e,t,r);else{let n=this.split(e);if(n!=null){let s=this.scroll.create(t,r);n.parent.insertBefore(s,n)}else throw new Error("Attempt to insertAt after block boundaries")}}replaceWith(e,t){let r=super.replaceWith(e,t);return this.attributes.copy(r),r}update(e,t){super.update(e,t),e.some(r=>r.target===this.domNode&&r.type==="attributes")&&this.attributes.build()}};Pa.blotName="block",Pa.scope=X.BLOCK_BLOT,Pa.tagName="P",Pa.allowedChildren=[bd,Pa,dt];Sz=Pa,vs=Sz,Mf=class extends ni{checkMerge(){return this.next!==null&&this.next.statics.blotName===this.statics.blotName}deleteAt(e,t){super.deleteAt(e,t),this.enforceAllowedChildren()}formatAt(e,t,r,n){super.formatAt(e,t,r,n),this.enforceAllowedChildren()}insertAt(e,t,r){super.insertAt(e,t,r),this.enforceAllowedChildren()}optimize(e){super.optimize(e),this.children.length>0&&this.next!=null&&this.checkMerge()&&(this.next.moveChildren(this),this.next.remove())}};Mf.blotName="container",Mf.scope=X.BLOCK_BLOT;Az=Mf,qa=Az,Tf=class extends dt{static formats(e,t){}format(e,t){super.formatAt(0,this.length(),e,t)}formatAt(e,t,r,n){e===0&&t===this.length()?this.format(r,n):super.formatAt(e,t,r,n)}formats(){return this.statics.formats(this.domNode,this.scroll)}},Ke=Tf,kz={attributes:!0,characterData:!0,characterDataOldValue:!0,childList:!0,subtree:!0},Cz=100,Fa=class extends ni{constructor(e,t){super(null,t),this.registry=e,this.scroll=this,this.build(),this.observer=new MutationObserver(r=>{this.update(r)}),this.observer.observe(this.domNode,kz),this.attach()}create(e,t){return this.registry.create(this,e,t)}find(e,t=!1){let r=this.registry.find(e,t);return r?r.scroll===this?r:t?this.find(r.scroll.domNode.parentNode,!0):null:null}query(e,t=X.ANY){return this.registry.query(e,t)}register(...e){return this.registry.register(...e)}build(){this.scroll!=null&&super.build()}detach(){super.detach(),this.observer.disconnect()}deleteAt(e,t){this.update(),e===0&&t===this.length()?this.children.forEach(r=>{r.remove()}):super.deleteAt(e,t)}formatAt(e,t,r,n){this.update(),super.formatAt(e,t,r,n)}insertAt(e,t,r){this.update(),super.insertAt(e,t,r)}optimize(e=[],t={}){super.optimize(t);let r=t.mutationsMap||new WeakMap,n=Array.from(this.observer.takeRecords());for(;n.length>0;)e.push(n.pop());let s=(l,h=!0)=>{l==null||l===this||l.domNode.parentNode!=null&&(r.has(l.domNode)||r.set(l.domNode,[]),h&&s(l.parent))},a=l=>{r.has(l.domNode)&&(l instanceof ni&&l.children.forEach(a),r.delete(l.domNode),l.optimize(t))},o=e;for(let l=0;o.length>0;l+=1){if(l>=Cz)throw new Error("[Parchment] Maximum optimize iterations reached");for(o.forEach(h=>{let d=this.find(h.target,!0);d!=null&&(d.domNode===h.target&&(h.type==="childList"?(s(this.find(h.previousSibling,!1)),Array.from(h.addedNodes).forEach(c=>{let f=this.find(c,!1);s(f,!1),f instanceof ni&&f.children.forEach(m=>{s(m,!1)})})):h.type==="attributes"&&s(d.prev)),s(d))}),this.children.forEach(a),o=Array.from(this.observer.takeRecords()),n=o.slice();n.length>0;)e.push(n.pop())}}update(e,t={}){e=e||this.observer.takeRecords();let r=new WeakMap;e.map(n=>{let s=this.find(n.target,!0);return s==null?null:r.has(s.domNode)?(r.get(s.domNode).push(n),null):(r.set(s.domNode,[n]),s)}).forEach(n=>{n!=null&&n!==this&&r.has(n.domNode)&&n.update(r.get(n.domNode)||[],t)}),t.mutationsMap=r,r.has(this.domNode)&&super.update(r.get(this.domNode),t),this.optimize(e,t)}};Fa.blotName="scroll",Fa.defaultChild=vs,Fa.allowedChildren=[vs,qa],Fa.scope=X.BLOCK_BLOT,Fa.tagName="DIV";_z=Fa,vl=_z,Nf=class ex extends dt{static create(e){return document.createTextNode(e)}static value(e){return e.data}constructor(e,t){super(e,t),this.text=this.statics.value(this.domNode)}deleteAt(e,t){this.domNode.data=this.text=this.text.slice(0,e)+this.text.slice(e+t)}index(e,t){return this.domNode===e?t:-1}insertAt(e,t,r){r==null?(this.text=this.text.slice(0,e)+t+this.text.slice(e),this.domNode.data=this.text):super.insertAt(e,t,r)}length(){return this.text.length}optimize(e){super.optimize(e),this.text=this.statics.value(this.domNode),this.text.length===0?this.remove():this.next instanceof ex&&this.next.prev===this&&(this.insertAt(this.length(),this.next.value()),this.next.remove())}position(e,t=!1){return[this.domNode,e]}split(e,t=!1){if(!t){if(e===0)return this;if(e===this.length())return this.next}let r=this.scroll.create(this.domNode.splitText(e));return this.parent.insertBefore(r,this.next||void 0),this.text=this.statics.value(this.domNode),r}update(e,t){e.some(r=>r.type==="characterData"&&r.target===this.domNode)&&(this.text=this.statics.value(this.domNode))}value(){return this.text}};Nf.blotName="text",Nf.scope=X.INLINE_BLOT;Lz=Nf,Ha=Lz});var cx=rr((FZ,dx)=>{var mi=-1,Ht=1,ut=0;function wl(i,e,t,r,n){if(i===e)return i?[[ut,i]]:[];if(t!=null){var s=qz(i,e,t);if(s)return s}var a=Sf(i,e),o=i.substring(0,a);i=i.substring(a),e=e.substring(a),a=wd(i,e);var l=i.substring(i.length-a);i=i.substring(0,i.length-a),e=e.substring(0,e.length-a);var h=Iz(i,e);return o&&h.unshift([ut,o]),l&&h.push([ut,l]),Af(h,n),r&&Dz(h),h}function Iz(i,e){var t;if(!i)return[[Ht,e]];if(!e)return[[mi,i]];var r=i.length>e.length?i:e,n=i.length>e.length?e:i,s=r.indexOf(n);if(s!==-1)return t=[[Ht,r.substring(0,s)],[ut,n],[Ht,r.substring(s+n.length)]],i.length>e.length&&(t[0][0]=t[2][0]=mi),t;if(n.length===1)return[[mi,i],[Ht,e]];var a=Rz(i,e);if(a){var o=a[0],l=a[1],h=a[2],d=a[3],c=a[4],f=wl(o,h),m=wl(l,d);return f.concat([[ut,c]],m)}return zz(i,e)}function zz(i,e){for(var t=i.length,r=e.length,n=Math.ceil((t+r)/2),s=n,a=2*n,o=new Array(a),l=new Array(a),h=0;ht)m+=2;else if(C>r)f+=2;else if(c){var I=s+d-b;if(I>=0&&I=O)return tx(i,e,A,C)}}}for(var q=-y+g;q<=y-x;q+=2){var I=s+q,O;q===-y||q!==y&&l[I-1]t)x+=2;else if(P>r)g+=2;else if(!c){var S=s+d-q;if(S>=0&&S=O)return tx(i,e,A,C)}}}}return[[mi,i],[Ht,e]]}function tx(i,e,t,r){var n=i.substring(0,t),s=e.substring(0,r),a=i.substring(t),o=e.substring(r),l=wl(n,s),h=wl(a,o);return l.concat(h)}function Sf(i,e){if(!i||!e||i.charAt(0)!==e.charAt(0))return 0;for(var t=0,r=Math.min(i.length,e.length),n=r,s=0;tr?i=i.substring(t-r):te.length?i:e,r=i.length>e.length?e:i;if(t.length<4||r.length*2=m.length?[A,C,I,O,S]:null}var s=n(t,r,Math.ceil(t.length/4)),a=n(t,r,Math.ceil(t.length/2)),o;if(!s&&!a)return null;a?s?o=s[4].length>a[4].length?s:a:o=a:o=s;var l,h,d,c;i.length>e.length?(l=o[0],h=o[1],d=o[2],c=o[3]):(d=o[0],c=o[1],l=o[2],h=o[3]);var f=o[4];return[l,h,d,c,f]}function Dz(i){for(var e=!1,t=[],r=0,n=null,s=0,a=0,o=0,l=0,h=0;s0?t[r-1]:-1,a=0,o=0,l=0,h=0,n=null,e=!0)),s++;for(e&&Af(i),Pz(i),s=1;s=m?(f>=d.length/2||f>=c.length/2)&&(i.splice(s,0,[ut,c.substring(0,f)]),i[s-1][1]=d.substring(0,d.length-f),i[s+1][1]=c.substring(f),s++):(m>=d.length/2||m>=c.length/2)&&(i.splice(s,0,[ut,d.substring(0,m)]),i[s-1][0]=Ht,i[s-1][1]=c.substring(0,c.length-m),i[s+1][0]=mi,i[s+1][1]=d.substring(m),s++),s++}s++}}var rx=/[^a-zA-Z0-9]/,nx=/\s/,sx=/[\r\n]/,Oz=/\n\r?\n$/,Bz=/^\r?\n\r?\n/;function Pz(i){function e(m,g){if(!m||!g)return 6;var x=m.charAt(m.length-1),y=g.charAt(0),b=x.match(rx),S=y.match(rx),A=b&&x.match(nx),C=S&&y.match(nx),I=A&&x.match(sx),O=C&&y.match(sx),q=I&&m.match(Oz),P=O&&g.match(Bz);return q||P?5:I||O?4:b&&!A&&C?3:A||C?2:b||S?1:0}for(var t=1;t=c&&(c=f,l=r,h=n,d=s)}i[t-1][1]!=l&&(l?i[t-1][1]=l:(i.splice(t-1,1),t--),i[t][1]=h,d?i[t+1][1]=d:(i.splice(t+1,1),t--))}t++}}function Af(i,e){i.push([ut,""]);for(var t=0,r=0,n=0,s="",a="",o;t=0&&hx(i[l][1])){var h=i[l][1].slice(-1);if(i[l][1]=i[l][1].slice(0,-1),s=h+s,a=h+a,!i[l][1]){i.splice(l,1),t--;var d=l-1;i[d]&&i[d][0]===Ht&&(n++,a=i[d][1]+a,d--),i[d]&&i[d][0]===mi&&(r++,s=i[d][1]+s,d--),l=d}}if(lx(i[t][1])){var h=i[t][1].charAt(0);i[t][1]=i[t][1].slice(1),s+=h,a+=h}}if(t0||a.length>0){s.length>0&&a.length>0&&(o=Sf(a,s),o!==0&&(l>=0?i[l][1]+=a.substring(0,o):(i.splice(0,0,[ut,a.substring(0,o)]),t++),a=a.substring(o),s=s.substring(o)),o=wd(a,s),o!==0&&(i[t][1]=a.substring(a.length-o)+i[t][1],a=a.substring(0,a.length-o),s=s.substring(0,s.length-o)));var c=n+r;s.length===0&&a.length===0?(i.splice(t-c,c),t=t-c):s.length===0?(i.splice(t-c,c,[Ht,a]),t=t-c+1):a.length===0?(i.splice(t-c,c,[mi,s]),t=t-c+1):(i.splice(t-c,c,[mi,s],[Ht,a]),t=t-c+2)}t!==0&&i[t-1][0]===ut?(i[t-1][1]+=i[t][1],i.splice(t,1)):t++,n=0,r=0,s="",a="";break}}i[i.length-1][1]===""&&i.pop();var f=!1;for(t=1;t=55296&&i<=56319}function ox(i){return i>=56320&&i<=57343}function lx(i){return ox(i.charCodeAt(0))}function hx(i){return ax(i.charCodeAt(i.length-1))}function Fz(i){for(var e=[],t=0;t0&&e.push(i[t]);return e}function Ef(i,e,t,r){return hx(i)||lx(r)?null:Fz([[ut,i],[mi,e],[Ht,t],[ut,r]])}function qz(i,e,t){var r=typeof t=="number"?{index:t,length:0}:t.oldRange,n=typeof t=="number"?null:t.newRange,s=i.length,a=e.length;if(r.length===0&&(n===null||n.length===0)){var o=r.index,l=i.slice(0,o),h=i.slice(o),d=n?n.index:null;e:{var c=o+a-s;if(d!==null&&d!==c||c<0||c>a)break e;var f=e.slice(0,c),m=e.slice(c);if(m!==h)break e;var g=Math.min(o,c),x=l.slice(0,g),y=f.slice(0,g);if(x!==y)break e;var b=l.slice(g),S=f.slice(g);return Ef(x,b,S,h)}e:{if(d!==null&&d!==o)break e;var A=o,f=e.slice(0,A),m=e.slice(A);if(f!==l)break e;var C=Math.min(s-A,a-A),I=h.slice(h.length-C),O=m.slice(m.length-C);if(I!==O)break e;var b=h.slice(0,h.length-C),S=m.slice(0,m.length-C);return Ef(l,b,S,I)}}if(r.length>0&&n&&n.length===0)e:{var x=i.slice(0,r.index),I=i.slice(r.index+r.length),g=x.length,C=I.length;if(a{var Hz=200,Tx="__lodash_hash_undefined__",Nx=9007199254740991,Df="[object Arguments]",Uz="[object Array]",Ex="[object Boolean]",Sx="[object Date]",$z="[object Error]",Of="[object Function]",Ax="[object GeneratorFunction]",Td="[object Map]",kx="[object Number]",Bf="[object Object]",ux="[object Promise]",Cx="[object RegExp]",Nd="[object Set]",_x="[object String]",Lx="[object Symbol]",Cf="[object WeakMap]",Ix="[object ArrayBuffer]",Ed="[object DataView]",zx="[object Float32Array]",Rx="[object Float64Array]",Dx="[object Int8Array]",Ox="[object Int16Array]",Bx="[object Int32Array]",Px="[object Uint8Array]",Fx="[object Uint8ClampedArray]",qx="[object Uint16Array]",Hx="[object Uint32Array]",jz=/[\\^$.*+?()[\]{}|]/g,Gz=/\w*$/,Vz=/^\[object .+?Constructor\]$/,Wz=/^(?:0|[1-9]\d*)$/,Pe={};Pe[Df]=Pe[Uz]=Pe[Ix]=Pe[Ed]=Pe[Ex]=Pe[Sx]=Pe[zx]=Pe[Rx]=Pe[Dx]=Pe[Ox]=Pe[Bx]=Pe[Td]=Pe[kx]=Pe[Bf]=Pe[Cx]=Pe[Nd]=Pe[_x]=Pe[Lx]=Pe[Px]=Pe[Fx]=Pe[qx]=Pe[Hx]=!0;Pe[$z]=Pe[Of]=Pe[Cf]=!1;var Yz=typeof global=="object"&&global&&global.Object===Object&&global,Xz=typeof self=="object"&&self&&self.Object===Object&&self,$r=Yz||Xz||Function("return this")(),Ux=typeof Ml=="object"&&Ml&&!Ml.nodeType&&Ml,fx=Ux&&typeof Ua=="object"&&Ua&&!Ua.nodeType&&Ua,Kz=fx&&fx.exports===Ux;function Zz(i,e){return i.set(e[0],e[1]),i}function Qz(i,e){return i.add(e),i}function Jz(i,e){for(var t=-1,r=i?i.length:0;++t-1}function SR(i,e){var t=this.__data__,r=kd(t,i);return r<0?t.push([i,e]):t[r][1]=e,this}jr.prototype.clear=MR;jr.prototype.delete=TR;jr.prototype.get=NR;jr.prototype.has=ER;jr.prototype.set=SR;function $a(i){var e=-1,t=i?i.length:0;for(this.clear();++e-1&&i%1==0&&i-1&&i%1==0&&i<=Nx}function _d(i){var e=typeof i;return!!i&&(e=="object"||e=="function")}function cD(i){return!!i&&typeof i=="object"}function Hf(i){return Zx(i)?BR(i):$R(i)}function uD(){return[]}function fD(){return!1}Ua.exports=aD});var em=rr((El,Wa)=>{var mD=200,Jf="__lodash_hash_undefined__",Pd=1,d9=2,c9=9007199254740991,Ld="[object Arguments]",Vf="[object Array]",pD="[object AsyncFunction]",u9="[object Boolean]",f9="[object Date]",m9="[object Error]",p9="[object Function]",gD="[object GeneratorFunction]",Id="[object Map]",g9="[object Number]",xD="[object Null]",Va="[object Object]",Jx="[object Promise]",yD="[object Proxy]",x9="[object RegExp]",zd="[object Set]",y9="[object String]",vD="[object Symbol]",bD="[object Undefined]",Wf="[object WeakMap]",v9="[object ArrayBuffer]",Rd="[object DataView]",wD="[object Float32Array]",MD="[object Float64Array]",TD="[object Int8Array]",ND="[object Int16Array]",ED="[object Int32Array]",SD="[object Uint8Array]",AD="[object Uint8ClampedArray]",kD="[object Uint16Array]",CD="[object Uint32Array]",_D=/[\\^$.*+?()[\]{}|]/g,LD=/^\[object .+?Constructor\]$/,ID=/^(?:0|[1-9]\d*)$/,Ue={};Ue[wD]=Ue[MD]=Ue[TD]=Ue[ND]=Ue[ED]=Ue[SD]=Ue[AD]=Ue[kD]=Ue[CD]=!0;Ue[Ld]=Ue[Vf]=Ue[v9]=Ue[u9]=Ue[Rd]=Ue[f9]=Ue[m9]=Ue[p9]=Ue[Id]=Ue[g9]=Ue[Va]=Ue[x9]=Ue[zd]=Ue[y9]=Ue[Wf]=!1;var b9=typeof global=="object"&&global&&global.Object===Object&&global,zD=typeof self=="object"&&self&&self.Object===Object&&self,Gr=b9||zD||Function("return this")(),w9=typeof El=="object"&&El&&!El.nodeType&&El,e9=w9&&typeof Wa=="object"&&Wa&&!Wa.nodeType&&Wa,M9=e9&&e9.exports===w9,$f=M9&&b9.process,t9=(function(){try{return $f&&$f.binding&&$f.binding("util")}catch{}})(),i9=t9&&t9.isTypedArray;function RD(i,e){for(var t=-1,r=i==null?0:i.length,n=0,s=[];++t-1}function dO(i,e){var t=this.__data__,r=qd(t,i);return r<0?(++this.size,t.push([i,e])):t[r][1]=e,this}Vr.prototype.clear=aO;Vr.prototype.delete=oO;Vr.prototype.get=lO;Vr.prototype.has=hO;Vr.prototype.set=dO;function Es(i){var e=-1,t=i==null?0:i.length;for(this.clear();++eo))return!1;var h=s.get(i);if(h&&s.get(e))return h==e;var d=-1,c=!0,f=t&d9?new Od:void 0;for(s.set(i,e),s.set(e,i);++d-1&&i%1==0&&i-1&&i%1==0&&i<=c9}function L9(i){var e=typeof i;return i!=null&&(e=="object"||e=="function")}function kl(i){return i!=null&&typeof i=="object"}var I9=i9?PD(i9):AO;function HO(i){return FO(i)?TO(i):kO(i)}function UO(){return[]}function $O(){return!1}Wa.exports=qO});var z9=rr(im=>{"use strict";Object.defineProperty(im,"__esModule",{value:!0});var jO=Uf(),GO=em(),tm;(function(i){function e(s={},a={},o=!1){typeof s!="object"&&(s={}),typeof a!="object"&&(a={});let l=jO(a);o||(l=Object.keys(l).reduce((h,d)=>(l[d]!=null&&(h[d]=l[d]),h),{}));for(let h in s)s[h]!==void 0&&a[h]===void 0&&(l[h]=s[h]);return Object.keys(l).length>0?l:void 0}i.compose=e;function t(s={},a={}){typeof s!="object"&&(s={}),typeof a!="object"&&(a={});let o=Object.keys(s).concat(Object.keys(a)).reduce((l,h)=>(GO(s[h],a[h])||(l[h]=a[h]===void 0?null:a[h]),l),{});return Object.keys(o).length>0?o:void 0}i.diff=t;function r(s={},a={}){s=s||{};let o=Object.keys(a).reduce((l,h)=>(a[h]!==s[h]&&s[h]!==void 0&&(l[h]=a[h]),l),{});return Object.keys(s).reduce((l,h)=>(s[h]!==a[h]&&a[h]===void 0&&(l[h]=null),l),o)}i.invert=r;function n(s,a,o=!1){if(typeof s!="object")return a;if(typeof a!="object")return;if(!o)return a;let l=Object.keys(a).reduce((h,d)=>(s[d]===void 0&&(h[d]=a[d]),h),{});return Object.keys(l).length>0?l:void 0}i.transform=n})(tm||(tm={}));im.default=tm});var sm=rr(nm=>{"use strict";Object.defineProperty(nm,"__esModule",{value:!0});var rm;(function(i){function e(t){return typeof t.delete=="number"?t.delete:typeof t.retain=="number"?t.retain:typeof t.retain=="object"&&t.retain!==null?1:typeof t.insert=="string"?t.insert.length:1}i.length=e})(rm||(rm={}));nm.default=rm});var D9=rr(om=>{"use strict";Object.defineProperty(om,"__esModule",{value:!0});var R9=sm(),am=class{constructor(e){this.ops=e,this.index=0,this.offset=0}hasNext(){return this.peekLength()<1/0}next(e){e||(e=1/0);let t=this.ops[this.index];if(t){let r=this.offset,n=R9.default.length(t);if(e>=n-r?(e=n-r,this.index+=1,this.offset=0):this.offset+=e,typeof t.delete=="number")return{delete:e};{let s={};return t.attributes&&(s.attributes=t.attributes),typeof t.retain=="number"?s.retain=e:typeof t.retain=="object"&&t.retain!==null?s.retain=t.retain:typeof t.insert=="string"?s.insert=t.insert.substr(r,e):s.insert=t.insert,s}}else return{retain:1/0}}peek(){return this.ops[this.index]}peekLength(){return this.ops[this.index]?R9.default.length(this.ops[this.index])-this.offset:1/0}peekType(){let e=this.ops[this.index];return e?typeof e.delete=="number"?"delete":typeof e.retain=="number"||typeof e.retain=="object"&&e.retain!==null?"retain":"insert":"retain"}rest(){if(this.hasNext()){if(this.offset===0)return this.ops.slice(this.index);{let e=this.offset,t=this.index,r=this.next(),n=this.ops.slice(this.index);return this.offset=e,this.index=t,[r].concat(n)}}else return[]}};om.default=am});var pi=rr((Yr,$d)=>{"use strict";Object.defineProperty(Yr,"__esModule",{value:!0});Yr.AttributeMap=Yr.OpIterator=Yr.Op=void 0;var Ud=cx(),VO=Uf(),lm=em(),As=z9();Yr.AttributeMap=As.default;var Wr=sm();Yr.Op=Wr.default;var Ii=D9();Yr.OpIterator=Ii.default;var WO="\0",O9=(i,e)=>{if(typeof i!="object"||i===null)throw new Error(`cannot retain a ${typeof i}`);if(typeof e!="object"||e===null)throw new Error(`cannot retain a ${typeof e}`);let t=Object.keys(i)[0];if(!t||t!==Object.keys(e)[0])throw new Error(`embed types not matched: ${t} != ${Object.keys(e)[0]}`);return[t,i[t],e[t]]},Xr=class i{constructor(e){Array.isArray(e)?this.ops=e:e!=null&&Array.isArray(e.ops)?this.ops=e.ops:this.ops=[]}static registerEmbed(e,t){this.handlers[e]=t}static unregisterEmbed(e){delete this.handlers[e]}static getHandler(e){let t=this.handlers[e];if(!t)throw new Error(`no handlers for embed type "${e}"`);return t}insert(e,t){let r={};return typeof e=="string"&&e.length===0?this:(r.insert=e,t!=null&&typeof t=="object"&&Object.keys(t).length>0&&(r.attributes=t),this.push(r))}delete(e){return e<=0?this:this.push({delete:e})}retain(e,t){if(typeof e=="number"&&e<=0)return this;let r={retain:e};return t!=null&&typeof t=="object"&&Object.keys(t).length>0&&(r.attributes=t),this.push(r)}push(e){let t=this.ops.length,r=this.ops[t-1];if(e=VO(e),typeof r=="object"){if(typeof e.delete=="number"&&typeof r.delete=="number")return this.ops[t-1]={delete:r.delete+e.delete},this;if(typeof r.delete=="number"&&e.insert!=null&&(t-=1,r=this.ops[t-1],typeof r!="object"))return this.ops.unshift(e),this;if(lm(e.attributes,r.attributes)){if(typeof e.insert=="string"&&typeof r.insert=="string")return this.ops[t-1]={insert:r.insert+e.insert},typeof e.attributes=="object"&&(this.ops[t-1].attributes=e.attributes),this;if(typeof e.retain=="number"&&typeof r.retain=="number")return this.ops[t-1]={retain:r.retain+e.retain},typeof e.attributes=="object"&&(this.ops[t-1].attributes=e.attributes),this}}return t===this.ops.length?this.ops.push(e):this.ops.splice(t,0,e),this}chop(){let e=this.ops[this.ops.length-1];return e&&typeof e.retain=="number"&&!e.attributes&&this.ops.pop(),this}filter(e){return this.ops.filter(e)}forEach(e){this.ops.forEach(e)}map(e){return this.ops.map(e)}partition(e){let t=[],r=[];return this.forEach(n=>{(e(n)?t:r).push(n)}),[t,r]}reduce(e,t){return this.ops.reduce(e,t)}changeLength(){return this.reduce((e,t)=>t.insert?e+Wr.default.length(t):t.delete?e-t.delete:e,0)}length(){return this.reduce((e,t)=>e+Wr.default.length(t),0)}slice(e=0,t=1/0){let r=[],n=new Ii.default(this.ops),s=0;for(;s0&&r.next(s.retain-o)}let a=new i(n);for(;t.hasNext()||r.hasNext();)if(r.peekType()==="insert")a.push(r.next());else if(t.peekType()==="delete")a.push(t.next());else{let o=Math.min(t.peekLength(),r.peekLength()),l=t.next(o),h=r.next(o);if(h.retain){let d={};if(typeof l.retain=="number")d.retain=typeof h.retain=="number"?o:h.retain;else if(typeof h.retain=="number")l.retain==null?d.insert=l.insert:d.retain=l.retain;else{let f=l.retain==null?"insert":"retain",[m,g,x]=O9(l[f],h.retain),y=i.getHandler(m);d[f]={[m]:y.compose(g,x,f==="retain")}}let c=As.default.compose(l.attributes,h.attributes,typeof l.retain=="number");if(c&&(d.attributes=c),a.push(d),!r.hasNext()&&lm(a.ops[a.ops.length-1],d)){let f=new i(t.rest());return a.concat(f).chop()}}else typeof h.delete=="number"&&(typeof l.retain=="number"||typeof l.retain=="object"&&l.retain!==null)&&a.push(h)}return a.chop()}concat(e){let t=new i(this.ops.slice());return e.ops.length>0&&(t.push(e.ops[0]),t.ops=t.ops.concat(e.ops.slice(1))),t}diff(e,t){if(this.ops===e.ops)return new i;let r=[this,e].map(l=>l.map(h=>{if(h.insert!=null)return typeof h.insert=="string"?h.insert:WO;let d=l===e?"on":"with";throw new Error("diff() called "+d+" non-document")}).join("")),n=new i,s=Ud(r[0],r[1],t,!0),a=new Ii.default(this.ops),o=new Ii.default(e.ops);return s.forEach(l=>{let h=l[1].length;for(;h>0;){let d=0;switch(l[0]){case Ud.INSERT:d=Math.min(o.peekLength(),h),n.push(o.next(d));break;case Ud.DELETE:d=Math.min(h,a.peekLength()),a.next(d),n.delete(d);break;case Ud.EQUAL:d=Math.min(a.peekLength(),o.peekLength(),h);let c=a.next(d),f=o.next(d);lm(c.insert,f.insert)?n.retain(d,As.default.diff(c.attributes,f.attributes)):n.push(f).delete(d);break}h-=d}}),n.chop()}eachLine(e,t=` -`){let r=new Ii.default(this.ops),n=new i,s=0;for(;r.hasNext();){if(r.peekType()!=="insert")return;let a=r.peek(),o=Wr.default.length(a)-r.peekLength(),l=typeof a.insert=="string"?a.insert.indexOf(t,o)-o:-1;if(l<0)n.push(r.next());else if(l>0)n.push(r.next(l));else{if(e(n,r.next(1).attributes||{},s)===!1)return;s+=1,n=new i}}n.length()>0&&e(n,{},s)}invert(e){let t=new i;return this.reduce((r,n)=>{if(n.insert)t.delete(Wr.default.length(n));else{if(typeof n.retain=="number"&&n.attributes==null)return t.retain(n.retain),r+n.retain;if(n.delete||typeof n.retain=="number"){let s=n.delete||n.retain;return e.slice(r,r+s).forEach(o=>{n.delete?t.push(o):n.retain&&n.attributes&&t.retain(Wr.default.length(o),As.default.invert(n.attributes,o.attributes))}),r+s}else if(typeof n.retain=="object"&&n.retain!==null){let s=e.slice(r,r+1),a=new Ii.default(s.ops).next(),[o,l,h]=O9(n.retain,a.insert),d=i.getHandler(o);return t.retain({[o]:d.invert(l,h)},As.default.invert(n.attributes,a.attributes)),r+1}}return r},0),t.chop()}transform(e,t=!1){if(t=!!t,typeof e=="number")return this.transformPosition(e,t);let r=e,n=new Ii.default(this.ops),s=new Ii.default(r.ops),a=new i;for(;n.hasNext()||s.hasNext();)if(n.peekType()==="insert"&&(t||s.peekType()!=="insert"))a.retain(Wr.default.length(n.next()));else if(s.peekType()==="insert")a.push(s.next());else{let o=Math.min(n.peekLength(),s.peekLength()),l=n.next(o),h=s.next(o);if(l.delete)continue;if(h.delete)a.push(h);else{let d=l.retain,c=h.retain,f=typeof c=="object"&&c!==null?c:o;if(typeof d=="object"&&d!==null&&typeof c=="object"&&c!==null){let m=Object.keys(d)[0];if(m===Object.keys(c)[0]){let g=i.getHandler(m);g&&(f={[m]:g.transform(d[m],c[m],t)})}}a.retain(f,As.default.transform(l.attributes,h.attributes,t))}}return a.chop()}transformPosition(e,t=!1){t=!!t;let r=new Ii.default(this.ops),n=0;for(;r.hasNext()&&n<=e;){let s=r.peekLength(),a=r.peekType();if(r.next(),a==="delete"){e-=Math.min(s,e-n);continue}else a==="insert"&&(n{Re();_l=class extends Ke{static value(){}optimize(){(this.prev||this.next)&&this.remove()}length(){return 0}value(){return""}};_l.blotName="break";_l.tagName="BR";Et=_l});function ks(i){return i.replace(/[&<>"']/g,e=>YO[e])}var nt,YO,Kr=T(()=>{Re();nt=class extends Ha{},YO={"&":"&","<":"<",">":">",'"':""","'":"'"}});var br,hm,St,Zr=T(()=>{Re();Dn();Kr();br=class br extends bd{static compare(e,t){let r=br.order.indexOf(e),n=br.order.indexOf(t);return r>=0||n>=0?r-n:e===t?0:e0){let t=this.parent.isolate(this.offset(),this.length());this.moveChildren(t),t.wrap(this)}}};U(br,"allowedChildren",[br,Et,Ke,nt]),U(br,"order",["cursor","inline","link","underline","strike","italic","bold","script","code"]);hm=br,St=hm});function cm(i){let e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0;return i.descendants(dt).reduce((t,r)=>r.length()===0?t:t.insert(r.value(),ai(r,{},e)),new dm.default).insert(` -`,ai(i))}function ai(i){let e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},t=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!0;return i==null||("formats"in i&&typeof i.formats=="function"&&(e={...e,...i.formats()},t&&delete e["code-token"]),i.parent==null||i.parent.statics.blotName==="scroll"||i.parent.statics.scope!==i.statics.scope)?e:ai(i.parent,e,t)}var dm,B9,De,ft,zi=T(()=>{Re();dm=pt(pi(),1);Dn();Zr();Kr();B9=1,De=class extends vs{constructor(){super(...arguments);U(this,"cache",{})}delta(){return this.cache.delta==null&&(this.cache.delta=cm(this)),this.cache.delta}deleteAt(t,r){super.deleteAt(t,r),this.cache={}}formatAt(t,r,n,s){r<=0||(this.scroll.query(n,X.BLOCK)?t+r===this.length()&&this.format(n,s):super.formatAt(t,Math.min(r,this.length()-t-1),n,s),this.cache={})}insertAt(t,r,n){if(n!=null){super.insertAt(t,r,n),this.cache={};return}if(r.length===0)return;let s=r.split(` -`),a=s.shift();a.length>0&&(t(o=o.split(l,!0),o.insertAt(0,h),h.length),t+a.length)}insertBefore(t,r){let{head:n}=this.children;super.insertBefore(t,r),n instanceof Et&&n.remove(),this.cache={}}length(){return this.cache.length==null&&(this.cache.length=super.length()+B9),this.cache.length}moveChildren(t,r){super.moveChildren(t,r),this.cache={}}optimize(t){super.optimize(t),this.cache={}}path(t){return super.path(t,!0)}removeChild(t){super.removeChild(t),this.cache={}}split(t){let r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;if(r&&(t===0||t>=this.length()-B9)){let s=this.clone();return t===0?(this.parent.insertBefore(s,this),this):(this.parent.insertBefore(s,this.next),s)}let n=super.split(t,r);return this.cache={},n}};De.blotName="block";De.tagName="P";De.defaultChild=Et;De.allowedChildren=[Et,St,Ke,nt];ft=class extends Ke{attach(){super.attach(),this.attributes=new yl(this.domNode)}delta(){return new dm.default().insert(this.value(),{...this.formats(),...this.attributes.values()})}format(e,t){let r=this.scroll.query(e,X.BLOCK_ATTRIBUTE);r!=null&&this.attributes.attribute(r,t)}formatAt(e,t,r,n){this.format(r,n)}insertAt(e,t,r){if(r!=null){super.insertAt(e,t,r);return}let n=t.split(` -`),s=n.pop(),a=n.map(l=>{let h=this.scroll.create(De.blotName);return h.insertAt(0,l),h}),o=this.split(e);a.forEach(l=>{this.parent.insertBefore(l,o)}),s&&this.parent.insertBefore(this.scroll.create("text",s),o)}};ft.scope=X.BLOCK_BLOT});var Ri,um,On,Ll=T(()=>{Re();Kr();Ri=class Ri extends Ke{static value(){}constructor(e,t,r){super(e,t),this.selection=r,this.textNode=document.createTextNode(Ri.CONTENTS),this.domNode.appendChild(this.textNode),this.savedLength=0}detach(){this.parent!=null&&this.parent.removeChild(this)}format(e,t){if(this.savedLength!==0){super.format(e,t);return}let r=this,n=0;for(;r!=null&&r.statics.scope!==X.BLOCK_BLOT;)n+=r.offset(r.parent),r=r.parent;r!=null&&(this.savedLength=Ri.CONTENTS.length,r.optimize(),r.formatAt(n,Ri.CONTENTS.length,e,t),this.savedLength=0)}index(e,t){return e===this.textNode?0:super.index(e,t)}length(){return this.savedLength}position(){return[this.textNode,this.textNode.data.length]}remove(){super.remove(),this.parent=null}restore(){if(this.selection.composing||this.parent==null)return null;let e=this.selection.getNativeRange();for(;this.domNode.lastChild!=null&&this.domNode.lastChild!==this.textNode;)this.domNode.parentNode.insertBefore(this.domNode.lastChild,this.domNode);let t=this.prev instanceof nt?this.prev:null,r=t?t.length():0,n=this.next instanceof nt?this.next:null,s=n?n.text:"",{textNode:a}=this,o=a.data.split(Ri.CONTENTS).join("");a.data=Ri.CONTENTS;let l;if(t)l=t,(o||n)&&(t.insertAt(t.length(),o+s),n&&n.remove());else if(n)l=n,n.insertAt(0,o);else{let h=document.createTextNode(o);l=this.scroll.create(h),this.parent.insertBefore(l,this)}if(this.remove(),e){let h=(f,m)=>t&&f===t.domNode?m:f===a?r+m-1:n&&f===n.domNode?r+o.length+m:null,d=h(e.start.node,e.start.offset),c=h(e.end.node,e.end.offset);if(d!==null&&c!==null)return{startNode:l.domNode,startOffset:d,endNode:l.domNode,endOffset:c}}return null}update(e,t){if(e.some(r=>r.type==="characterData"&&r.target===this.textNode)){let r=this.restore();r&&(t.range=r)}}optimize(e){super.optimize(e);let{parent:t}=this;for(;t;){if(t.domNode.tagName==="A"){this.savedLength=Ri.CONTENTS.length,t.isolate(this.offset(t),this.length()).unwrap(),this.savedLength=0;break}t=t.parent}}value(){return""}};U(Ri,"blotName","cursor"),U(Ri,"className","ql-cursor"),U(Ri,"tagName","span"),U(Ri,"CONTENTS","\uFEFF");um=Ri,On=um});var F9=rr((lQ,fm)=>{"use strict";var XO=Object.prototype.hasOwnProperty,Ut="~";function Il(){}Object.create&&(Il.prototype=Object.create(null),new Il().__proto__||(Ut=!1));function KO(i,e,t){this.fn=i,this.context=e,this.once=t||!1}function P9(i,e,t,r,n){if(typeof t!="function")throw new TypeError("The listener must be a function");var s=new KO(t,r||i,n),a=Ut?Ut+e:e;return i._events[a]?i._events[a].fn?i._events[a]=[i._events[a],s]:i._events[a].push(s):(i._events[a]=s,i._eventsCount++),i}function jd(i,e){--i._eventsCount===0?i._events=new Il:delete i._events[e]}function At(){this._events=new Il,this._eventsCount=0}At.prototype.eventNames=function(){var e=[],t,r;if(this._eventsCount===0)return e;for(r in t=this._events)XO.call(t,r)&&e.push(Ut?r.slice(1):r);return Object.getOwnPropertySymbols?e.concat(Object.getOwnPropertySymbols(t)):e};At.prototype.listeners=function(e){var t=Ut?Ut+e:e,r=this._events[t];if(!r)return[];if(r.fn)return[r.fn];for(var n=0,s=r.length,a=new Array(s);n{mm=pt(F9(),1)});var zl,pm=T(()=>{zl=new WeakMap});function H9(i){if(xm&&gm.indexOf(i)<=gm.indexOf(xm)){for(var e=arguments.length,t=new Array(e>1?e-1:0),r=1;r(e[t]=H9.bind(console,t,i),e),{})}var gm,xm,gi,Cs=T(()=>{gm=["error","warn","log","info"],xm="warn";ym.level=i=>{xm=i};H9.level=ym.level;gi=ym});var vm,ZO,Rl,J,Qr=T(()=>{q9();pm();Cs();vm=gi("quill:events"),ZO=["selectionchange","mousedown","mouseup","click"];ZO.forEach(i=>{document.addEventListener(i,function(){for(var e=arguments.length,t=new Array(e),r=0;r{let s=zl.get(n);s&&s.emitter&&s.emitter.handleDOM(...t)})})});Rl=class extends mm.default{constructor(){super(),this.domListeners={},this.on("error",vm.error)}emit(){for(var e=arguments.length,t=new Array(e),r=0;r1?t-1:0),n=1;n{let{node:a,handler:o}=s;(e.target===a||a.contains(e.target))&&o(e,...r)})}listenDOM(e,t,r){this.domListeners[e]||(this.domListeners[e]=[]),this.domListeners[e].push({node:t,handler:r})}};U(Rl,"events",{EDITOR_CHANGE:"editor-change",SCROLL_BEFORE_UPDATE:"scroll-before-update",SCROLL_BLOT_MOUNT:"scroll-blot-mount",SCROLL_BLOT_UNMOUNT:"scroll-blot-unmount",SCROLL_OPTIMIZE:"scroll-optimize",SCROLL_UPDATE:"scroll-update",SCROLL_EMBED_UPDATE:"scroll-embed-update",SELECTION_CHANGE:"selection-change",TEXT_CHANGE:"text-change",COMPOSITION_BEFORE_START:"composition-before-start",COMPOSITION_START:"composition-start",COMPOSITION_BEFORE_END:"composition-before-end",COMPOSITION_END:"composition-end"}),U(Rl,"sources",{API:"api",SILENT:"silent",USER:"user"});J=Rl});function wm(i,e){try{e.parentNode}catch{return!1}return i.contains(e)}var bm,$t,Mm,U9,Dl=T(()=>{Re();kn();Qr();Cs();bm=gi("quill:selection"),$t=class{constructor(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0;this.index=e,this.length=t}},Mm=class{constructor(e,t){this.emitter=t,this.scroll=e,this.composing=!1,this.mouseDown=!1,this.root=this.scroll.domNode,this.cursor=this.scroll.create("cursor",this),this.savedRange=new $t(0,0),this.lastRange=this.savedRange,this.lastNative=null,this.handleComposition(),this.handleDragging(),this.emitter.listenDOM("selectionchange",document,()=>{!this.mouseDown&&!this.composing&&setTimeout(this.update.bind(this,J.sources.USER),1)}),this.emitter.on(J.events.SCROLL_BEFORE_UPDATE,()=>{if(!this.hasFocus())return;let r=this.getNativeRange();r!=null&&r.start.node!==this.cursor.textNode&&this.emitter.once(J.events.SCROLL_UPDATE,(n,s)=>{try{this.root.contains(r.start.node)&&this.root.contains(r.end.node)&&this.setNativeRange(r.start.node,r.start.offset,r.end.node,r.end.offset);let a=s.some(o=>o.type==="characterData"||o.type==="childList"||o.type==="attributes"&&o.target===this.root);this.update(a?J.sources.SILENT:n)}catch{}})}),this.emitter.on(J.events.SCROLL_OPTIMIZE,(r,n)=>{if(n.range){let{startNode:s,startOffset:a,endNode:o,endOffset:l}=n.range;this.setNativeRange(s,a,o,l),this.update(J.sources.SILENT)}}),this.update(J.sources.SILENT)}handleComposition(){this.emitter.on(J.events.COMPOSITION_BEFORE_START,()=>{this.composing=!0}),this.emitter.on(J.events.COMPOSITION_END,()=>{if(this.composing=!1,this.cursor.parent){let e=this.cursor.restore();if(!e)return;setTimeout(()=>{this.setNativeRange(e.startNode,e.startOffset,e.endNode,e.endOffset)},1)}})}handleDragging(){this.emitter.listenDOM("mousedown",document.body,()=>{this.mouseDown=!0}),this.emitter.listenDOM("mouseup",document.body,()=>{this.mouseDown=!1,this.update(J.sources.USER)})}focus(){this.hasFocus()||(this.root.focus({preventScroll:!0}),this.setRange(this.savedRange))}format(e,t){this.scroll.update();let r=this.getNativeRange();if(!(r==null||!r.native.collapsed||this.scroll.query(e,X.BLOCK))){if(r.start.node!==this.cursor.textNode){let n=this.scroll.find(r.start.node,!1);if(n==null)return;if(n instanceof dt){let s=n.split(r.start.offset);n.parent.insertBefore(this.cursor,s)}else n.insertBefore(this.cursor,r.start.node);this.cursor.attach()}this.cursor.format(e,t),this.scroll.optimize(),this.setNativeRange(this.cursor.textNode,this.cursor.textNode.data.length),this.update()}}getBounds(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,r=this.scroll.length();e=Math.min(e,r-1),t=Math.min(e+t,r-1)-e;let n,[s,a]=this.scroll.leaf(e);if(s==null)return null;if(t>0&&a===s.length()){let[d]=this.scroll.leaf(e+1);if(d){let[c]=this.scroll.line(e),[f]=this.scroll.line(e+1);c===f&&(s=d,a=0)}}[n,a]=s.position(a,!0);let o=document.createRange();if(t>0)return o.setStart(n,a),[s,a]=this.scroll.leaf(e+t),s==null?null:([n,a]=s.position(a,!0),o.setEnd(n,a),o.getBoundingClientRect());let l="left",h;if(n instanceof Text){if(!n.data.length)return null;a0&&(l="right")}return{bottom:h.top+h.height,height:h.height,left:h[l],right:h[l],top:h.top,width:0}}getNativeRange(){let e=document.getSelection();if(e==null||e.rangeCount<=0)return null;let t=e.getRangeAt(0);if(t==null)return null;let r=this.normalizeNative(t);return bm.info("getNativeRange",r),r}getRange(){let e=this.scroll.domNode;if("isConnected"in e&&!e.isConnected)return[null,null];let t=this.getNativeRange();return t==null?[null,null]:[this.normalizedToRange(t),t]}hasFocus(){return document.activeElement===this.root||document.activeElement!=null&&wm(this.root,document.activeElement)}normalizedToRange(e){let t=[[e.start.node,e.start.offset]];e.native.collapsed||t.push([e.end.node,e.end.offset]);let r=t.map(a=>{let[o,l]=a,h=this.scroll.find(o,!0),d=h.offset(this.scroll);return l===0?d:h instanceof dt?d+h.index(o,l):d+h.length()}),n=Math.min(Math.max(...r),this.scroll.length()-1),s=Math.min(n,...r);return new $t(s,n-s)}normalizeNative(e){if(!wm(this.root,e.startContainer)||!e.collapsed&&!wm(this.root,e.endContainer))return null;let t={start:{node:e.startContainer,offset:e.startOffset},end:{node:e.endContainer,offset:e.endOffset},native:e};return[t.start,t.end].forEach(r=>{let{node:n,offset:s}=r;for(;!(n instanceof Text)&&n.childNodes.length>0;)if(n.childNodes.length>s)n=n.childNodes[s],s=0;else if(n.childNodes.length===s)n=n.lastChild,n instanceof Text?s=n.data.length:n.childNodes.length>0?s=n.childNodes.length:s=n.childNodes.length+1;else break;r.node=n,r.offset=s}),t}rangeToNative(e){let t=this.scroll.length(),r=(n,s)=>{n=Math.min(t-1,n);let[a,o]=this.scroll.leaf(n);return a?a.position(o,s):[null,-1]};return[...r(e.index,!1),...r(e.index+e.length,!0)]}setNativeRange(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e,n=arguments.length>3&&arguments[3]!==void 0?arguments[3]:t,s=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!1;if(bm.info("setNativeRange",e,t,r,n),e!=null&&(this.root.parentNode==null||e.parentNode==null||r.parentNode==null))return;let a=document.getSelection();if(a!=null)if(e!=null){this.hasFocus()||this.root.focus({preventScroll:!0});let{native:o}=this.getNativeRange()||{};if(o==null||s||e!==o.startContainer||t!==o.startOffset||r!==o.endContainer||n!==o.endOffset){e instanceof Element&&e.tagName==="BR"&&(t=Array.from(e.parentNode.childNodes).indexOf(e),e=e.parentNode),r instanceof Element&&r.tagName==="BR"&&(n=Array.from(r.parentNode.childNodes).indexOf(r),r=r.parentNode);let l=document.createRange();l.setStart(e,t),l.setEnd(r,n),a.removeAllRanges(),a.addRange(l)}}else a.removeAllRanges(),this.root.blur()}setRange(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1,r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:J.sources.API;if(typeof t=="string"&&(r=t,t=!1),bm.info("setRange",e),e!=null){let n=this.rangeToNative(e);this.setNativeRange(...n,t)}else this.setNativeRange(null);this.update(r)}update(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:J.sources.USER,t=this.lastRange,[r,n]=this.getRange();if(this.lastRange=r,this.lastNative=n,this.lastRange!=null&&(this.savedRange=this.lastRange),!ys(t,this.lastRange)){if(!this.composing&&n!=null&&n.native.collapsed&&n.start.node!==this.cursor.textNode){let a=this.cursor.restore();a&&this.setNativeRange(a.startNode,a.startOffset,a.endNode,a.endOffset)}let s=[J.events.SELECTION_CHANGE,yr(this.lastRange),yr(t),e];this.emitter.emit(J.events.EDITOR_CHANGE,...s),e!==J.sources.SILENT&&this.emitter.emit(...s)}}};U9=Mm});function Xa(i,e,t){if(i.length===0){let[m]=Tm(t.pop());return e<=0?``:`${Xa([],e-1,t)}`}let[{child:r,offset:n,length:s,indent:a,type:o},...l]=i,[h,d]=Tm(o);if(a>e)return t.push(o),a===e+1?`<${h}>${Ol(r,n,s)}${Xa(l,a,t)}`:`<${h}>
  • ${Xa(i,e+1,t)}`;let c=t[t.length-1];if(a===e&&o===c)return`
  • ${Ol(r,n,s)}${Xa(l,a,t)}`;let[f]=Tm(t.pop());return`${Xa(i,e-1,t)}`}function Ol(i,e,t){let r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1;if("html"in i&&typeof i.html=="function")return i.html(e,t);if(i instanceof nt)return ks(i.value().slice(e,e+t)).replaceAll(" "," ");if(i instanceof ni){if(i.statics.blotName==="list-container"){let h=[];return i.children.forEachAt(e,t,(d,c,f)=>{let m="formats"in d&&typeof d.formats=="function"?d.formats():{};h.push({child:d,offset:c,length:f,indent:m.indent||0,type:m.list})}),Xa(h,-1,[])}let n=[];if(i.children.forEachAt(e,t,(h,d,c)=>{n.push(Ol(h,d,c))}),r||i.statics.blotName==="list")return n.join("");let{outerHTML:s,innerHTML:a}=i.domNode,[o,l]=s.split(`>${a}<`);return o==="${n.join("")}<${l}`:`${o}>${n.join("")}<${l}`}return i.domNode instanceof Element?i.domNode.outerHTML:""}function JO(i,e){return Object.keys(e).reduce((t,r)=>{if(i[r]==null)return t;let n=e[r];return n===i[r]?t[r]=n:Array.isArray(n)?n.indexOf(i[r])<0?t[r]=n.concat([i[r]]):t[r]=n:t[r]=[n,i[r]],t},{})}function Tm(i){let e=i==="ordered"?"ol":"ul";switch(i){case"checked":return[e,' data-list="checked"'];case"unchecked":return[e,' data-list="unchecked"'];default:return[e,""]}}function $9(i){return i.reduce((e,t)=>{if(typeof t.insert=="string"){let r=t.insert.replace(/\r\n/g,` +?)[ \r ]*`,_1="[\u0300-\u036F]",mk=new RegExp(_1+"+$"),pk="("+pg+"+)|"+(fk+"|")+"([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]"+(_1+"*")+"|[\uD800-\uDBFF][\uDC00-\uDFFF]"+(_1+"*")+"|\\\\verb\\*([^]).*?\\4|\\\\verb([^*a-zA-Z]).*?\\5"+("|"+uk)+("|"+ck+")"),Oh=class{constructor(e,t){this.input=void 0,this.settings=void 0,this.tokenRegex=void 0,this.catcodes=void 0,this.input=e,this.settings=t,this.tokenRegex=new RegExp(pk,"g"),this.catcodes={"%":14,"~":13}}setCatcode(e,t){this.catcodes[e]=t}lex(){var e=this.input,t=this.tokenRegex.lastIndex;if(t===e.length)return new mi("EOF",new ti(this,t,t));var r=this.tokenRegex.exec(e);if(r===null||r.index!==t)throw new G("Unexpected character: '"+e[t]+"'",new mi(e[t],new ti(this,t,t+1)));var n=r[6]||r[3]||(r[2]?"\\ ":" ");if(this.catcodes[n]===14){var s=e.indexOf(` +`,this.tokenRegex.lastIndex);return s===-1?(this.tokenRegex.lastIndex=e.length,this.settings.reportNonstrict("commentAtEnd","% comment has no terminating newline; LaTeX would fail because of commenting the end of math mode (e.g. $)")):this.tokenRegex.lastIndex=s+1,this.lex()}return new mi(n,new ti(this,t,this.tokenRegex.lastIndex))}},L1=class{constructor(e,t){e===void 0&&(e={}),t===void 0&&(t={}),this.current=void 0,this.builtins=void 0,this.undefStack=void 0,this.current=t,this.builtins=e,this.undefStack=[]}beginGroup(){this.undefStack.push({})}endGroup(){if(this.undefStack.length===0)throw new G("Unbalanced namespace destruction: attempt to pop global namespace; please report this as a bug");var e=this.undefStack.pop();for(var t in e)e.hasOwnProperty(t)&&(e[t]==null?delete this.current[t]:this.current[t]=e[t])}endGroups(){for(;this.undefStack.length>0;)this.endGroup()}has(e){return this.current.hasOwnProperty(e)||this.builtins.hasOwnProperty(e)}get(e){return this.current.hasOwnProperty(e)?this.current[e]:this.builtins[e]}set(e,t,r){if(r===void 0&&(r=!1),r){for(var n=0;n0&&(this.undefStack[this.undefStack.length-1][e]=t)}else{var s=this.undefStack[this.undefStack.length-1];s&&!s.hasOwnProperty(e)&&(s[e]=this.current[e])}t==null?delete this.current[e]:this.current[e]=t}},gk=ng;M("\\noexpand",function(i){var e=i.popToken();return i.isExpandable(e.text)&&(e.noexpand=!0,e.treatAsRelax=!0),{tokens:[e],numArgs:0}});M("\\expandafter",function(i){var e=i.popToken();return i.expandOnce(!0),{tokens:[e],numArgs:0}});M("\\@firstoftwo",function(i){var e=i.consumeArgs(2);return{tokens:e[0],numArgs:0}});M("\\@secondoftwo",function(i){var e=i.consumeArgs(2);return{tokens:e[1],numArgs:0}});M("\\@ifnextchar",function(i){var e=i.consumeArgs(3);i.consumeSpaces();var t=i.future();return e[0].length===1&&e[0][0].text===t.text?{tokens:e[1],numArgs:0}:{tokens:e[2],numArgs:0}});M("\\@ifstar","\\@ifnextchar *{\\@firstoftwo{#1}}");M("\\TextOrMath",function(i){var e=i.consumeArgs(2);return i.mode==="text"?{tokens:e[0],numArgs:0}:{tokens:e[1],numArgs:0}});M4={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,e:14,E:14,f:15,F:15};M("\\char",function(i){var e=i.popToken(),t,r="";if(e.text==="'")t=8,e=i.popToken();else if(e.text==='"')t=16,e=i.popToken();else if(e.text==="`")if(e=i.popToken(),e.text[0]==="\\")r=e.text.charCodeAt(1);else{if(e.text==="EOF")throw new G("\\char` missing argument");r=e.text.charCodeAt(0)}else t=10;if(t){if(r=M4[e.text],r==null||r>=t)throw new G("Invalid base-"+t+" digit "+e.text);for(var n;(n=M4[i.future().text])!=null&&n{var n=i.consumeArg().tokens;if(n.length!==1)throw new G("\\newcommand's first argument must be a macro name");var s=n[0].text,a=i.isDefined(s);if(a&&!e)throw new G("\\newcommand{"+s+"} attempting to redefine "+(s+"; use \\renewcommand"));if(!a&&!t)throw new G("\\renewcommand{"+s+"} when command "+s+" does not yet exist; use \\newcommand");var o=0;if(n=i.consumeArg().tokens,n.length===1&&n[0].text==="["){for(var l="",h=i.expandNextToken();h.text!=="]"&&h.text!=="EOF";)l+=h.text,h=i.expandNextToken();if(!l.match(/^\s*[0-9]+\s*$/))throw new G("Invalid number of arguments: "+l);o=parseInt(l),n=i.consumeArg().tokens}return a&&r||i.macros.set(s,{tokens:n,numArgs:o}),""};M("\\newcommand",i=>V1(i,!1,!0,!1));M("\\renewcommand",i=>V1(i,!0,!1,!1));M("\\providecommand",i=>V1(i,!0,!0,!0));M("\\message",i=>{var e=i.consumeArgs(1)[0];return console.log(e.reverse().map(t=>t.text).join("")),""});M("\\errmessage",i=>{var e=i.consumeArgs(1)[0];return console.error(e.reverse().map(t=>t.text).join("")),""});M("\\show",i=>{var e=i.popToken(),t=e.text;return console.log(e,i.macros.get(t),gn[t],Be.math[t],Be.text[t]),""});M("\\bgroup","{");M("\\egroup","}");M("~","\\nobreakspace");M("\\lq","`");M("\\rq","'");M("\\aa","\\r a");M("\\AA","\\r A");M("\\textcopyright","\\html@mathml{\\textcircled{c}}{\\char`\xA9}");M("\\copyright","\\TextOrMath{\\textcopyright}{\\text{\\textcopyright}}");M("\\textregistered","\\html@mathml{\\textcircled{\\scriptsize R}}{\\char`\xAE}");M("\u212C","\\mathscr{B}");M("\u2130","\\mathscr{E}");M("\u2131","\\mathscr{F}");M("\u210B","\\mathscr{H}");M("\u2110","\\mathscr{I}");M("\u2112","\\mathscr{L}");M("\u2133","\\mathscr{M}");M("\u211B","\\mathscr{R}");M("\u212D","\\mathfrak{C}");M("\u210C","\\mathfrak{H}");M("\u2128","\\mathfrak{Z}");M("\\Bbbk","\\Bbb{k}");M("\xB7","\\cdotp");M("\\llap","\\mathllap{\\textrm{#1}}");M("\\rlap","\\mathrlap{\\textrm{#1}}");M("\\clap","\\mathclap{\\textrm{#1}}");M("\\mathstrut","\\vphantom{(}");M("\\underbar","\\underline{\\text{#1}}");M("\\not",'\\html@mathml{\\mathrel{\\mathrlap\\@not}}{\\char"338}');M("\\neq","\\html@mathml{\\mathrel{\\not=}}{\\mathrel{\\char`\u2260}}");M("\\ne","\\neq");M("\u2260","\\neq");M("\\notin","\\html@mathml{\\mathrel{{\\in}\\mathllap{/\\mskip1mu}}}{\\mathrel{\\char`\u2209}}");M("\u2209","\\notin");M("\u2258","\\html@mathml{\\mathrel{=\\kern{-1em}\\raisebox{0.4em}{$\\scriptsize\\frown$}}}{\\mathrel{\\char`\u2258}}");M("\u2259","\\html@mathml{\\stackrel{\\tiny\\wedge}{=}}{\\mathrel{\\char`\u2258}}");M("\u225A","\\html@mathml{\\stackrel{\\tiny\\vee}{=}}{\\mathrel{\\char`\u225A}}");M("\u225B","\\html@mathml{\\stackrel{\\scriptsize\\star}{=}}{\\mathrel{\\char`\u225B}}");M("\u225D","\\html@mathml{\\stackrel{\\tiny\\mathrm{def}}{=}}{\\mathrel{\\char`\u225D}}");M("\u225E","\\html@mathml{\\stackrel{\\tiny\\mathrm{m}}{=}}{\\mathrel{\\char`\u225E}}");M("\u225F","\\html@mathml{\\stackrel{\\tiny?}{=}}{\\mathrel{\\char`\u225F}}");M("\u27C2","\\perp");M("\u203C","\\mathclose{!\\mkern-0.8mu!}");M("\u220C","\\notni");M("\u231C","\\ulcorner");M("\u231D","\\urcorner");M("\u231E","\\llcorner");M("\u231F","\\lrcorner");M("\xA9","\\copyright");M("\xAE","\\textregistered");M("\uFE0F","\\textregistered");M("\\ulcorner",'\\html@mathml{\\@ulcorner}{\\mathop{\\char"231c}}');M("\\urcorner",'\\html@mathml{\\@urcorner}{\\mathop{\\char"231d}}');M("\\llcorner",'\\html@mathml{\\@llcorner}{\\mathop{\\char"231e}}');M("\\lrcorner",'\\html@mathml{\\@lrcorner}{\\mathop{\\char"231f}}');M("\\vdots","{\\varvdots\\rule{0pt}{15pt}}");M("\u22EE","\\vdots");M("\\varGamma","\\mathit{\\Gamma}");M("\\varDelta","\\mathit{\\Delta}");M("\\varTheta","\\mathit{\\Theta}");M("\\varLambda","\\mathit{\\Lambda}");M("\\varXi","\\mathit{\\Xi}");M("\\varPi","\\mathit{\\Pi}");M("\\varSigma","\\mathit{\\Sigma}");M("\\varUpsilon","\\mathit{\\Upsilon}");M("\\varPhi","\\mathit{\\Phi}");M("\\varPsi","\\mathit{\\Psi}");M("\\varOmega","\\mathit{\\Omega}");M("\\substack","\\begin{subarray}{c}#1\\end{subarray}");M("\\colon","\\nobreak\\mskip2mu\\mathpunct{}\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu\\relax");M("\\boxed","\\fbox{$\\displaystyle{#1}$}");M("\\iff","\\DOTSB\\;\\Longleftrightarrow\\;");M("\\implies","\\DOTSB\\;\\Longrightarrow\\;");M("\\impliedby","\\DOTSB\\;\\Longleftarrow\\;");M("\\dddot","{\\overset{\\raisebox{-0.1ex}{\\normalsize ...}}{#1}}");M("\\ddddot","{\\overset{\\raisebox{-0.1ex}{\\normalsize ....}}{#1}}");T4={",":"\\dotsc","\\not":"\\dotsb","+":"\\dotsb","=":"\\dotsb","<":"\\dotsb",">":"\\dotsb","-":"\\dotsb","*":"\\dotsb",":":"\\dotsb","\\DOTSB":"\\dotsb","\\coprod":"\\dotsb","\\bigvee":"\\dotsb","\\bigwedge":"\\dotsb","\\biguplus":"\\dotsb","\\bigcap":"\\dotsb","\\bigcup":"\\dotsb","\\prod":"\\dotsb","\\sum":"\\dotsb","\\bigotimes":"\\dotsb","\\bigoplus":"\\dotsb","\\bigodot":"\\dotsb","\\bigsqcup":"\\dotsb","\\And":"\\dotsb","\\longrightarrow":"\\dotsb","\\Longrightarrow":"\\dotsb","\\longleftarrow":"\\dotsb","\\Longleftarrow":"\\dotsb","\\longleftrightarrow":"\\dotsb","\\Longleftrightarrow":"\\dotsb","\\mapsto":"\\dotsb","\\longmapsto":"\\dotsb","\\hookrightarrow":"\\dotsb","\\doteq":"\\dotsb","\\mathbin":"\\dotsb","\\mathrel":"\\dotsb","\\relbar":"\\dotsb","\\Relbar":"\\dotsb","\\xrightarrow":"\\dotsb","\\xleftarrow":"\\dotsb","\\DOTSI":"\\dotsi","\\int":"\\dotsi","\\oint":"\\dotsi","\\iint":"\\dotsi","\\iiint":"\\dotsi","\\iiiint":"\\dotsi","\\idotsint":"\\dotsi","\\DOTSX":"\\dotsx"};M("\\dots",function(i){var e="\\dotso",t=i.expandAfterFuture().text;return t in T4?e=T4[t]:(t.slice(0,4)==="\\not"||t in Be.math&&["bin","rel"].includes(Be.math[t].group))&&(e="\\dotsb"),e});Y1={")":!0,"]":!0,"\\rbrack":!0,"\\}":!0,"\\rbrace":!0,"\\rangle":!0,"\\rceil":!0,"\\rfloor":!0,"\\rgroup":!0,"\\rmoustache":!0,"\\right":!0,"\\bigr":!0,"\\biggr":!0,"\\Bigr":!0,"\\Biggr":!0,$:!0,";":!0,".":!0,",":!0};M("\\dotso",function(i){var e=i.future().text;return e in Y1?"\\ldots\\,":"\\ldots"});M("\\dotsc",function(i){var e=i.future().text;return e in Y1&&e!==","?"\\ldots\\,":"\\ldots"});M("\\cdots",function(i){var e=i.future().text;return e in Y1?"\\@cdots\\,":"\\@cdots"});M("\\dotsb","\\cdots");M("\\dotsm","\\cdots");M("\\dotsi","\\!\\cdots");M("\\dotsx","\\ldots\\,");M("\\DOTSI","\\relax");M("\\DOTSB","\\relax");M("\\DOTSX","\\relax");M("\\tmspace","\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax");M("\\,","\\tmspace+{3mu}{.1667em}");M("\\thinspace","\\,");M("\\>","\\mskip{4mu}");M("\\:","\\tmspace+{4mu}{.2222em}");M("\\medspace","\\:");M("\\;","\\tmspace+{5mu}{.2777em}");M("\\thickspace","\\;");M("\\!","\\tmspace-{3mu}{.1667em}");M("\\negthinspace","\\!");M("\\negmedspace","\\tmspace-{4mu}{.2222em}");M("\\negthickspace","\\tmspace-{5mu}{.277em}");M("\\enspace","\\kern.5em ");M("\\enskip","\\hskip.5em\\relax");M("\\quad","\\hskip1em\\relax");M("\\qquad","\\hskip2em\\relax");M("\\tag","\\@ifstar\\tag@literal\\tag@paren");M("\\tag@paren","\\tag@literal{({#1})}");M("\\tag@literal",i=>{if(i.macros.get("\\df@tag"))throw new G("Multiple \\tag");return"\\gdef\\df@tag{\\text{#1}}"});M("\\bmod","\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}");M("\\pod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)");M("\\pmod","\\pod{{\\rm mod}\\mkern6mu#1}");M("\\mod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1");M("\\newline","\\\\\\relax");M("\\TeX","\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}");gg=V(lr["Main-Regular"][84][1]-.7*lr["Main-Regular"][65][1]);M("\\LaTeX","\\textrm{\\html@mathml{"+("L\\kern-.36em\\raisebox{"+gg+"}{\\scriptstyle A}")+"\\kern-.15em\\TeX}{LaTeX}}");M("\\KaTeX","\\textrm{\\html@mathml{"+("K\\kern-.17em\\raisebox{"+gg+"}{\\scriptstyle A}")+"\\kern-.15em\\TeX}{KaTeX}}");M("\\hspace","\\@ifstar\\@hspacer\\@hspace");M("\\@hspace","\\hskip #1\\relax");M("\\@hspacer","\\rule{0pt}{0pt}\\hskip #1\\relax");M("\\ordinarycolon",":");M("\\vcentcolon","\\mathrel{\\mathop\\ordinarycolon}");M("\\dblcolon",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char"2237}}');M("\\coloneqq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2254}}');M("\\Coloneqq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2237\\char"3d}}');M("\\coloneq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"3a\\char"2212}}');M("\\Coloneq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"2237\\char"2212}}');M("\\eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2255}}');M("\\Eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"3d\\char"2237}}');M("\\eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2239}}');M("\\Eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"2212\\char"2237}}');M("\\colonapprox",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"3a\\char"2248}}');M("\\Colonapprox",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"2237\\char"2248}}');M("\\colonsim",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"3a\\char"223c}}');M("\\Colonsim",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"2237\\char"223c}}');M("\u2237","\\dblcolon");M("\u2239","\\eqcolon");M("\u2254","\\coloneqq");M("\u2255","\\eqqcolon");M("\u2A74","\\Coloneqq");M("\\ratio","\\vcentcolon");M("\\coloncolon","\\dblcolon");M("\\colonequals","\\coloneqq");M("\\coloncolonequals","\\Coloneqq");M("\\equalscolon","\\eqqcolon");M("\\equalscoloncolon","\\Eqqcolon");M("\\colonminus","\\coloneq");M("\\coloncolonminus","\\Coloneq");M("\\minuscolon","\\eqcolon");M("\\minuscoloncolon","\\Eqcolon");M("\\coloncolonapprox","\\Colonapprox");M("\\coloncolonsim","\\Colonsim");M("\\simcolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}");M("\\simcoloncolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}");M("\\approxcolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}");M("\\approxcoloncolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}");M("\\notni","\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u220C}}");M("\\limsup","\\DOTSB\\operatorname*{lim\\,sup}");M("\\liminf","\\DOTSB\\operatorname*{lim\\,inf}");M("\\injlim","\\DOTSB\\operatorname*{inj\\,lim}");M("\\projlim","\\DOTSB\\operatorname*{proj\\,lim}");M("\\varlimsup","\\DOTSB\\operatorname*{\\overline{lim}}");M("\\varliminf","\\DOTSB\\operatorname*{\\underline{lim}}");M("\\varinjlim","\\DOTSB\\operatorname*{\\underrightarrow{lim}}");M("\\varprojlim","\\DOTSB\\operatorname*{\\underleftarrow{lim}}");M("\\gvertneqq","\\html@mathml{\\@gvertneqq}{\u2269}");M("\\lvertneqq","\\html@mathml{\\@lvertneqq}{\u2268}");M("\\ngeqq","\\html@mathml{\\@ngeqq}{\u2271}");M("\\ngeqslant","\\html@mathml{\\@ngeqslant}{\u2271}");M("\\nleqq","\\html@mathml{\\@nleqq}{\u2270}");M("\\nleqslant","\\html@mathml{\\@nleqslant}{\u2270}");M("\\nshortmid","\\html@mathml{\\@nshortmid}{\u2224}");M("\\nshortparallel","\\html@mathml{\\@nshortparallel}{\u2226}");M("\\nsubseteqq","\\html@mathml{\\@nsubseteqq}{\u2288}");M("\\nsupseteqq","\\html@mathml{\\@nsupseteqq}{\u2289}");M("\\varsubsetneq","\\html@mathml{\\@varsubsetneq}{\u228A}");M("\\varsubsetneqq","\\html@mathml{\\@varsubsetneqq}{\u2ACB}");M("\\varsupsetneq","\\html@mathml{\\@varsupsetneq}{\u228B}");M("\\varsupsetneqq","\\html@mathml{\\@varsupsetneqq}{\u2ACC}");M("\\imath","\\html@mathml{\\@imath}{\u0131}");M("\\jmath","\\html@mathml{\\@jmath}{\u0237}");M("\\llbracket","\\html@mathml{\\mathopen{[\\mkern-3.2mu[}}{\\mathopen{\\char`\u27E6}}");M("\\rrbracket","\\html@mathml{\\mathclose{]\\mkern-3.2mu]}}{\\mathclose{\\char`\u27E7}}");M("\u27E6","\\llbracket");M("\u27E7","\\rrbracket");M("\\lBrace","\\html@mathml{\\mathopen{\\{\\mkern-3.2mu[}}{\\mathopen{\\char`\u2983}}");M("\\rBrace","\\html@mathml{\\mathclose{]\\mkern-3.2mu\\}}}{\\mathclose{\\char`\u2984}}");M("\u2983","\\lBrace");M("\u2984","\\rBrace");M("\\minuso","\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`\u29B5}}");M("\u29B5","\\minuso");M("\\darr","\\downarrow");M("\\dArr","\\Downarrow");M("\\Darr","\\Downarrow");M("\\lang","\\langle");M("\\rang","\\rangle");M("\\uarr","\\uparrow");M("\\uArr","\\Uparrow");M("\\Uarr","\\Uparrow");M("\\N","\\mathbb{N}");M("\\R","\\mathbb{R}");M("\\Z","\\mathbb{Z}");M("\\alef","\\aleph");M("\\alefsym","\\aleph");M("\\Alpha","\\mathrm{A}");M("\\Beta","\\mathrm{B}");M("\\bull","\\bullet");M("\\Chi","\\mathrm{X}");M("\\clubs","\\clubsuit");M("\\cnums","\\mathbb{C}");M("\\Complex","\\mathbb{C}");M("\\Dagger","\\ddagger");M("\\diamonds","\\diamondsuit");M("\\empty","\\emptyset");M("\\Epsilon","\\mathrm{E}");M("\\Eta","\\mathrm{H}");M("\\exist","\\exists");M("\\harr","\\leftrightarrow");M("\\hArr","\\Leftrightarrow");M("\\Harr","\\Leftrightarrow");M("\\hearts","\\heartsuit");M("\\image","\\Im");M("\\infin","\\infty");M("\\Iota","\\mathrm{I}");M("\\isin","\\in");M("\\Kappa","\\mathrm{K}");M("\\larr","\\leftarrow");M("\\lArr","\\Leftarrow");M("\\Larr","\\Leftarrow");M("\\lrarr","\\leftrightarrow");M("\\lrArr","\\Leftrightarrow");M("\\Lrarr","\\Leftrightarrow");M("\\Mu","\\mathrm{M}");M("\\natnums","\\mathbb{N}");M("\\Nu","\\mathrm{N}");M("\\Omicron","\\mathrm{O}");M("\\plusmn","\\pm");M("\\rarr","\\rightarrow");M("\\rArr","\\Rightarrow");M("\\Rarr","\\Rightarrow");M("\\real","\\Re");M("\\reals","\\mathbb{R}");M("\\Reals","\\mathbb{R}");M("\\Rho","\\mathrm{P}");M("\\sdot","\\cdot");M("\\sect","\\S");M("\\spades","\\spadesuit");M("\\sub","\\subset");M("\\sube","\\subseteq");M("\\supe","\\supseteq");M("\\Tau","\\mathrm{T}");M("\\thetasym","\\vartheta");M("\\weierp","\\wp");M("\\Zeta","\\mathrm{Z}");M("\\argmin","\\DOTSB\\operatorname*{arg\\,min}");M("\\argmax","\\DOTSB\\operatorname*{arg\\,max}");M("\\plim","\\DOTSB\\mathop{\\operatorname{plim}}\\limits");M("\\bra","\\mathinner{\\langle{#1}|}");M("\\ket","\\mathinner{|{#1}\\rangle}");M("\\braket","\\mathinner{\\langle{#1}\\rangle}");M("\\Bra","\\left\\langle#1\\right|");M("\\Ket","\\left|#1\\right\\rangle");xg=i=>e=>{var t=e.consumeArg().tokens,r=e.consumeArg().tokens,n=e.consumeArg().tokens,s=e.consumeArg().tokens,a=e.macros.get("|"),o=e.macros.get("\\|");e.macros.beginGroup();var l=c=>f=>{i&&(f.macros.set("|",a),n.length&&f.macros.set("\\|",o));var m=c;if(!c&&n.length){var g=f.future();g.text==="|"&&(f.popToken(),m=!0)}return{tokens:m?n:r,numArgs:0}};e.macros.set("|",l(!1)),n.length&&e.macros.set("\\|",l(!0));var h=e.consumeArg().tokens,d=e.expandTokens([...s,...h,...t]);return e.macros.endGroup(),{tokens:d.reverse(),numArgs:0}};M("\\bra@ket",xg(!1));M("\\bra@set",xg(!0));M("\\Braket","\\bra@ket{\\left\\langle}{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}");M("\\Set","\\bra@set{\\left\\{\\:}{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}");M("\\set","\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}");M("\\angln","{\\angl n}");M("\\blue","\\textcolor{##6495ed}{#1}");M("\\orange","\\textcolor{##ffa500}{#1}");M("\\pink","\\textcolor{##ff00af}{#1}");M("\\red","\\textcolor{##df0030}{#1}");M("\\green","\\textcolor{##28ae7b}{#1}");M("\\gray","\\textcolor{gray}{#1}");M("\\purple","\\textcolor{##9d38bd}{#1}");M("\\blueA","\\textcolor{##ccfaff}{#1}");M("\\blueB","\\textcolor{##80f6ff}{#1}");M("\\blueC","\\textcolor{##63d9ea}{#1}");M("\\blueD","\\textcolor{##11accd}{#1}");M("\\blueE","\\textcolor{##0c7f99}{#1}");M("\\tealA","\\textcolor{##94fff5}{#1}");M("\\tealB","\\textcolor{##26edd5}{#1}");M("\\tealC","\\textcolor{##01d1c1}{#1}");M("\\tealD","\\textcolor{##01a995}{#1}");M("\\tealE","\\textcolor{##208170}{#1}");M("\\greenA","\\textcolor{##b6ffb0}{#1}");M("\\greenB","\\textcolor{##8af281}{#1}");M("\\greenC","\\textcolor{##74cf70}{#1}");M("\\greenD","\\textcolor{##1fab54}{#1}");M("\\greenE","\\textcolor{##0d923f}{#1}");M("\\goldA","\\textcolor{##ffd0a9}{#1}");M("\\goldB","\\textcolor{##ffbb71}{#1}");M("\\goldC","\\textcolor{##ff9c39}{#1}");M("\\goldD","\\textcolor{##e07d10}{#1}");M("\\goldE","\\textcolor{##a75a05}{#1}");M("\\redA","\\textcolor{##fca9a9}{#1}");M("\\redB","\\textcolor{##ff8482}{#1}");M("\\redC","\\textcolor{##f9685d}{#1}");M("\\redD","\\textcolor{##e84d39}{#1}");M("\\redE","\\textcolor{##bc2612}{#1}");M("\\maroonA","\\textcolor{##ffbde0}{#1}");M("\\maroonB","\\textcolor{##ff92c6}{#1}");M("\\maroonC","\\textcolor{##ed5fa6}{#1}");M("\\maroonD","\\textcolor{##ca337c}{#1}");M("\\maroonE","\\textcolor{##9e034e}{#1}");M("\\purpleA","\\textcolor{##ddd7ff}{#1}");M("\\purpleB","\\textcolor{##c6b9fc}{#1}");M("\\purpleC","\\textcolor{##aa87ff}{#1}");M("\\purpleD","\\textcolor{##7854ab}{#1}");M("\\purpleE","\\textcolor{##543b78}{#1}");M("\\mintA","\\textcolor{##f5f9e8}{#1}");M("\\mintB","\\textcolor{##edf2df}{#1}");M("\\mintC","\\textcolor{##e0e5cc}{#1}");M("\\grayA","\\textcolor{##f6f7f7}{#1}");M("\\grayB","\\textcolor{##f0f1f2}{#1}");M("\\grayC","\\textcolor{##e3e5e6}{#1}");M("\\grayD","\\textcolor{##d6d8da}{#1}");M("\\grayE","\\textcolor{##babec2}{#1}");M("\\grayF","\\textcolor{##888d93}{#1}");M("\\grayG","\\textcolor{##626569}{#1}");M("\\grayH","\\textcolor{##3b3e40}{#1}");M("\\grayI","\\textcolor{##21242c}{#1}");M("\\kaBlue","\\textcolor{##314453}{#1}");M("\\kaGreen","\\textcolor{##71B307}{#1}");yg={"^":!0,_:!0,"\\limits":!0,"\\nolimits":!0},I1=class{constructor(e,t,r){this.settings=void 0,this.expansionCount=void 0,this.lexer=void 0,this.macros=void 0,this.stack=void 0,this.mode=void 0,this.settings=t,this.expansionCount=0,this.feed(e),this.macros=new L1(gk,t.macros),this.mode=r,this.stack=[]}feed(e){this.lexer=new Oh(e,this.settings)}switchMode(e){this.mode=e}beginGroup(){this.macros.beginGroup()}endGroup(){this.macros.endGroup()}endGroups(){this.macros.endGroups()}future(){return this.stack.length===0&&this.pushToken(this.lexer.lex()),this.stack[this.stack.length-1]}popToken(){return this.future(),this.stack.pop()}pushToken(e){this.stack.push(e)}pushTokens(e){this.stack.push(...e)}scanArgument(e){var t,r,n;if(e){if(this.consumeSpaces(),this.future().text!=="[")return null;t=this.popToken(),{tokens:n,end:r}=this.consumeArg(["]"])}else({tokens:n,start:t,end:r}=this.consumeArg());return this.pushToken(new mi("EOF",r.loc)),this.pushTokens(n),new mi("",ti.range(t,r))}consumeSpaces(){for(;;){var e=this.future();if(e.text===" ")this.stack.pop();else break}}consumeArg(e){var t=[],r=e&&e.length>0;r||this.consumeSpaces();var n=this.future(),s,a=0,o=0;do{if(s=this.popToken(),t.push(s),s.text==="{")++a;else if(s.text==="}"){if(--a,a===-1)throw new G("Extra }",s)}else if(s.text==="EOF")throw new G("Unexpected end of input in a macro argument, expected '"+(e&&r?e[o]:"}")+"'",s);if(e&&r)if((a===0||a===1&&e[o]==="{")&&s.text===e[o]){if(++o,o===e.length){t.splice(-o,o);break}}else o=0}while(a!==0||r);return n.text==="{"&&t[t.length-1].text==="}"&&(t.pop(),t.shift()),t.reverse(),{tokens:t,start:n,end:s}}consumeArgs(e,t){if(t){if(t.length!==e+1)throw new G("The length of delimiters doesn't match the number of args!");for(var r=t[0],n=0;nthis.settings.maxExpand)throw new G("Too many expansions: infinite loop or need to increase maxExpand setting")}expandOnce(e){var t=this.popToken(),r=t.text,n=t.noexpand?null:this._getExpansion(r);if(n==null||e&&n.unexpandable){if(e&&n==null&&r[0]==="\\"&&!this.isDefined(r))throw new G("Undefined control sequence: "+r);return this.pushToken(t),!1}this.countExpansion(1);var s=n.tokens,a=this.consumeArgs(n.numArgs,n.delimiters);if(n.numArgs){s=s.slice();for(var o=s.length-1;o>=0;--o){var l=s[o];if(l.text==="#"){if(o===0)throw new G("Incomplete placeholder at end of macro body",l);if(l=s[--o],l.text==="#")s.splice(o+1,1);else if(/^[1-9]$/.test(l.text))s.splice(o,2,...a[+l.text-1]);else throw new G("Not a valid argument number",l)}}}return this.pushTokens(s),s.length}expandAfterFuture(){return this.expandOnce(),this.future()}expandNextToken(){for(;;)if(this.expandOnce()===!1){var e=this.stack.pop();return e.treatAsRelax&&(e.text="\\relax"),e}throw new Error}expandMacro(e){return this.macros.has(e)?this.expandTokens([new mi(e)]):void 0}expandTokens(e){var t=[],r=this.stack.length;for(this.pushTokens(e);this.stack.length>r;)if(this.expandOnce(!0)===!1){var n=this.stack.pop();n.treatAsRelax&&(n.noexpand=!1,n.treatAsRelax=!1),t.push(n)}return this.countExpansion(t.length),t}expandMacroAsText(e){var t=this.expandMacro(e);return t&&t.map(r=>r.text).join("")}_getExpansion(e){var t=this.macros.get(e);if(t==null)return t;if(e.length===1){var r=this.lexer.catcodes[e];if(r!=null&&r!==13)return}var n=typeof t=="function"?t(this):t;if(typeof n=="string"){var s=0;if(n.indexOf("#")!==-1)for(var a=n.replace(/##/g,"");a.indexOf("#"+(s+1))!==-1;)++s;for(var o=new Oh(n,this.settings),l=[],h=o.lex();h.text!=="EOF";)l.push(h),h=o.lex();l.reverse();var d={tokens:l,numArgs:s};return d}return n}isDefined(e){return this.macros.has(e)||gn.hasOwnProperty(e)||Be.math.hasOwnProperty(e)||Be.text.hasOwnProperty(e)||yg.hasOwnProperty(e)}isExpandable(e){var t=this.macros.get(e);return t!=null?typeof t=="string"||typeof t=="function"||!t.unexpandable:gn.hasOwnProperty(e)&&!gn[e].primitive}},N4=/^[₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ]/,Ah=Object.freeze({"\u208A":"+","\u208B":"-","\u208C":"=","\u208D":"(","\u208E":")","\u2080":"0","\u2081":"1","\u2082":"2","\u2083":"3","\u2084":"4","\u2085":"5","\u2086":"6","\u2087":"7","\u2088":"8","\u2089":"9","\u2090":"a","\u2091":"e","\u2095":"h","\u1D62":"i","\u2C7C":"j","\u2096":"k","\u2097":"l","\u2098":"m","\u2099":"n","\u2092":"o","\u209A":"p","\u1D63":"r","\u209B":"s","\u209C":"t","\u1D64":"u","\u1D65":"v","\u2093":"x","\u1D66":"\u03B2","\u1D67":"\u03B3","\u1D68":"\u03C1","\u1D69":"\u03D5","\u1D6A":"\u03C7","\u207A":"+","\u207B":"-","\u207C":"=","\u207D":"(","\u207E":")","\u2070":"0","\xB9":"1","\xB2":"2","\xB3":"3","\u2074":"4","\u2075":"5","\u2076":"6","\u2077":"7","\u2078":"8","\u2079":"9","\u1D2C":"A","\u1D2E":"B","\u1D30":"D","\u1D31":"E","\u1D33":"G","\u1D34":"H","\u1D35":"I","\u1D36":"J","\u1D37":"K","\u1D38":"L","\u1D39":"M","\u1D3A":"N","\u1D3C":"O","\u1D3E":"P","\u1D3F":"R","\u1D40":"T","\u1D41":"U","\u2C7D":"V","\u1D42":"W","\u1D43":"a","\u1D47":"b","\u1D9C":"c","\u1D48":"d","\u1D49":"e","\u1DA0":"f","\u1D4D":"g",\u02B0:"h","\u2071":"i",\u02B2:"j","\u1D4F":"k",\u02E1:"l","\u1D50":"m",\u207F:"n","\u1D52":"o","\u1D56":"p",\u02B3:"r",\u02E2:"s","\u1D57":"t","\u1D58":"u","\u1D5B":"v",\u02B7:"w",\u02E3:"x",\u02B8:"y","\u1DBB":"z","\u1D5D":"\u03B2","\u1D5E":"\u03B3","\u1D5F":"\u03B4","\u1D60":"\u03D5","\u1D61":"\u03C7","\u1DBF":"\u03B8"}),b1={"\u0301":{text:"\\'",math:"\\acute"},"\u0300":{text:"\\`",math:"\\grave"},"\u0308":{text:'\\"',math:"\\ddot"},"\u0303":{text:"\\~",math:"\\tilde"},"\u0304":{text:"\\=",math:"\\bar"},"\u0306":{text:"\\u",math:"\\breve"},"\u030C":{text:"\\v",math:"\\check"},"\u0302":{text:"\\^",math:"\\hat"},"\u0307":{text:"\\.",math:"\\dot"},"\u030A":{text:"\\r",math:"\\mathring"},"\u030B":{text:"\\H"},"\u0327":{text:"\\c"}},E4={\u00E1:"a\u0301",\u00E0:"a\u0300",\u00E4:"a\u0308",\u01DF:"a\u0308\u0304",\u00E3:"a\u0303",\u0101:"a\u0304",\u0103:"a\u0306",\u1EAF:"a\u0306\u0301",\u1EB1:"a\u0306\u0300",\u1EB5:"a\u0306\u0303",\u01CE:"a\u030C",\u00E2:"a\u0302",\u1EA5:"a\u0302\u0301",\u1EA7:"a\u0302\u0300",\u1EAB:"a\u0302\u0303",\u0227:"a\u0307",\u01E1:"a\u0307\u0304",\u00E5:"a\u030A",\u01FB:"a\u030A\u0301",\u1E03:"b\u0307",\u0107:"c\u0301",\u1E09:"c\u0327\u0301",\u010D:"c\u030C",\u0109:"c\u0302",\u010B:"c\u0307",\u00E7:"c\u0327",\u010F:"d\u030C",\u1E0B:"d\u0307",\u1E11:"d\u0327",\u00E9:"e\u0301",\u00E8:"e\u0300",\u00EB:"e\u0308",\u1EBD:"e\u0303",\u0113:"e\u0304",\u1E17:"e\u0304\u0301",\u1E15:"e\u0304\u0300",\u0115:"e\u0306",\u1E1D:"e\u0327\u0306",\u011B:"e\u030C",\u00EA:"e\u0302",\u1EBF:"e\u0302\u0301",\u1EC1:"e\u0302\u0300",\u1EC5:"e\u0302\u0303",\u0117:"e\u0307",\u0229:"e\u0327",\u1E1F:"f\u0307",\u01F5:"g\u0301",\u1E21:"g\u0304",\u011F:"g\u0306",\u01E7:"g\u030C",\u011D:"g\u0302",\u0121:"g\u0307",\u0123:"g\u0327",\u1E27:"h\u0308",\u021F:"h\u030C",\u0125:"h\u0302",\u1E23:"h\u0307",\u1E29:"h\u0327",\u00ED:"i\u0301",\u00EC:"i\u0300",\u00EF:"i\u0308",\u1E2F:"i\u0308\u0301",\u0129:"i\u0303",\u012B:"i\u0304",\u012D:"i\u0306",\u01D0:"i\u030C",\u00EE:"i\u0302",\u01F0:"j\u030C",\u0135:"j\u0302",\u1E31:"k\u0301",\u01E9:"k\u030C",\u0137:"k\u0327",\u013A:"l\u0301",\u013E:"l\u030C",\u013C:"l\u0327",\u1E3F:"m\u0301",\u1E41:"m\u0307",\u0144:"n\u0301",\u01F9:"n\u0300",\u00F1:"n\u0303",\u0148:"n\u030C",\u1E45:"n\u0307",\u0146:"n\u0327",\u00F3:"o\u0301",\u00F2:"o\u0300",\u00F6:"o\u0308",\u022B:"o\u0308\u0304",\u00F5:"o\u0303",\u1E4D:"o\u0303\u0301",\u1E4F:"o\u0303\u0308",\u022D:"o\u0303\u0304",\u014D:"o\u0304",\u1E53:"o\u0304\u0301",\u1E51:"o\u0304\u0300",\u014F:"o\u0306",\u01D2:"o\u030C",\u00F4:"o\u0302",\u1ED1:"o\u0302\u0301",\u1ED3:"o\u0302\u0300",\u1ED7:"o\u0302\u0303",\u022F:"o\u0307",\u0231:"o\u0307\u0304",\u0151:"o\u030B",\u1E55:"p\u0301",\u1E57:"p\u0307",\u0155:"r\u0301",\u0159:"r\u030C",\u1E59:"r\u0307",\u0157:"r\u0327",\u015B:"s\u0301",\u1E65:"s\u0301\u0307",\u0161:"s\u030C",\u1E67:"s\u030C\u0307",\u015D:"s\u0302",\u1E61:"s\u0307",\u015F:"s\u0327",\u1E97:"t\u0308",\u0165:"t\u030C",\u1E6B:"t\u0307",\u0163:"t\u0327",\u00FA:"u\u0301",\u00F9:"u\u0300",\u00FC:"u\u0308",\u01D8:"u\u0308\u0301",\u01DC:"u\u0308\u0300",\u01D6:"u\u0308\u0304",\u01DA:"u\u0308\u030C",\u0169:"u\u0303",\u1E79:"u\u0303\u0301",\u016B:"u\u0304",\u1E7B:"u\u0304\u0308",\u016D:"u\u0306",\u01D4:"u\u030C",\u00FB:"u\u0302",\u016F:"u\u030A",\u0171:"u\u030B",\u1E7D:"v\u0303",\u1E83:"w\u0301",\u1E81:"w\u0300",\u1E85:"w\u0308",\u0175:"w\u0302",\u1E87:"w\u0307",\u1E98:"w\u030A",\u1E8D:"x\u0308",\u1E8B:"x\u0307",\u00FD:"y\u0301",\u1EF3:"y\u0300",\u00FF:"y\u0308",\u1EF9:"y\u0303",\u0233:"y\u0304",\u0177:"y\u0302",\u1E8F:"y\u0307",\u1E99:"y\u030A",\u017A:"z\u0301",\u017E:"z\u030C",\u1E91:"z\u0302",\u017C:"z\u0307",\u00C1:"A\u0301",\u00C0:"A\u0300",\u00C4:"A\u0308",\u01DE:"A\u0308\u0304",\u00C3:"A\u0303",\u0100:"A\u0304",\u0102:"A\u0306",\u1EAE:"A\u0306\u0301",\u1EB0:"A\u0306\u0300",\u1EB4:"A\u0306\u0303",\u01CD:"A\u030C",\u00C2:"A\u0302",\u1EA4:"A\u0302\u0301",\u1EA6:"A\u0302\u0300",\u1EAA:"A\u0302\u0303",\u0226:"A\u0307",\u01E0:"A\u0307\u0304",\u00C5:"A\u030A",\u01FA:"A\u030A\u0301",\u1E02:"B\u0307",\u0106:"C\u0301",\u1E08:"C\u0327\u0301",\u010C:"C\u030C",\u0108:"C\u0302",\u010A:"C\u0307",\u00C7:"C\u0327",\u010E:"D\u030C",\u1E0A:"D\u0307",\u1E10:"D\u0327",\u00C9:"E\u0301",\u00C8:"E\u0300",\u00CB:"E\u0308",\u1EBC:"E\u0303",\u0112:"E\u0304",\u1E16:"E\u0304\u0301",\u1E14:"E\u0304\u0300",\u0114:"E\u0306",\u1E1C:"E\u0327\u0306",\u011A:"E\u030C",\u00CA:"E\u0302",\u1EBE:"E\u0302\u0301",\u1EC0:"E\u0302\u0300",\u1EC4:"E\u0302\u0303",\u0116:"E\u0307",\u0228:"E\u0327",\u1E1E:"F\u0307",\u01F4:"G\u0301",\u1E20:"G\u0304",\u011E:"G\u0306",\u01E6:"G\u030C",\u011C:"G\u0302",\u0120:"G\u0307",\u0122:"G\u0327",\u1E26:"H\u0308",\u021E:"H\u030C",\u0124:"H\u0302",\u1E22:"H\u0307",\u1E28:"H\u0327",\u00CD:"I\u0301",\u00CC:"I\u0300",\u00CF:"I\u0308",\u1E2E:"I\u0308\u0301",\u0128:"I\u0303",\u012A:"I\u0304",\u012C:"I\u0306",\u01CF:"I\u030C",\u00CE:"I\u0302",\u0130:"I\u0307",\u0134:"J\u0302",\u1E30:"K\u0301",\u01E8:"K\u030C",\u0136:"K\u0327",\u0139:"L\u0301",\u013D:"L\u030C",\u013B:"L\u0327",\u1E3E:"M\u0301",\u1E40:"M\u0307",\u0143:"N\u0301",\u01F8:"N\u0300",\u00D1:"N\u0303",\u0147:"N\u030C",\u1E44:"N\u0307",\u0145:"N\u0327",\u00D3:"O\u0301",\u00D2:"O\u0300",\u00D6:"O\u0308",\u022A:"O\u0308\u0304",\u00D5:"O\u0303",\u1E4C:"O\u0303\u0301",\u1E4E:"O\u0303\u0308",\u022C:"O\u0303\u0304",\u014C:"O\u0304",\u1E52:"O\u0304\u0301",\u1E50:"O\u0304\u0300",\u014E:"O\u0306",\u01D1:"O\u030C",\u00D4:"O\u0302",\u1ED0:"O\u0302\u0301",\u1ED2:"O\u0302\u0300",\u1ED6:"O\u0302\u0303",\u022E:"O\u0307",\u0230:"O\u0307\u0304",\u0150:"O\u030B",\u1E54:"P\u0301",\u1E56:"P\u0307",\u0154:"R\u0301",\u0158:"R\u030C",\u1E58:"R\u0307",\u0156:"R\u0327",\u015A:"S\u0301",\u1E64:"S\u0301\u0307",\u0160:"S\u030C",\u1E66:"S\u030C\u0307",\u015C:"S\u0302",\u1E60:"S\u0307",\u015E:"S\u0327",\u0164:"T\u030C",\u1E6A:"T\u0307",\u0162:"T\u0327",\u00DA:"U\u0301",\u00D9:"U\u0300",\u00DC:"U\u0308",\u01D7:"U\u0308\u0301",\u01DB:"U\u0308\u0300",\u01D5:"U\u0308\u0304",\u01D9:"U\u0308\u030C",\u0168:"U\u0303",\u1E78:"U\u0303\u0301",\u016A:"U\u0304",\u1E7A:"U\u0304\u0308",\u016C:"U\u0306",\u01D3:"U\u030C",\u00DB:"U\u0302",\u016E:"U\u030A",\u0170:"U\u030B",\u1E7C:"V\u0303",\u1E82:"W\u0301",\u1E80:"W\u0300",\u1E84:"W\u0308",\u0174:"W\u0302",\u1E86:"W\u0307",\u1E8C:"X\u0308",\u1E8A:"X\u0307",\u00DD:"Y\u0301",\u1EF2:"Y\u0300",\u0178:"Y\u0308",\u1EF8:"Y\u0303",\u0232:"Y\u0304",\u0176:"Y\u0302",\u1E8E:"Y\u0307",\u0179:"Z\u0301",\u017D:"Z\u030C",\u1E90:"Z\u0302",\u017B:"Z\u0307",\u03AC:"\u03B1\u0301",\u1F70:"\u03B1\u0300",\u1FB1:"\u03B1\u0304",\u1FB0:"\u03B1\u0306",\u03AD:"\u03B5\u0301",\u1F72:"\u03B5\u0300",\u03AE:"\u03B7\u0301",\u1F74:"\u03B7\u0300",\u03AF:"\u03B9\u0301",\u1F76:"\u03B9\u0300",\u03CA:"\u03B9\u0308",\u0390:"\u03B9\u0308\u0301",\u1FD2:"\u03B9\u0308\u0300",\u1FD1:"\u03B9\u0304",\u1FD0:"\u03B9\u0306",\u03CC:"\u03BF\u0301",\u1F78:"\u03BF\u0300",\u03CD:"\u03C5\u0301",\u1F7A:"\u03C5\u0300",\u03CB:"\u03C5\u0308",\u03B0:"\u03C5\u0308\u0301",\u1FE2:"\u03C5\u0308\u0300",\u1FE1:"\u03C5\u0304",\u1FE0:"\u03C5\u0306",\u03CE:"\u03C9\u0301",\u1F7C:"\u03C9\u0300",\u038E:"\u03A5\u0301",\u1FEA:"\u03A5\u0300",\u03AB:"\u03A5\u0308",\u1FE9:"\u03A5\u0304",\u1FE8:"\u03A5\u0306",\u038F:"\u03A9\u0301",\u1FFA:"\u03A9\u0300"},Bh=class i{constructor(e,t){this.mode=void 0,this.gullet=void 0,this.settings=void 0,this.leftrightDepth=void 0,this.nextToken=void 0,this.mode="math",this.gullet=new I1(e,t,this.mode),this.settings=t,this.leftrightDepth=0}expect(e,t){if(t===void 0&&(t=!0),this.fetch().text!==e)throw new G("Expected '"+e+"', got '"+this.fetch().text+"'",this.fetch());t&&this.consume()}consume(){this.nextToken=null}fetch(){return this.nextToken==null&&(this.nextToken=this.gullet.expandNextToken()),this.nextToken}switchMode(e){this.mode=e,this.gullet.switchMode(e)}parse(){this.settings.globalGroup||this.gullet.beginGroup(),this.settings.colorIsTextColor&&this.gullet.macros.set("\\color","\\textcolor");try{var e=this.parseExpression(!1);return this.expect("EOF"),this.settings.globalGroup||this.gullet.endGroup(),e}finally{this.gullet.endGroups()}}subparse(e){var t=this.nextToken;this.consume(),this.gullet.pushToken(new mi("}")),this.gullet.pushTokens(e);var r=this.parseExpression(!1);return this.expect("}"),this.nextToken=t,r}parseExpression(e,t){for(var r=[];;){this.mode==="math"&&this.consumeSpaces();var n=this.fetch();if(i.endOfExpression.indexOf(n.text)!==-1||t&&n.text===t||e&&gn[n.text]&&gn[n.text].infix)break;var s=this.parseAtom(t);if(s){if(s.type==="internal")continue}else break;r.push(s)}return this.mode==="text"&&this.formLigatures(r),this.handleInfixNodes(r)}handleInfixNodes(e){for(var t=-1,r,n=0;n=0&&this.settings.reportNonstrict("unicodeTextInMathMode",'Latin-1/Unicode text character "'+t[0]+'" used in math mode',e);var o=Be[this.mode][t].group,l=ti.range(e),h;if(oA.hasOwnProperty(o)){var d=o;h={type:"atom",mode:this.mode,family:d,loc:l,text:t}}else h={type:o,mode:this.mode,loc:l,text:t};a=h}else if(t.charCodeAt(0)>=128)this.settings.strict&&(A4(t.charCodeAt(0))?this.mode==="math"&&this.settings.reportNonstrict("unicodeTextInMathMode",'Unicode text character "'+t[0]+'" used in math mode',e):this.settings.reportNonstrict("unknownSymbol",'Unrecognized Unicode character "'+t[0]+'"'+(" ("+t.charCodeAt(0)+")"),e)),a={type:"textord",mode:"text",loc:ti.range(e),text:t};else return null;if(this.consume(),s)for(var c=0;c{Mk=typeof global=="object"&&global&&global.Object===Object&&global,Gh=Mk});var Tk,Nk,ht,Xi=T(()=>{Z1();Tk=typeof self=="object"&&self&&self.Object===Object&&self,Nk=Gh||Tk||Function("return this")(),ht=Nk});var Ek,Li,dl=T(()=>{Xi();Ek=ht.Symbol,Li=Ek});function kk(i){var e=Sk.call(i,cl),t=i[cl];try{i[cl]=void 0;var r=!0}catch{}var n=Ak.call(i);return r&&(e?i[cl]=t:delete i[cl]),n}var Mg,Sk,Ak,cl,Tg,Ng=T(()=>{dl();Mg=Object.prototype,Sk=Mg.hasOwnProperty,Ak=Mg.toString,cl=Li?Li.toStringTag:void 0;Tg=kk});function Lk(i){return _k.call(i)}var Ck,_k,Eg,Sg=T(()=>{Ck=Object.prototype,_k=Ck.toString;Eg=Lk});function Rk(i){return i==null?i===void 0?zk:Ik:Ag&&Ag in Object(i)?Tg(i):Eg(i)}var Ik,zk,Ag,Ki,xa=T(()=>{dl();Ng();Sg();Ik="[object Null]",zk="[object Undefined]",Ag=Li?Li.toStringTag:void 0;Ki=Rk});function Dk(i){return i!=null&&typeof i=="object"}var Mt,Or=T(()=>{Mt=Dk});var Ok,Ii,ya=T(()=>{Ok=Array.isArray,Ii=Ok});function Bk(i){var e=typeof i;return i!=null&&(e=="object"||e=="function")}var St,Br=T(()=>{St=Bk});function Pk(i){return i}var Wh,Q1=T(()=>{Wh=Pk});function $k(i){if(!St(i))return!1;var e=Ki(i);return e==qk||e==Hk||e==Fk||e==Uk}var Fk,qk,Hk,Uk,va,Vh=T(()=>{xa();Br();Fk="[object AsyncFunction]",qk="[object Function]",Hk="[object GeneratorFunction]",Uk="[object Proxy]";va=$k});var jk,Yh,kg=T(()=>{Xi();jk=ht["__core-js_shared__"],Yh=jk});function Gk(i){return!!Cg&&Cg in i}var Cg,_g,Lg=T(()=>{kg();Cg=(function(){var i=/[^.]+$/.exec(Yh&&Yh.keys&&Yh.keys.IE_PROTO||"");return i?"Symbol(src)_1."+i:""})();_g=Gk});function Yk(i){if(i!=null){try{return Vk.call(i)}catch{}try{return i+""}catch{}}return""}var Wk,Vk,Pr,J1=T(()=>{Wk=Function.prototype,Vk=Wk.toString;Pr=Yk});function iC(i){if(!St(i)||_g(i))return!1;var e=va(i)?tC:Kk;return e.test(Pr(i))}var Xk,Kk,Zk,Qk,Jk,eC,tC,Ig,zg=T(()=>{Vh();Lg();Br();J1();Xk=/[\\^$.*+?()[\]{}|]/g,Kk=/^\[object .+?Constructor\]$/,Zk=Function.prototype,Qk=Object.prototype,Jk=Zk.toString,eC=Qk.hasOwnProperty,tC=RegExp("^"+Jk.call(eC).replace(Xk,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");Ig=iC});function rC(i,e){return i?.[e]}var Rg,Dg=T(()=>{Rg=rC});function nC(i,e){var t=Rg(i,e);return Ig(t)?t:void 0}var ni,bn=T(()=>{zg();Dg();ni=nC});var sC,Xh,Og=T(()=>{bn();Xi();sC=ni(ht,"WeakMap"),Xh=sC});var Bg,aC,Pg,Fg=T(()=>{Br();Bg=Object.create,aC=(function(){function i(){}return function(e){if(!St(e))return{};if(Bg)return Bg(e);i.prototype=e;var t=new i;return i.prototype=void 0,t}})(),Pg=aC});function oC(i,e,t){switch(t.length){case 0:return i.call(e);case 1:return i.call(e,t[0]);case 2:return i.call(e,t[0],t[1]);case 3:return i.call(e,t[0],t[1],t[2])}return i.apply(e,t)}var qg,Hg=T(()=>{qg=oC});function lC(i,e){var t=-1,r=i.length;for(e||(e=Array(r));++t{Kh=lC});function uC(i){var e=0,t=0;return function(){var r=cC(),n=dC-(r-t);if(t=r,n>0){if(++e>=hC)return arguments[0]}else e=0;return i.apply(void 0,arguments)}}var hC,dC,cC,Ug,$g=T(()=>{hC=800,dC=16,cC=Date.now;Ug=uC});function fC(i){return function(){return i}}var jg,Gg=T(()=>{jg=fC});var mC,ba,tf=T(()=>{bn();mC=(function(){try{var i=ni(Object,"defineProperty");return i({},"",{}),i}catch{}})(),ba=mC});var pC,Wg,Vg=T(()=>{Gg();tf();Q1();pC=ba?function(i,e){return ba(i,"toString",{configurable:!0,enumerable:!1,value:jg(e),writable:!0})}:Wh,Wg=pC});var gC,Yg,Xg=T(()=>{Vg();$g();gC=Ug(Wg),Yg=gC});function xC(i,e){for(var t=-1,r=i==null?0:i.length;++t{Kg=xC});function bC(i,e){var t=typeof i;return e=e??yC,!!e&&(t=="number"||t!="symbol"&&vC.test(i))&&i>-1&&i%1==0&&i{yC=9007199254740991,vC=/^(?:0|[1-9]\d*)$/;Zh=bC});function wC(i,e,t){e=="__proto__"&&ba?ba(i,e,{configurable:!0,enumerable:!0,value:t,writable:!0}):i[e]=t}var wa,Qh=T(()=>{tf();wa=wC});function MC(i,e){return i===e||i!==i&&e!==e}var fr,Ma=T(()=>{fr=MC});function EC(i,e,t){var r=i[e];(!(NC.call(i,e)&&fr(r,t))||t===void 0&&!(e in i))&&wa(i,e,t)}var TC,NC,Jh,nf=T(()=>{Qh();Ma();TC=Object.prototype,NC=TC.hasOwnProperty;Jh=EC});function SC(i,e,t,r){var n=!t;t||(t={});for(var s=-1,a=e.length;++s{nf();Qh();mr=SC});function AC(i,e,t){return e=Qg(e===void 0?i.length-1:e,0),function(){for(var r=arguments,n=-1,s=Qg(r.length-e,0),a=Array(s);++n{Hg();Qg=Math.max;Jg=AC});function kC(i,e){return Yg(Jg(i,e,Wh),i+"")}var t5,i5=T(()=>{Q1();e5();Xg();t5=kC});function _C(i){return typeof i=="number"&&i>-1&&i%1==0&&i<=CC}var CC,ed,sf=T(()=>{CC=9007199254740991;ed=_C});function LC(i){return i!=null&&ed(i.length)&&!va(i)}var wn,ul=T(()=>{Vh();sf();wn=LC});function IC(i,e,t){if(!St(t))return!1;var r=typeof e;return(r=="number"?wn(t)&&Zh(e,t.length):r=="string"&&e in t)?fr(t[e],i):!1}var r5,n5=T(()=>{Ma();ul();rf();Br();r5=IC});function zC(i){return t5(function(e,t){var r=-1,n=t.length,s=n>1?t[n-1]:void 0,a=n>2?t[2]:void 0;for(s=i.length>3&&typeof s=="function"?(n--,s):void 0,a&&r5(t[0],t[1],a)&&(s=n<3?void 0:s,n=1),e=Object(e);++r{i5();n5();s5=zC});function DC(i){var e=i&&i.constructor,t=typeof e=="function"&&e.prototype||RC;return i===t}var RC,Na,td=T(()=>{RC=Object.prototype;Na=DC});function OC(i,e){for(var t=-1,r=Array(i);++t{o5=OC});function PC(i){return Mt(i)&&Ki(i)==BC}var BC,af,h5=T(()=>{xa();Or();BC="[object Arguments]";af=PC});var d5,FC,qC,HC,fl,of=T(()=>{h5();Or();d5=Object.prototype,FC=d5.hasOwnProperty,qC=d5.propertyIsEnumerable,HC=af((function(){return arguments})())?af:function(i){return Mt(i)&&FC.call(i,"callee")&&!qC.call(i,"callee")},fl=HC});function UC(){return!1}var c5,u5=T(()=>{c5=UC});var p5,f5,$C,m5,jC,GC,Fr,ml=T(()=>{Xi();u5();p5=typeof exports=="object"&&exports&&!exports.nodeType&&exports,f5=p5&&typeof module=="object"&&module&&!module.nodeType&&module,$C=f5&&f5.exports===p5,m5=$C?ht.Buffer:void 0,jC=m5?m5.isBuffer:void 0,GC=jC||c5,Fr=GC});function g_(i){return Mt(i)&&ed(i.length)&&!!$e[Ki(i)]}var WC,VC,YC,XC,KC,ZC,QC,JC,e_,t_,i_,r_,n_,s_,a_,o_,l_,h_,d_,c_,u_,f_,m_,p_,$e,g5,x5=T(()=>{xa();sf();Or();WC="[object Arguments]",VC="[object Array]",YC="[object Boolean]",XC="[object Date]",KC="[object Error]",ZC="[object Function]",QC="[object Map]",JC="[object Number]",e_="[object Object]",t_="[object RegExp]",i_="[object Set]",r_="[object String]",n_="[object WeakMap]",s_="[object ArrayBuffer]",a_="[object DataView]",o_="[object Float32Array]",l_="[object Float64Array]",h_="[object Int8Array]",d_="[object Int16Array]",c_="[object Int32Array]",u_="[object Uint8Array]",f_="[object Uint8ClampedArray]",m_="[object Uint16Array]",p_="[object Uint32Array]",$e={};$e[o_]=$e[l_]=$e[h_]=$e[d_]=$e[c_]=$e[u_]=$e[f_]=$e[m_]=$e[p_]=!0;$e[WC]=$e[VC]=$e[s_]=$e[YC]=$e[a_]=$e[XC]=$e[KC]=$e[ZC]=$e[QC]=$e[JC]=$e[e_]=$e[t_]=$e[i_]=$e[r_]=$e[n_]=!1;g5=g_});function x_(i){return function(e){return i(e)}}var Ea,id=T(()=>{Ea=x_});var y5,pl,y_,lf,v_,qr,rd=T(()=>{Z1();y5=typeof exports=="object"&&exports&&!exports.nodeType&&exports,pl=y5&&typeof module=="object"&&module&&!module.nodeType&&module,y_=pl&&pl.exports===y5,lf=y_&&Gh.process,v_=(function(){try{var i=pl&&pl.require&&pl.require("util").types;return i||lf&&lf.binding&&lf.binding("util")}catch{}})(),qr=v_});var v5,b_,Sa,nd=T(()=>{x5();id();rd();v5=qr&&qr.isTypedArray,b_=v5?Ea(v5):g5,Sa=b_});function T_(i,e){var t=Ii(i),r=!t&&fl(i),n=!t&&!r&&Fr(i),s=!t&&!r&&!n&&Sa(i),a=t||r||n||s,o=a?o5(i.length,String):[],l=o.length;for(var h in i)(e||M_.call(i,h))&&!(a&&(h=="length"||n&&(h=="offset"||h=="parent")||s&&(h=="buffer"||h=="byteLength"||h=="byteOffset")||Zh(h,l)))&&o.push(h);return o}var w_,M_,sd,hf=T(()=>{l5();of();ya();ml();rf();nd();w_=Object.prototype,M_=w_.hasOwnProperty;sd=T_});function N_(i,e){return function(t){return i(e(t))}}var ad,df=T(()=>{ad=N_});var E_,b5,w5=T(()=>{df();E_=ad(Object.keys,Object),b5=E_});function k_(i){if(!Na(i))return b5(i);var e=[];for(var t in Object(i))A_.call(i,t)&&t!="constructor"&&e.push(t);return e}var S_,A_,M5,T5=T(()=>{td();w5();S_=Object.prototype,A_=S_.hasOwnProperty;M5=k_});function C_(i){return wn(i)?sd(i):M5(i)}var Aa,od=T(()=>{hf();T5();ul();Aa=C_});function __(i){var e=[];if(i!=null)for(var t in Object(i))e.push(t);return e}var N5,E5=T(()=>{N5=__});function z_(i){if(!St(i))return N5(i);var e=Na(i),t=[];for(var r in i)r=="constructor"&&(e||!I_.call(i,r))||t.push(r);return t}var L_,I_,S5,A5=T(()=>{Br();td();E5();L_=Object.prototype,I_=L_.hasOwnProperty;S5=z_});function R_(i){return wn(i)?sd(i,!0):S5(i)}var pr,ka=T(()=>{hf();A5();ul();pr=R_});var D_,Hr,gl=T(()=>{bn();D_=ni(Object,"create"),Hr=D_});function O_(){this.__data__=Hr?Hr(null):{},this.size=0}var k5,C5=T(()=>{gl();k5=O_});function B_(i){var e=this.has(i)&&delete this.__data__[i];return this.size-=e?1:0,e}var _5,L5=T(()=>{_5=B_});function H_(i){var e=this.__data__;if(Hr){var t=e[i];return t===P_?void 0:t}return q_.call(e,i)?e[i]:void 0}var P_,F_,q_,I5,z5=T(()=>{gl();P_="__lodash_hash_undefined__",F_=Object.prototype,q_=F_.hasOwnProperty;I5=H_});function j_(i){var e=this.__data__;return Hr?e[i]!==void 0:$_.call(e,i)}var U_,$_,R5,D5=T(()=>{gl();U_=Object.prototype,$_=U_.hasOwnProperty;R5=j_});function W_(i,e){var t=this.__data__;return this.size+=this.has(i)?0:1,t[i]=Hr&&e===void 0?G_:e,this}var G_,O5,B5=T(()=>{gl();G_="__lodash_hash_undefined__";O5=W_});function Ca(i){var e=-1,t=i==null?0:i.length;for(this.clear();++e{C5();L5();z5();D5();B5();Ca.prototype.clear=k5;Ca.prototype.delete=_5;Ca.prototype.get=I5;Ca.prototype.has=R5;Ca.prototype.set=O5;cf=Ca});function V_(){this.__data__=[],this.size=0}var F5,q5=T(()=>{F5=V_});function Y_(i,e){for(var t=i.length;t--;)if(fr(i[t][0],e))return t;return-1}var Mn,xl=T(()=>{Ma();Mn=Y_});function Z_(i){var e=this.__data__,t=Mn(e,i);if(t<0)return!1;var r=e.length-1;return t==r?e.pop():K_.call(e,t,1),--this.size,!0}var X_,K_,H5,U5=T(()=>{xl();X_=Array.prototype,K_=X_.splice;H5=Z_});function Q_(i){var e=this.__data__,t=Mn(e,i);return t<0?void 0:e[t][1]}var $5,j5=T(()=>{xl();$5=Q_});function J_(i){return Mn(this.__data__,i)>-1}var G5,W5=T(()=>{xl();G5=J_});function eL(i,e){var t=this.__data__,r=Mn(t,i);return r<0?(++this.size,t.push([i,e])):t[r][1]=e,this}var V5,Y5=T(()=>{xl();V5=eL});function _a(i){var e=-1,t=i==null?0:i.length;for(this.clear();++e{q5();U5();j5();W5();Y5();_a.prototype.clear=F5;_a.prototype.delete=H5;_a.prototype.get=$5;_a.prototype.has=G5;_a.prototype.set=V5;Tn=_a});var tL,Nn,ld=T(()=>{bn();Xi();tL=ni(ht,"Map"),Nn=tL});function iL(){this.size=0,this.__data__={hash:new cf,map:new(Nn||Tn),string:new cf}}var X5,K5=T(()=>{P5();yl();ld();X5=iL});function rL(i){var e=typeof i;return e=="string"||e=="number"||e=="symbol"||e=="boolean"?i!=="__proto__":i===null}var Z5,Q5=T(()=>{Z5=rL});function nL(i,e){var t=i.__data__;return Z5(e)?t[typeof e=="string"?"string":"hash"]:t.map}var En,vl=T(()=>{Q5();En=nL});function sL(i){var e=En(this,i).delete(i);return this.size-=e?1:0,e}var J5,e7=T(()=>{vl();J5=sL});function aL(i){return En(this,i).get(i)}var t7,i7=T(()=>{vl();t7=aL});function oL(i){return En(this,i).has(i)}var r7,n7=T(()=>{vl();r7=oL});function lL(i,e){var t=En(this,i),r=t.size;return t.set(i,e),this.size+=t.size==r?0:1,this}var s7,a7=T(()=>{vl();s7=lL});function La(i){var e=-1,t=i==null?0:i.length;for(this.clear();++e{K5();e7();i7();n7();a7();La.prototype.clear=X5;La.prototype.delete=J5;La.prototype.get=t7;La.prototype.has=r7;La.prototype.set=s7;hd=La});function hL(i,e){for(var t=-1,r=e.length,n=i.length;++t{dd=hL});var dL,Ia,cd=T(()=>{df();dL=ad(Object.getPrototypeOf,Object),Ia=dL});function gL(i){if(!Mt(i)||Ki(i)!=cL)return!1;var e=Ia(i);if(e===null)return!0;var t=mL.call(e,"constructor")&&e.constructor;return typeof t=="function"&&t instanceof t&&o7.call(t)==pL}var cL,uL,fL,o7,mL,pL,l7,h7=T(()=>{xa();cd();Or();cL="[object Object]",uL=Function.prototype,fL=Object.prototype,o7=uL.toString,mL=fL.hasOwnProperty,pL=o7.call(Object);l7=gL});function xL(){this.__data__=new Tn,this.size=0}var d7,c7=T(()=>{yl();d7=xL});function yL(i){var e=this.__data__,t=e.delete(i);return this.size=e.size,t}var u7,f7=T(()=>{u7=yL});function vL(i){return this.__data__.get(i)}var m7,p7=T(()=>{m7=vL});function bL(i){return this.__data__.has(i)}var g7,x7=T(()=>{g7=bL});function ML(i,e){var t=this.__data__;if(t instanceof Tn){var r=t.__data__;if(!Nn||r.length{yl();ld();uf();wL=200;y7=ML});function za(i){var e=this.__data__=new Tn(i);this.size=e.size}var Sn,ud=T(()=>{yl();c7();f7();p7();x7();v7();za.prototype.clear=d7;za.prototype.delete=u7;za.prototype.get=m7;za.prototype.has=g7;za.prototype.set=y7;Sn=za});function TL(i,e){return i&&mr(e,Aa(e),i)}var b7,w7=T(()=>{Ta();od();b7=TL});function NL(i,e){return i&&mr(e,pr(e),i)}var M7,T7=T(()=>{Ta();ka();M7=NL});function SL(i,e){if(e)return i.slice();var t=i.length,r=S7?S7(t):new i.constructor(t);return i.copy(r),r}var A7,N7,EL,E7,S7,fd,mf=T(()=>{Xi();A7=typeof exports=="object"&&exports&&!exports.nodeType&&exports,N7=A7&&typeof module=="object"&&module&&!module.nodeType&&module,EL=N7&&N7.exports===A7,E7=EL?ht.Buffer:void 0,S7=E7?E7.allocUnsafe:void 0;fd=SL});function AL(i,e){for(var t=-1,r=i==null?0:i.length,n=0,s=[];++t{k7=AL});function kL(){return[]}var md,pf=T(()=>{md=kL});var CL,_L,_7,LL,Ra,pd=T(()=>{C7();pf();CL=Object.prototype,_L=CL.propertyIsEnumerable,_7=Object.getOwnPropertySymbols,LL=_7?function(i){return i==null?[]:(i=Object(i),k7(_7(i),function(e){return _L.call(i,e)}))}:md,Ra=LL});function IL(i,e){return mr(i,Ra(i),e)}var L7,I7=T(()=>{Ta();pd();L7=IL});var zL,RL,gd,gf=T(()=>{ff();cd();pd();pf();zL=Object.getOwnPropertySymbols,RL=zL?function(i){for(var e=[];i;)dd(e,Ra(i)),i=Ia(i);return e}:md,gd=RL});function DL(i,e){return mr(i,gd(i),e)}var z7,R7=T(()=>{Ta();gf();z7=DL});function OL(i,e,t){var r=e(i);return Ii(i)?r:dd(r,t(i))}var xd,xf=T(()=>{ff();ya();xd=OL});function BL(i){return xd(i,Aa,Ra)}var bl,yf=T(()=>{xf();pd();od();bl=BL});function PL(i){return xd(i,pr,gd)}var D7,O7=T(()=>{xf();gf();ka();D7=PL});var FL,yd,B7=T(()=>{bn();Xi();FL=ni(ht,"DataView"),yd=FL});var qL,vd,P7=T(()=>{bn();Xi();qL=ni(ht,"Promise"),vd=qL});var HL,bd,F7=T(()=>{bn();Xi();HL=ni(ht,"Set"),bd=HL});var q7,UL,H7,U7,$7,j7,$L,jL,GL,WL,VL,xs,Ur,wl=T(()=>{B7();ld();P7();F7();Og();xa();J1();q7="[object Map]",UL="[object Object]",H7="[object Promise]",U7="[object Set]",$7="[object WeakMap]",j7="[object DataView]",$L=Pr(yd),jL=Pr(Nn),GL=Pr(vd),WL=Pr(bd),VL=Pr(Xh),xs=Ki;(yd&&xs(new yd(new ArrayBuffer(1)))!=j7||Nn&&xs(new Nn)!=q7||vd&&xs(vd.resolve())!=H7||bd&&xs(new bd)!=U7||Xh&&xs(new Xh)!=$7)&&(xs=function(i){var e=Ki(i),t=e==UL?i.constructor:void 0,r=t?Pr(t):"";if(r)switch(r){case $L:return j7;case jL:return q7;case GL:return H7;case WL:return U7;case VL:return $7}return e});Ur=xs});function KL(i){var e=i.length,t=new i.constructor(e);return e&&typeof i[0]=="string"&&XL.call(i,"index")&&(t.index=i.index,t.input=i.input),t}var YL,XL,G7,W7=T(()=>{YL=Object.prototype,XL=YL.hasOwnProperty;G7=KL});var ZL,Da,vf=T(()=>{Xi();ZL=ht.Uint8Array,Da=ZL});function QL(i){var e=new i.constructor(i.byteLength);return new Da(e).set(new Da(i)),e}var Oa,wd=T(()=>{vf();Oa=QL});function JL(i,e){var t=e?Oa(i.buffer):i.buffer;return new i.constructor(t,i.byteOffset,i.byteLength)}var V7,Y7=T(()=>{wd();V7=JL});function tI(i){var e=new i.constructor(i.source,eI.exec(i));return e.lastIndex=i.lastIndex,e}var eI,X7,K7=T(()=>{eI=/\w*$/;X7=tI});function iI(i){return Q7?Object(Q7.call(i)):{}}var Z7,Q7,J7,e8=T(()=>{dl();Z7=Li?Li.prototype:void 0,Q7=Z7?Z7.valueOf:void 0;J7=iI});function rI(i,e){var t=e?Oa(i.buffer):i.buffer;return new i.constructor(t,i.byteOffset,i.length)}var Md,bf=T(()=>{wd();Md=rI});function TI(i,e,t){var r=i.constructor;switch(e){case uI:return Oa(i);case nI:case sI:return new r(+i);case fI:return V7(i,t);case mI:case pI:case gI:case xI:case yI:case vI:case bI:case wI:case MI:return Md(i,t);case aI:return new r;case oI:case dI:return new r(i);case lI:return X7(i);case hI:return new r;case cI:return J7(i)}}var nI,sI,aI,oI,lI,hI,dI,cI,uI,fI,mI,pI,gI,xI,yI,vI,bI,wI,MI,t8,i8=T(()=>{wd();Y7();K7();e8();bf();nI="[object Boolean]",sI="[object Date]",aI="[object Map]",oI="[object Number]",lI="[object RegExp]",hI="[object Set]",dI="[object String]",cI="[object Symbol]",uI="[object ArrayBuffer]",fI="[object DataView]",mI="[object Float32Array]",pI="[object Float64Array]",gI="[object Int8Array]",xI="[object Int16Array]",yI="[object Int32Array]",vI="[object Uint8Array]",bI="[object Uint8ClampedArray]",wI="[object Uint16Array]",MI="[object Uint32Array]";t8=TI});function NI(i){return typeof i.constructor=="function"&&!Na(i)?Pg(Ia(i)):{}}var Td,wf=T(()=>{Fg();cd();td();Td=NI});function SI(i){return Mt(i)&&Ur(i)==EI}var EI,r8,n8=T(()=>{wl();Or();EI="[object Map]";r8=SI});var s8,AI,a8,o8=T(()=>{n8();id();rd();s8=qr&&qr.isMap,AI=s8?Ea(s8):r8,a8=AI});function CI(i){return Mt(i)&&Ur(i)==kI}var kI,l8,h8=T(()=>{wl();Or();kI="[object Set]";l8=CI});var d8,_I,c8,u8=T(()=>{h8();id();rd();d8=qr&&qr.isSet,_I=d8?Ea(d8):l8,c8=_I});function Nd(i,e,t,r,n,s){var a,o=e&LI,l=e&II,h=e&zI;if(t&&(a=n?t(i,r,n,s):t(i)),a!==void 0)return a;if(!St(i))return i;var d=Ii(i);if(d){if(a=G7(i),!o)return Kh(i,a)}else{var c=Ur(i),f=c==m8||c==PI;if(Fr(i))return fd(i,o);if(c==p8||c==f8||f&&!n){if(a=l||f?{}:Td(i),!o)return l?z7(i,M7(a,i)):L7(i,b7(a,i))}else{if(!Pe[c])return n?i:{};a=t8(i,c,o)}}s||(s=new Sn);var m=s.get(i);if(m)return m;s.set(i,a),c8(i)?i.forEach(function(y){a.add(Nd(y,e,t,y,i,s))}):a8(i)&&i.forEach(function(y,b){a.set(b,Nd(y,e,t,b,i,s))});var g=h?l?D7:bl:l?pr:Aa,x=d?void 0:g(i);return Kg(x||i,function(y,b){x&&(b=y,y=i[b]),Jh(a,b,Nd(y,e,t,b,i,s))}),a}var LI,II,zI,f8,RI,DI,OI,BI,m8,PI,FI,qI,p8,HI,UI,$I,jI,GI,WI,VI,YI,XI,KI,ZI,QI,JI,ez,tz,iz,Pe,g8,x8=T(()=>{ud();Zg();nf();w7();T7();mf();ef();I7();R7();yf();O7();wl();W7();i8();wf();ya();ml();o8();Br();u8();od();ka();LI=1,II=2,zI=4,f8="[object Arguments]",RI="[object Array]",DI="[object Boolean]",OI="[object Date]",BI="[object Error]",m8="[object Function]",PI="[object GeneratorFunction]",FI="[object Map]",qI="[object Number]",p8="[object Object]",HI="[object RegExp]",UI="[object Set]",$I="[object String]",jI="[object Symbol]",GI="[object WeakMap]",WI="[object ArrayBuffer]",VI="[object DataView]",YI="[object Float32Array]",XI="[object Float64Array]",KI="[object Int8Array]",ZI="[object Int16Array]",QI="[object Int32Array]",JI="[object Uint8Array]",ez="[object Uint8ClampedArray]",tz="[object Uint16Array]",iz="[object Uint32Array]",Pe={};Pe[f8]=Pe[RI]=Pe[WI]=Pe[VI]=Pe[DI]=Pe[OI]=Pe[YI]=Pe[XI]=Pe[KI]=Pe[ZI]=Pe[QI]=Pe[FI]=Pe[qI]=Pe[p8]=Pe[HI]=Pe[UI]=Pe[$I]=Pe[jI]=Pe[JI]=Pe[ez]=Pe[tz]=Pe[iz]=!0;Pe[BI]=Pe[m8]=Pe[GI]=!1;g8=Nd});function sz(i){return g8(i,rz|nz)}var rz,nz,gr,y8=T(()=>{x8();rz=1,nz=4;gr=sz});function oz(i){return this.__data__.set(i,az),this}var az,v8,b8=T(()=>{az="__lodash_hash_undefined__";v8=oz});function lz(i){return this.__data__.has(i)}var w8,M8=T(()=>{w8=lz});function Ed(i){var e=-1,t=i==null?0:i.length;for(this.__data__=new hd;++e{uf();b8();M8();Ed.prototype.add=Ed.prototype.push=v8;Ed.prototype.has=w8;T8=Ed});function hz(i,e){for(var t=-1,r=i==null?0:i.length;++t{E8=hz});function dz(i,e){return i.has(e)}var A8,k8=T(()=>{A8=dz});function fz(i,e,t,r,n,s){var a=t&cz,o=i.length,l=e.length;if(o!=l&&!(a&&l>o))return!1;var h=s.get(i),d=s.get(e);if(h&&d)return h==e&&d==i;var c=-1,f=!0,m=t&uz?new T8:void 0;for(s.set(i,e),s.set(e,i);++c{N8();S8();k8();cz=1,uz=2;Sd=fz});function mz(i){var e=-1,t=Array(i.size);return i.forEach(function(r,n){t[++e]=[n,r]}),t}var C8,_8=T(()=>{C8=mz});function pz(i){var e=-1,t=Array(i.size);return i.forEach(function(r){t[++e]=r}),t}var L8,I8=T(()=>{L8=pz});function Cz(i,e,t,r,n,s,a){switch(t){case kz:if(i.byteLength!=e.byteLength||i.byteOffset!=e.byteOffset)return!1;i=i.buffer,e=e.buffer;case Az:return!(i.byteLength!=e.byteLength||!s(new Da(i),new Da(e)));case yz:case vz:case Mz:return fr(+i,+e);case bz:return i.name==e.name&&i.message==e.message;case Tz:case Ez:return i==e+"";case wz:var o=C8;case Nz:var l=r&gz;if(o||(o=L8),i.size!=e.size&&!l)return!1;var h=a.get(i);if(h)return h==e;r|=xz,a.set(i,e);var d=Sd(o(i),o(e),r,n,s,a);return a.delete(i),d;case Sz:if(Tf)return Tf.call(i)==Tf.call(e)}return!1}var gz,xz,yz,vz,bz,wz,Mz,Tz,Nz,Ez,Sz,Az,kz,z8,Tf,R8,D8=T(()=>{dl();vf();Ma();Mf();_8();I8();gz=1,xz=2,yz="[object Boolean]",vz="[object Date]",bz="[object Error]",wz="[object Map]",Mz="[object Number]",Tz="[object RegExp]",Nz="[object Set]",Ez="[object String]",Sz="[object Symbol]",Az="[object ArrayBuffer]",kz="[object DataView]",z8=Li?Li.prototype:void 0,Tf=z8?z8.valueOf:void 0;R8=Cz});function zz(i,e,t,r,n,s){var a=t&_z,o=bl(i),l=o.length,h=bl(e),d=h.length;if(l!=d&&!a)return!1;for(var c=l;c--;){var f=o[c];if(!(a?f in e:Iz.call(e,f)))return!1}var m=s.get(i),g=s.get(e);if(m&&g)return m==e&&g==i;var x=!0;s.set(i,e),s.set(e,i);for(var y=a;++c{yf();_z=1,Lz=Object.prototype,Iz=Lz.hasOwnProperty;O8=zz});function Oz(i,e,t,r,n,s){var a=Ii(i),o=Ii(e),l=a?F8:Ur(i),h=o?F8:Ur(e);l=l==P8?Ad:l,h=h==P8?Ad:h;var d=l==Ad,c=h==Ad,f=l==h;if(f&&Fr(i)){if(!Fr(e))return!1;a=!0,d=!1}if(f&&!d)return s||(s=new Sn),a||Sa(i)?Sd(i,e,t,r,n,s):R8(i,e,l,t,r,n,s);if(!(t&Rz)){var m=d&&q8.call(i,"__wrapped__"),g=c&&q8.call(e,"__wrapped__");if(m||g){var x=m?i.value():i,y=g?e.value():e;return s||(s=new Sn),n(x,y,t,r,s)}}return f?(s||(s=new Sn),O8(i,e,t,r,n,s)):!1}var Rz,P8,F8,Ad,Dz,q8,H8,U8=T(()=>{ud();Mf();D8();B8();wl();ya();ml();nd();Rz=1,P8="[object Arguments]",F8="[object Array]",Ad="[object Object]",Dz=Object.prototype,q8=Dz.hasOwnProperty;H8=Oz});function $8(i,e,t,r,n){return i===e?!0:i==null||e==null||!Mt(i)&&!Mt(e)?i!==i&&e!==e:H8(i,e,t,r,$8,n)}var j8,G8=T(()=>{U8();Or();j8=$8});function Bz(i){return function(e,t,r){for(var n=-1,s=Object(e),a=r(e),o=a.length;o--;){var l=a[i?o:++n];if(t(s[l],l,s)===!1)break}return e}}var W8,V8=T(()=>{W8=Bz});var Pz,Y8,X8=T(()=>{V8();Pz=W8(),Y8=Pz});function Fz(i,e,t){(t!==void 0&&!fr(i[e],t)||t===void 0&&!(e in i))&&wa(i,e,t)}var Ml,Nf=T(()=>{Qh();Ma();Ml=Fz});function qz(i){return Mt(i)&&wn(i)}var K8,Z8=T(()=>{ul();Or();K8=qz});function Hz(i,e){if(!(e==="constructor"&&typeof i[e]=="function")&&e!="__proto__")return i[e]}var Tl,Ef=T(()=>{Tl=Hz});function Uz(i){return mr(i,pr(i))}var Q8,J8=T(()=>{Ta();ka();Q8=Uz});function $z(i,e,t,r,n,s,a){var o=Tl(i,t),l=Tl(e,t),h=a.get(l);if(h){Ml(i,t,h);return}var d=s?s(o,l,t+"",i,e,a):void 0,c=d===void 0;if(c){var f=Ii(l),m=!f&&Fr(l),g=!f&&!m&&Sa(l);d=l,f||m||g?Ii(o)?d=o:K8(o)?d=Kh(o):m?(c=!1,d=fd(l,!0)):g?(c=!1,d=Md(l,!0)):d=[]:l7(l)||fl(l)?(d=o,fl(o)?d=Q8(o):(!St(o)||va(o))&&(d=Td(l))):c=!1}c&&(a.set(l,d),n(d,l,r,s,a),a.delete(l)),Ml(i,t,d)}var ex,tx=T(()=>{Nf();mf();bf();ef();wf();of();ya();Z8();ml();Vh();Br();h7();nd();Ef();J8();ex=$z});function ix(i,e,t,r,n){i!==e&&Y8(e,function(s,a){if(n||(n=new Sn),St(s))ex(i,e,a,t,ix,r,n);else{var o=r?r(Tl(i,a),s,a+"",i,e,n):void 0;o===void 0&&(o=s),Ml(i,a,o)}},pr)}var rx,nx=T(()=>{ud();Nf();X8();tx();Br();ka();Ef();rx=ix});function jz(i,e){return j8(i,e)}var ys,sx=T(()=>{G8();ys=jz});var Gz,si,ax=T(()=>{nx();a5();Gz=s5(function(i,e,t){rx(i,e,t)}),si=Gz});var An=T(()=>{y8();sx();ax();});var Sl={};tt(Sl,{Attributor:()=>qt,AttributorStore:()=>Nl,BlockBlot:()=>vs,ClassAttributor:()=>dt,ContainerBlot:()=>Ha,EmbedBlot:()=>Ze,InlineBlot:()=>kd,LeafBlot:()=>ct,ParentBlot:()=>ai,Registry:()=>_n,Scope:()=>X,ScrollBlot:()=>El,StyleAttributor:()=>oi,TextBlot:()=>Ua});function ox(i,e){return(i.getAttribute("class")||"").split(/\s+/).filter(t=>t.indexOf(`${e}-`)===0)}function Sf(i){let e=i.split("-"),t=e.slice(1).map(r=>r[0].toUpperCase()+r.slice(1)).join("");return e[0]+t}function lx(i,e){let t=e.find(i);if(t)return t;try{return e.create(i)}catch{let r=e.create(X.INLINE);return Array.from(i.childNodes).forEach(n=>{r.domNode.appendChild(n)}),i.parentNode&&i.parentNode.replaceChild(r.domNode,i),r.attach(),r}}function Yz(i,e){if(Object.keys(i).length!==Object.keys(e).length)return!1;for(let t in i)if(i[t]!==e[t])return!1;return!0}var X,qt,Cn,hx,_n,kf,dt,Cf,oi,_f,Nl,dx,cx,ux,Wz,ct,Lf,fx,Vz,ai,Ba,Xz,kd,Fa,Kz,vs,zf,Zz,Ha,Rf,Ze,Qz,Jz,qa,eR,El,Df,tR,Ua,De=T(()=>{X=(i=>(i[i.TYPE=3]="TYPE",i[i.LEVEL=12]="LEVEL",i[i.ATTRIBUTE=13]="ATTRIBUTE",i[i.BLOT=14]="BLOT",i[i.INLINE=7]="INLINE",i[i.BLOCK=11]="BLOCK",i[i.BLOCK_BLOT=10]="BLOCK_BLOT",i[i.INLINE_BLOT=6]="INLINE_BLOT",i[i.BLOCK_ATTRIBUTE=9]="BLOCK_ATTRIBUTE",i[i.INLINE_ATTRIBUTE=5]="INLINE_ATTRIBUTE",i[i.ANY=15]="ANY",i))(X||{}),qt=class{constructor(e,t,r={}){this.attrName=e,this.keyName=t;let n=X.TYPE&X.ATTRIBUTE;this.scope=r.scope!=null?r.scope&X.LEVEL|n:X.ATTRIBUTE,r.whitelist!=null&&(this.whitelist=r.whitelist)}static keys(e){return Array.from(e.attributes).map(t=>t.name)}add(e,t){return this.canAdd(e,t)?(e.setAttribute(this.keyName,t),!0):!1}canAdd(e,t){return this.whitelist==null?!0:typeof t=="string"?this.whitelist.indexOf(t.replace(/["']/g,""))>-1:this.whitelist.indexOf(t)>-1}remove(e){e.removeAttribute(this.keyName)}value(e){let t=e.getAttribute(this.keyName);return this.canAdd(e,t)&&t?t:""}},Cn=class extends Error{constructor(e){e="[Parchment] "+e,super(e),this.message=e,this.name=this.constructor.name}},hx=class Af{constructor(){this.attributes={},this.classes={},this.tags={},this.types={}}static find(e,t=!1){if(e==null)return null;if(this.blots.has(e))return this.blots.get(e)||null;if(t){let r=null;try{r=e.parentNode}catch{return null}return this.find(r,t)}return null}create(e,t,r){let n=this.query(t);if(n==null)throw new Cn(`Unable to create ${t} blot`);let s=n,a=t instanceof Node||t.nodeType===Node.TEXT_NODE?t:s.create(r),o=new s(e,a,r);return Af.blots.set(o.domNode,o),o}find(e,t=!1){return Af.find(e,t)}query(e,t=X.ANY){let r;return typeof e=="string"?r=this.types[e]||this.attributes[e]:e instanceof Text||e.nodeType===Node.TEXT_NODE?r=this.types.text:typeof e=="number"?e&X.LEVEL&X.BLOCK?r=this.types.block:e&X.LEVEL&X.INLINE&&(r=this.types.inline):e instanceof Element&&((e.getAttribute("class")||"").split(/\s+/).some(n=>(r=this.classes[n],!!r)),r=r||this.tags[e.tagName]),r==null?null:"scope"in r&&t&X.LEVEL&r.scope&&t&X.TYPE&r.scope?r:null}register(...e){return e.map(t=>{let r="blotName"in t,n="attrName"in t;if(!r&&!n)throw new Cn("Invalid definition");if(r&&t.blotName==="abstract")throw new Cn("Cannot register abstract class");let s=r?t.blotName:n?t.attrName:void 0;return this.types[s]=t,n?typeof t.keyName=="string"&&(this.attributes[t.keyName]=t):r&&(t.className&&(this.classes[t.className]=t),t.tagName&&(Array.isArray(t.tagName)?t.tagName=t.tagName.map(a=>a.toUpperCase()):t.tagName=t.tagName.toUpperCase(),(Array.isArray(t.tagName)?t.tagName:[t.tagName]).forEach(a=>{(this.tags[a]==null||t.className==null)&&(this.tags[a]=t)}))),t})}};hx.blots=new WeakMap;_n=hx;kf=class extends qt{static keys(e){return(e.getAttribute("class")||"").split(/\s+/).map(t=>t.split("-").slice(0,-1).join("-"))}add(e,t){return this.canAdd(e,t)?(this.remove(e),e.classList.add(`${this.keyName}-${t}`),!0):!1}remove(e){ox(e,this.keyName).forEach(t=>{e.classList.remove(t)}),e.classList.length===0&&e.removeAttribute("class")}value(e){let t=(ox(e,this.keyName)[0]||"").slice(this.keyName.length+1);return this.canAdd(e,t)?t:""}},dt=kf;Cf=class extends qt{static keys(e){return(e.getAttribute("style")||"").split(";").map(t=>t.split(":")[0].trim())}add(e,t){return this.canAdd(e,t)?(e.style[Sf(this.keyName)]=t,!0):!1}remove(e){e.style[Sf(this.keyName)]="",e.getAttribute("style")||e.removeAttribute("style")}value(e){let t=e.style[Sf(this.keyName)];return this.canAdd(e,t)?t:""}},oi=Cf,_f=class{constructor(e){this.attributes={},this.domNode=e,this.build()}attribute(e,t){t?e.add(this.domNode,t)&&(e.value(this.domNode)!=null?this.attributes[e.attrName]=e:delete this.attributes[e.attrName]):(e.remove(this.domNode),delete this.attributes[e.attrName])}build(){this.attributes={};let e=_n.find(this.domNode);if(e==null)return;let t=qt.keys(this.domNode),r=dt.keys(this.domNode),n=oi.keys(this.domNode);t.concat(r).concat(n).forEach(s=>{let a=e.scroll.query(s,X.ATTRIBUTE);a instanceof qt&&(this.attributes[a.attrName]=a)})}copy(e){Object.keys(this.attributes).forEach(t=>{let r=this.attributes[t].value(this.domNode);e.format(t,r)})}move(e){this.copy(e),Object.keys(this.attributes).forEach(t=>{this.attributes[t].remove(this.domNode)}),this.attributes={}}values(){return Object.keys(this.attributes).reduce((e,t)=>(e[t]=this.attributes[t].value(this.domNode),e),{})}},Nl=_f,dx=class{constructor(e,t){this.scroll=e,this.domNode=t,_n.blots.set(t,this),this.prev=null,this.next=null}static create(e){if(this.tagName==null)throw new Cn("Blot definition missing tagName");let t,r;return Array.isArray(this.tagName)?(typeof e=="string"?(r=e.toUpperCase(),parseInt(r,10).toString()===r&&(r=parseInt(r,10))):typeof e=="number"&&(r=e),typeof r=="number"?t=document.createElement(this.tagName[r-1]):r&&this.tagName.indexOf(r)>-1?t=document.createElement(r):t=document.createElement(this.tagName[0])):t=document.createElement(this.tagName),this.className&&t.classList.add(this.className),t}get statics(){return this.constructor}attach(){}clone(){let e=this.domNode.cloneNode(!1);return this.scroll.create(e)}detach(){this.parent!=null&&this.parent.removeChild(this),_n.blots.delete(this.domNode)}deleteAt(e,t){this.isolate(e,t).remove()}formatAt(e,t,r,n){let s=this.isolate(e,t);if(this.scroll.query(r,X.BLOT)!=null&&n)s.wrap(r,n);else if(this.scroll.query(r,X.ATTRIBUTE)!=null){let a=this.scroll.create(this.statics.scope);s.wrap(a),a.format(r,n)}}insertAt(e,t,r){let n=r==null?this.scroll.create("text",t):this.scroll.create(t,r),s=this.split(e);this.parent.insertBefore(n,s||void 0)}isolate(e,t){let r=this.split(e);if(r==null)throw new Error("Attempt to isolate at end");return r.split(t),r}length(){return 1}offset(e=this.parent){return this.parent==null||this===e?0:this.parent.children.offset(this)+this.parent.offset(e)}optimize(e){this.statics.requiredContainer&&!(this.parent instanceof this.statics.requiredContainer)&&this.wrap(this.statics.requiredContainer.blotName)}remove(){this.domNode.parentNode!=null&&this.domNode.parentNode.removeChild(this.domNode),this.detach()}replaceWith(e,t){let r=typeof e=="string"?this.scroll.create(e,t):e;return this.parent!=null&&(this.parent.insertBefore(r,this.next||void 0),this.remove()),r}split(e,t){return e===0?this:this.next}update(e,t){}wrap(e,t){let r=typeof e=="string"?this.scroll.create(e,t):e;if(this.parent!=null&&this.parent.insertBefore(r,this.next||void 0),typeof r.appendChild!="function")throw new Cn(`Cannot wrap ${e}`);return r.appendChild(this),r}};dx.blotName="abstract";cx=dx,ux=class extends cx{static value(e){return!0}index(e,t){return this.domNode===e||this.domNode.compareDocumentPosition(e)&Node.DOCUMENT_POSITION_CONTAINED_BY?Math.min(t,1):-1}position(e,t){let r=Array.from(this.parent.domNode.childNodes).indexOf(this.domNode);return e>0&&(r+=1),[this.parent.domNode,r]}value(){return{[this.statics.blotName]:this.statics.value(this.domNode)||!0}}};ux.scope=X.INLINE_BLOT;Wz=ux,ct=Wz,Lf=class{constructor(){this.head=null,this.tail=null,this.length=0}append(...e){if(this.insertBefore(e[0],null),e.length>1){let t=e.slice(1);this.append(...t)}}at(e){let t=this.iterator(),r=t();for(;r&&e>0;)e-=1,r=t();return r}contains(e){let t=this.iterator(),r=t();for(;r;){if(r===e)return!0;r=t()}return!1}indexOf(e){let t=this.iterator(),r=t(),n=0;for(;r;){if(r===e)return n;n+=1,r=t()}return-1}insertBefore(e,t){e!=null&&(this.remove(e),e.next=t,t!=null?(e.prev=t.prev,t.prev!=null&&(t.prev.next=e),t.prev=e,t===this.head&&(this.head=e)):this.tail!=null?(this.tail.next=e,e.prev=this.tail,this.tail=e):(e.prev=null,this.head=this.tail=e),this.length+=1)}offset(e){let t=0,r=this.head;for(;r!=null;){if(r===e)return t;t+=r.length(),r=r.next}return-1}remove(e){this.contains(e)&&(e.prev!=null&&(e.prev.next=e.next),e.next!=null&&(e.next.prev=e.prev),e===this.head&&(this.head=e.next),e===this.tail&&(this.tail=e.prev),this.length-=1)}iterator(e=this.head){return()=>{let t=e;return e!=null&&(e=e.next),t}}find(e,t=!1){let r=this.iterator(),n=r();for(;n;){let s=n.length();if(ea?r(l,e-a,Math.min(t,a+h-e)):r(l,0,Math.min(h,e+t-a)),a+=h,l=o()}}map(e){return this.reduce((t,r)=>(t.push(e(r)),t),[])}reduce(e,t){let r=this.iterator(),n=r();for(;n;)t=e(t,n),n=r();return t}};fx=class kn extends cx{constructor(e,t){super(e,t),this.uiNode=null,this.build()}appendChild(e){this.insertBefore(e)}attach(){super.attach(),this.children.forEach(e=>{e.attach()})}attachUI(e){this.uiNode!=null&&this.uiNode.remove(),this.uiNode=e,kn.uiClass&&this.uiNode.classList.add(kn.uiClass),this.uiNode.setAttribute("contenteditable","false"),this.domNode.insertBefore(this.uiNode,this.domNode.firstChild)}build(){this.children=new Lf,Array.from(this.domNode.childNodes).filter(e=>e!==this.uiNode).reverse().forEach(e=>{try{let t=lx(e,this.scroll);this.insertBefore(t,this.children.head||void 0)}catch(t){if(t instanceof Cn)return;throw t}})}deleteAt(e,t){if(e===0&&t===this.length())return this.remove();this.children.forEachAt(e,t,(r,n,s)=>{r.deleteAt(n,s)})}descendant(e,t=0){let[r,n]=this.children.find(t);return e.blotName==null&&e(r)||e.blotName!=null&&r instanceof e?[r,n]:r instanceof kn?r.descendant(e,n):[null,-1]}descendants(e,t=0,r=Number.MAX_VALUE){let n=[],s=r;return this.children.forEachAt(t,r,(a,o,l)=>{(e.blotName==null&&e(a)||e.blotName!=null&&a instanceof e)&&n.push(a),a instanceof kn&&(n=n.concat(a.descendants(e,o,s))),s-=l}),n}detach(){this.children.forEach(e=>{e.detach()}),super.detach()}enforceAllowedChildren(){let e=!1;this.children.forEach(t=>{e||this.statics.allowedChildren.some(r=>t instanceof r)||(t.statics.scope===X.BLOCK_BLOT?(t.next!=null&&this.splitAfter(t),t.prev!=null&&this.splitAfter(t.prev),t.parent.unwrap(),e=!0):t instanceof kn?t.unwrap():t.remove())})}formatAt(e,t,r,n){this.children.forEachAt(e,t,(s,a,o)=>{s.formatAt(a,o,r,n)})}insertAt(e,t,r){let[n,s]=this.children.find(e);if(n)n.insertAt(s,t,r);else{let a=r==null?this.scroll.create("text",t):this.scroll.create(t,r);this.appendChild(a)}}insertBefore(e,t){e.parent!=null&&e.parent.children.remove(e);let r=null;this.children.insertBefore(e,t||null),e.parent=this,t!=null&&(r=t.domNode),(this.domNode.parentNode!==e.domNode||this.domNode.nextSibling!==r)&&this.domNode.insertBefore(e.domNode,r),e.attach()}length(){return this.children.reduce((e,t)=>e+t.length(),0)}moveChildren(e,t){this.children.forEach(r=>{e.insertBefore(r,t)})}optimize(e){if(super.optimize(e),this.enforceAllowedChildren(),this.uiNode!=null&&this.uiNode!==this.domNode.firstChild&&this.domNode.insertBefore(this.uiNode,this.domNode.firstChild),this.children.length===0)if(this.statics.defaultChild!=null){let t=this.scroll.create(this.statics.defaultChild.blotName);this.appendChild(t)}else this.remove()}path(e,t=!1){let[r,n]=this.children.find(e,t),s=[[this,e]];return r instanceof kn?s.concat(r.path(n,t)):(r!=null&&s.push([r,n]),s)}removeChild(e){this.children.remove(e)}replaceWith(e,t){let r=typeof e=="string"?this.scroll.create(e,t):e;return r instanceof kn&&this.moveChildren(r),super.replaceWith(r)}split(e,t=!1){if(!t){if(e===0)return this;if(e===this.length())return this.next}let r=this.clone();return this.parent&&this.parent.insertBefore(r,this.next||void 0),this.children.forEachAt(e,this.length(),(n,s,a)=>{let o=n.split(s,t);o!=null&&r.appendChild(o)}),r}splitAfter(e){let t=this.clone();for(;e.next!=null;)t.appendChild(e.next);return this.parent&&this.parent.insertBefore(t,this.next||void 0),t}unwrap(){this.parent&&this.moveChildren(this.parent,this.next||void 0),this.remove()}update(e,t){let r=[],n=[];e.forEach(s=>{s.target===this.domNode&&s.type==="childList"&&(r.push(...s.addedNodes),n.push(...s.removedNodes))}),n.forEach(s=>{if(s.parentNode!=null&&s.tagName!=="IFRAME"&&document.body.compareDocumentPosition(s)&Node.DOCUMENT_POSITION_CONTAINED_BY)return;let a=this.scroll.find(s);a!=null&&(a.domNode.parentNode==null||a.domNode.parentNode===this.domNode)&&a.detach()}),r.filter(s=>s.parentNode===this.domNode&&s!==this.uiNode).sort((s,a)=>s===a?0:s.compareDocumentPosition(a)&Node.DOCUMENT_POSITION_FOLLOWING?1:-1).forEach(s=>{let a=null;s.nextSibling!=null&&(a=this.scroll.find(s.nextSibling));let o=lx(s,this.scroll);(o.next!==a||o.next==null)&&(o.parent!=null&&o.parent.removeChild(this),this.insertBefore(o,a||void 0))}),this.enforceAllowedChildren()}};fx.uiClass="";Vz=fx,ai=Vz;Ba=class Pa extends ai{static create(e){return super.create(e)}static formats(e,t){let r=t.query(Pa.blotName);if(!(r!=null&&e.tagName===r.tagName)){if(typeof this.tagName=="string")return!0;if(Array.isArray(this.tagName))return e.tagName.toLowerCase()}}constructor(e,t){super(e,t),this.attributes=new Nl(this.domNode)}format(e,t){if(e===this.statics.blotName&&!t)this.children.forEach(r=>{r instanceof Pa||(r=r.wrap(Pa.blotName,!0)),this.attributes.copy(r)}),this.unwrap();else{let r=this.scroll.query(e,X.INLINE);if(r==null)return;r instanceof qt?this.attributes.attribute(r,t):t&&(e!==this.statics.blotName||this.formats()[e]!==t)&&this.replaceWith(e,t)}}formats(){let e=this.attributes.values(),t=this.statics.formats(this.domNode,this.scroll);return t!=null&&(e[this.statics.blotName]=t),e}formatAt(e,t,r,n){this.formats()[r]!=null||this.scroll.query(r,X.ATTRIBUTE)?this.isolate(e,t).format(r,n):super.formatAt(e,t,r,n)}optimize(e){super.optimize(e);let t=this.formats();if(Object.keys(t).length===0)return this.unwrap();let r=this.next;r instanceof Pa&&r.prev===this&&Yz(t,r.formats())&&(r.moveChildren(this),r.remove())}replaceWith(e,t){let r=super.replaceWith(e,t);return this.attributes.copy(r),r}update(e,t){super.update(e,t),e.some(r=>r.target===this.domNode&&r.type==="attributes")&&this.attributes.build()}wrap(e,t){let r=super.wrap(e,t);return r instanceof Pa&&this.attributes.move(r),r}};Ba.allowedChildren=[Ba,ct],Ba.blotName="inline",Ba.scope=X.INLINE_BLOT,Ba.tagName="SPAN";Xz=Ba,kd=Xz,Fa=class If extends ai{static create(e){return super.create(e)}static formats(e,t){let r=t.query(If.blotName);if(!(r!=null&&e.tagName===r.tagName)){if(typeof this.tagName=="string")return!0;if(Array.isArray(this.tagName))return e.tagName.toLowerCase()}}constructor(e,t){super(e,t),this.attributes=new Nl(this.domNode)}format(e,t){let r=this.scroll.query(e,X.BLOCK);r!=null&&(r instanceof qt?this.attributes.attribute(r,t):e===this.statics.blotName&&!t?this.replaceWith(If.blotName):t&&(e!==this.statics.blotName||this.formats()[e]!==t)&&this.replaceWith(e,t))}formats(){let e=this.attributes.values(),t=this.statics.formats(this.domNode,this.scroll);return t!=null&&(e[this.statics.blotName]=t),e}formatAt(e,t,r,n){this.scroll.query(r,X.BLOCK)!=null?this.format(r,n):super.formatAt(e,t,r,n)}insertAt(e,t,r){if(r==null||this.scroll.query(t,X.INLINE)!=null)super.insertAt(e,t,r);else{let n=this.split(e);if(n!=null){let s=this.scroll.create(t,r);n.parent.insertBefore(s,n)}else throw new Error("Attempt to insertAt after block boundaries")}}replaceWith(e,t){let r=super.replaceWith(e,t);return this.attributes.copy(r),r}update(e,t){super.update(e,t),e.some(r=>r.target===this.domNode&&r.type==="attributes")&&this.attributes.build()}};Fa.blotName="block",Fa.scope=X.BLOCK_BLOT,Fa.tagName="P",Fa.allowedChildren=[kd,Fa,ct];Kz=Fa,vs=Kz,zf=class extends ai{checkMerge(){return this.next!==null&&this.next.statics.blotName===this.statics.blotName}deleteAt(e,t){super.deleteAt(e,t),this.enforceAllowedChildren()}formatAt(e,t,r,n){super.formatAt(e,t,r,n),this.enforceAllowedChildren()}insertAt(e,t,r){super.insertAt(e,t,r),this.enforceAllowedChildren()}optimize(e){super.optimize(e),this.children.length>0&&this.next!=null&&this.checkMerge()&&(this.next.moveChildren(this),this.next.remove())}};zf.blotName="container",zf.scope=X.BLOCK_BLOT;Zz=zf,Ha=Zz,Rf=class extends ct{static formats(e,t){}format(e,t){super.formatAt(0,this.length(),e,t)}formatAt(e,t,r,n){e===0&&t===this.length()?this.format(r,n):super.formatAt(e,t,r,n)}formats(){return this.statics.formats(this.domNode,this.scroll)}},Ze=Rf,Qz={attributes:!0,characterData:!0,characterDataOldValue:!0,childList:!0,subtree:!0},Jz=100,qa=class extends ai{constructor(e,t){super(null,t),this.registry=e,this.scroll=this,this.build(),this.observer=new MutationObserver(r=>{this.update(r)}),this.observer.observe(this.domNode,Qz),this.attach()}create(e,t){return this.registry.create(this,e,t)}find(e,t=!1){let r=this.registry.find(e,t);return r?r.scroll===this?r:t?this.find(r.scroll.domNode.parentNode,!0):null:null}query(e,t=X.ANY){return this.registry.query(e,t)}register(...e){return this.registry.register(...e)}build(){this.scroll!=null&&super.build()}detach(){super.detach(),this.observer.disconnect()}deleteAt(e,t){this.update(),e===0&&t===this.length()?this.children.forEach(r=>{r.remove()}):super.deleteAt(e,t)}formatAt(e,t,r,n){this.update(),super.formatAt(e,t,r,n)}insertAt(e,t,r){this.update(),super.insertAt(e,t,r)}optimize(e=[],t={}){super.optimize(t);let r=t.mutationsMap||new WeakMap,n=Array.from(this.observer.takeRecords());for(;n.length>0;)e.push(n.pop());let s=(l,h=!0)=>{l==null||l===this||l.domNode.parentNode!=null&&(r.has(l.domNode)||r.set(l.domNode,[]),h&&s(l.parent))},a=l=>{r.has(l.domNode)&&(l instanceof ai&&l.children.forEach(a),r.delete(l.domNode),l.optimize(t))},o=e;for(let l=0;o.length>0;l+=1){if(l>=Jz)throw new Error("[Parchment] Maximum optimize iterations reached");for(o.forEach(h=>{let d=this.find(h.target,!0);d!=null&&(d.domNode===h.target&&(h.type==="childList"?(s(this.find(h.previousSibling,!1)),Array.from(h.addedNodes).forEach(c=>{let f=this.find(c,!1);s(f,!1),f instanceof ai&&f.children.forEach(m=>{s(m,!1)})})):h.type==="attributes"&&s(d.prev)),s(d))}),this.children.forEach(a),o=Array.from(this.observer.takeRecords()),n=o.slice();n.length>0;)e.push(n.pop())}}update(e,t={}){e=e||this.observer.takeRecords();let r=new WeakMap;e.map(n=>{let s=this.find(n.target,!0);return s==null?null:r.has(s.domNode)?(r.get(s.domNode).push(n),null):(r.set(s.domNode,[n]),s)}).forEach(n=>{n!=null&&n!==this&&r.has(n.domNode)&&n.update(r.get(n.domNode)||[],t)}),t.mutationsMap=r,r.has(this.domNode)&&super.update(r.get(this.domNode),t),this.optimize(e,t)}};qa.blotName="scroll",qa.defaultChild=vs,qa.allowedChildren=[vs,Ha],qa.scope=X.BLOCK_BLOT,qa.tagName="DIV";eR=qa,El=eR,Df=class mx extends ct{static create(e){return document.createTextNode(e)}static value(e){return e.data}constructor(e,t){super(e,t),this.text=this.statics.value(this.domNode)}deleteAt(e,t){this.domNode.data=this.text=this.text.slice(0,e)+this.text.slice(e+t)}index(e,t){return this.domNode===e?t:-1}insertAt(e,t,r){r==null?(this.text=this.text.slice(0,e)+t+this.text.slice(e),this.domNode.data=this.text):super.insertAt(e,t,r)}length(){return this.text.length}optimize(e){super.optimize(e),this.text=this.statics.value(this.domNode),this.text.length===0?this.remove():this.next instanceof mx&&this.next.prev===this&&(this.insertAt(this.length(),this.next.value()),this.next.remove())}position(e,t=!1){return[this.domNode,e]}split(e,t=!1){if(!t){if(e===0)return this;if(e===this.length())return this.next}let r=this.scroll.create(this.domNode.splitText(e));return this.parent.insertBefore(r,this.next||void 0),this.text=this.statics.value(this.domNode),r}update(e,t){e.some(r=>r.type==="characterData"&&r.target===this.domNode)&&(this.text=this.statics.value(this.domNode))}value(){return this.text}};Df.blotName="text",Df.scope=X.INLINE_BLOT;tR=Df,Ua=tR});var Ex=tr((cQ,Nx)=>{var gi=-1,Ht=1,pt=0;function Al(i,e,t,r,n){if(i===e)return i?[[pt,i]]:[];if(t!=null){var s=dR(i,e,t);if(s)return s}var a=Bf(i,e),o=i.substring(0,a);i=i.substring(a),e=e.substring(a),a=Cd(i,e);var l=i.substring(i.length-a);i=i.substring(0,i.length-a),e=e.substring(0,e.length-a);var h=iR(i,e);return o&&h.unshift([pt,o]),l&&h.push([pt,l]),Pf(h,n),r&&sR(h),h}function iR(i,e){var t;if(!i)return[[Ht,e]];if(!e)return[[gi,i]];var r=i.length>e.length?i:e,n=i.length>e.length?e:i,s=r.indexOf(n);if(s!==-1)return t=[[Ht,r.substring(0,s)],[pt,n],[Ht,r.substring(s+n.length)]],i.length>e.length&&(t[0][0]=t[2][0]=gi),t;if(n.length===1)return[[gi,i],[Ht,e]];var a=nR(i,e);if(a){var o=a[0],l=a[1],h=a[2],d=a[3],c=a[4],f=Al(o,h),m=Al(l,d);return f.concat([[pt,c]],m)}return rR(i,e)}function rR(i,e){for(var t=i.length,r=e.length,n=Math.ceil((t+r)/2),s=n,a=2*n,o=new Array(a),l=new Array(a),h=0;ht)m+=2;else if(L>r)f+=2;else if(c){var I=s+d-b;if(I>=0&&I=O)return px(i,e,k,L)}}}for(var q=-y+g;q<=y-x;q+=2){var I=s+q,O;q===-y||q!==y&&l[I-1]t)x+=2;else if(F>r)g+=2;else if(!c){var E=s+d-q;if(E>=0&&E=O)return px(i,e,k,L)}}}}return[[gi,i],[Ht,e]]}function px(i,e,t,r){var n=i.substring(0,t),s=e.substring(0,r),a=i.substring(t),o=e.substring(r),l=Al(n,s),h=Al(a,o);return l.concat(h)}function Bf(i,e){if(!i||!e||i.charAt(0)!==e.charAt(0))return 0;for(var t=0,r=Math.min(i.length,e.length),n=r,s=0;tr?i=i.substring(t-r):te.length?i:e,r=i.length>e.length?e:i;if(t.length<4||r.length*2=m.length?[k,L,I,O,E]:null}var s=n(t,r,Math.ceil(t.length/4)),a=n(t,r,Math.ceil(t.length/2)),o;if(!s&&!a)return null;a?s?o=s[4].length>a[4].length?s:a:o=a:o=s;var l,h,d,c;i.length>e.length?(l=o[0],h=o[1],d=o[2],c=o[3]):(d=o[0],c=o[1],l=o[2],h=o[3]);var f=o[4];return[l,h,d,c,f]}function sR(i){for(var e=!1,t=[],r=0,n=null,s=0,a=0,o=0,l=0,h=0;s0?t[r-1]:-1,a=0,o=0,l=0,h=0,n=null,e=!0)),s++;for(e&&Pf(i),lR(i),s=1;s=m?(f>=d.length/2||f>=c.length/2)&&(i.splice(s,0,[pt,c.substring(0,f)]),i[s-1][1]=d.substring(0,d.length-f),i[s+1][1]=c.substring(f),s++):(m>=d.length/2||m>=c.length/2)&&(i.splice(s,0,[pt,d.substring(0,m)]),i[s-1][0]=Ht,i[s-1][1]=c.substring(0,c.length-m),i[s+1][0]=gi,i[s+1][1]=d.substring(m),s++),s++}s++}}var xx=/[^a-zA-Z0-9]/,yx=/\s/,vx=/[\r\n]/,aR=/\n\r?\n$/,oR=/^\r?\n\r?\n/;function lR(i){function e(m,g){if(!m||!g)return 6;var x=m.charAt(m.length-1),y=g.charAt(0),b=x.match(xx),E=y.match(xx),k=b&&x.match(yx),L=E&&y.match(yx),I=k&&x.match(vx),O=L&&y.match(vx),q=I&&m.match(aR),F=O&&g.match(oR);return q||F?5:I||O?4:b&&!k&&L?3:k||L?2:b||E?1:0}for(var t=1;t=c&&(c=f,l=r,h=n,d=s)}i[t-1][1]!=l&&(l?i[t-1][1]=l:(i.splice(t-1,1),t--),i[t][1]=h,d?i[t+1][1]=d:(i.splice(t+1,1),t--))}t++}}function Pf(i,e){i.push([pt,""]);for(var t=0,r=0,n=0,s="",a="",o;t=0&&Tx(i[l][1])){var h=i[l][1].slice(-1);if(i[l][1]=i[l][1].slice(0,-1),s=h+s,a=h+a,!i[l][1]){i.splice(l,1),t--;var d=l-1;i[d]&&i[d][0]===Ht&&(n++,a=i[d][1]+a,d--),i[d]&&i[d][0]===gi&&(r++,s=i[d][1]+s,d--),l=d}}if(Mx(i[t][1])){var h=i[t][1].charAt(0);i[t][1]=i[t][1].slice(1),s+=h,a+=h}}if(t0||a.length>0){s.length>0&&a.length>0&&(o=Bf(a,s),o!==0&&(l>=0?i[l][1]+=a.substring(0,o):(i.splice(0,0,[pt,a.substring(0,o)]),t++),a=a.substring(o),s=s.substring(o)),o=Cd(a,s),o!==0&&(i[t][1]=a.substring(a.length-o)+i[t][1],a=a.substring(0,a.length-o),s=s.substring(0,s.length-o)));var c=n+r;s.length===0&&a.length===0?(i.splice(t-c,c),t=t-c):s.length===0?(i.splice(t-c,c,[Ht,a]),t=t-c+1):a.length===0?(i.splice(t-c,c,[gi,s]),t=t-c+1):(i.splice(t-c,c,[gi,s],[Ht,a]),t=t-c+2)}t!==0&&i[t-1][0]===pt?(i[t-1][1]+=i[t][1],i.splice(t,1)):t++,n=0,r=0,s="",a="";break}}i[i.length-1][1]===""&&i.pop();var f=!1;for(t=1;t=55296&&i<=56319}function wx(i){return i>=56320&&i<=57343}function Mx(i){return wx(i.charCodeAt(0))}function Tx(i){return bx(i.charCodeAt(i.length-1))}function hR(i){for(var e=[],t=0;t0&&e.push(i[t]);return e}function Of(i,e,t,r){return Tx(i)||Mx(r)?null:hR([[pt,i],[gi,e],[Ht,t],[pt,r]])}function dR(i,e,t){var r=typeof t=="number"?{index:t,length:0}:t.oldRange,n=typeof t=="number"?null:t.newRange,s=i.length,a=e.length;if(r.length===0&&(n===null||n.length===0)){var o=r.index,l=i.slice(0,o),h=i.slice(o),d=n?n.index:null;e:{var c=o+a-s;if(d!==null&&d!==c||c<0||c>a)break e;var f=e.slice(0,c),m=e.slice(c);if(m!==h)break e;var g=Math.min(o,c),x=l.slice(0,g),y=f.slice(0,g);if(x!==y)break e;var b=l.slice(g),E=f.slice(g);return Of(x,b,E,h)}e:{if(d!==null&&d!==o)break e;var k=o,f=e.slice(0,k),m=e.slice(k);if(f!==l)break e;var L=Math.min(s-k,a-k),I=h.slice(h.length-L),O=m.slice(m.length-L);if(I!==O)break e;var b=h.slice(0,h.length-L),E=m.slice(0,m.length-L);return Of(l,b,E,I)}}if(r.length>0&&n&&n.length===0)e:{var x=i.slice(0,r.index),I=i.slice(r.index+r.length),g=x.length,L=I.length;if(a{var cR=200,Bx="__lodash_hash_undefined__",Px=9007199254740991,Wf="[object Arguments]",uR="[object Array]",Fx="[object Boolean]",qx="[object Date]",fR="[object Error]",Vf="[object Function]",Hx="[object GeneratorFunction]",Ld="[object Map]",Ux="[object Number]",Yf="[object Object]",Sx="[object Promise]",$x="[object RegExp]",Id="[object Set]",jx="[object String]",Gx="[object Symbol]",qf="[object WeakMap]",Wx="[object ArrayBuffer]",zd="[object DataView]",Vx="[object Float32Array]",Yx="[object Float64Array]",Xx="[object Int8Array]",Kx="[object Int16Array]",Zx="[object Int32Array]",Qx="[object Uint8Array]",Jx="[object Uint8ClampedArray]",e9="[object Uint16Array]",t9="[object Uint32Array]",mR=/[\\^$.*+?()[\]{}|]/g,pR=/\w*$/,gR=/^\[object .+?Constructor\]$/,xR=/^(?:0|[1-9]\d*)$/,Fe={};Fe[Wf]=Fe[uR]=Fe[Wx]=Fe[zd]=Fe[Fx]=Fe[qx]=Fe[Vx]=Fe[Yx]=Fe[Xx]=Fe[Kx]=Fe[Zx]=Fe[Ld]=Fe[Ux]=Fe[Yf]=Fe[$x]=Fe[Id]=Fe[jx]=Fe[Gx]=Fe[Qx]=Fe[Jx]=Fe[e9]=Fe[t9]=!0;Fe[fR]=Fe[Vf]=Fe[qf]=!1;var yR=typeof global=="object"&&global&&global.Object===Object&&global,vR=typeof self=="object"&&self&&self.Object===Object&&self,$r=yR||vR||Function("return this")(),i9=typeof kl=="object"&&kl&&!kl.nodeType&&kl,Ax=i9&&typeof $a=="object"&&$a&&!$a.nodeType&&$a,bR=Ax&&Ax.exports===i9;function wR(i,e){return i.set(e[0],e[1]),i}function MR(i,e){return i.add(e),i}function TR(i,e){for(var t=-1,r=i?i.length:0;++t-1}function KR(i,e){var t=this.__data__,r=Od(t,i);return r<0?t.push([i,e]):t[r][1]=e,this}jr.prototype.clear=WR;jr.prototype.delete=VR;jr.prototype.get=YR;jr.prototype.has=XR;jr.prototype.set=KR;function ja(i){var e=-1,t=i?i.length:0;for(this.clear();++e-1&&i%1==0&&i-1&&i%1==0&&i<=Px}function Pd(i){var e=typeof i;return!!i&&(e=="object"||e=="function")}function DD(i){return!!i&&typeof i=="object"}function Qf(i){return c9(i)?oD(i):fD(i)}function OD(){return[]}function BD(){return!1}$a.exports=_D});var cm=tr((Ll,Ya)=>{var PD=200,dm="__lodash_hash_undefined__",Wd=1,N9=2,E9=9007199254740991,Fd="[object Arguments]",rm="[object Array]",FD="[object AsyncFunction]",S9="[object Boolean]",A9="[object Date]",k9="[object Error]",C9="[object Function]",qD="[object GeneratorFunction]",qd="[object Map]",_9="[object Number]",HD="[object Null]",Va="[object Object]",f9="[object Promise]",UD="[object Proxy]",L9="[object RegExp]",Hd="[object Set]",I9="[object String]",$D="[object Symbol]",jD="[object Undefined]",nm="[object WeakMap]",z9="[object ArrayBuffer]",Ud="[object DataView]",GD="[object Float32Array]",WD="[object Float64Array]",VD="[object Int8Array]",YD="[object Int16Array]",XD="[object Int32Array]",KD="[object Uint8Array]",ZD="[object Uint8ClampedArray]",QD="[object Uint16Array]",JD="[object Uint32Array]",eO=/[\\^$.*+?()[\]{}|]/g,tO=/^\[object .+?Constructor\]$/,iO=/^(?:0|[1-9]\d*)$/,je={};je[GD]=je[WD]=je[VD]=je[YD]=je[XD]=je[KD]=je[ZD]=je[QD]=je[JD]=!0;je[Fd]=je[rm]=je[z9]=je[S9]=je[Ud]=je[A9]=je[k9]=je[C9]=je[qd]=je[_9]=je[Va]=je[L9]=je[Hd]=je[I9]=je[nm]=!1;var R9=typeof global=="object"&&global&&global.Object===Object&&global,rO=typeof self=="object"&&self&&self.Object===Object&&self,Gr=R9||rO||Function("return this")(),D9=typeof Ll=="object"&&Ll&&!Ll.nodeType&&Ll,m9=D9&&typeof Ya=="object"&&Ya&&!Ya.nodeType&&Ya,O9=m9&&m9.exports===D9,em=O9&&R9.process,p9=(function(){try{return em&&em.binding&&em.binding("util")}catch{}})(),g9=p9&&p9.isTypedArray;function nO(i,e){for(var t=-1,r=i==null?0:i.length,n=0,s=[];++t-1}function RO(i,e){var t=this.__data__,r=Yd(t,i);return r<0?(++this.size,t.push([i,e])):t[r][1]=e,this}Wr.prototype.clear=_O;Wr.prototype.delete=LO;Wr.prototype.get=IO;Wr.prototype.has=zO;Wr.prototype.set=RO;function Es(i){var e=-1,t=i==null?0:i.length;for(this.clear();++eo))return!1;var h=s.get(i);if(h&&s.get(e))return h==e;var d=-1,c=!0,f=t&N9?new jd:void 0;for(s.set(i,e),s.set(e,i);++d-1&&i%1==0&&i-1&&i%1==0&&i<=E9}function G9(i){var e=typeof i;return i!=null&&(e=="object"||e=="function")}function Rl(i){return i!=null&&typeof i=="object"}var W9=g9?lO(g9):ZO;function cB(i){return hB(i)?VO(i):QO(i)}function uB(){return[]}function fB(){return!1}Ya.exports=dB});var V9=tr(fm=>{"use strict";Object.defineProperty(fm,"__esModule",{value:!0});var mB=Jf(),pB=cm(),um;(function(i){function e(s={},a={},o=!1){typeof s!="object"&&(s={}),typeof a!="object"&&(a={});let l=mB(a);o||(l=Object.keys(l).reduce((h,d)=>(l[d]!=null&&(h[d]=l[d]),h),{}));for(let h in s)s[h]!==void 0&&a[h]===void 0&&(l[h]=s[h]);return Object.keys(l).length>0?l:void 0}i.compose=e;function t(s={},a={}){typeof s!="object"&&(s={}),typeof a!="object"&&(a={});let o=Object.keys(s).concat(Object.keys(a)).reduce((l,h)=>(pB(s[h],a[h])||(l[h]=a[h]===void 0?null:a[h]),l),{});return Object.keys(o).length>0?o:void 0}i.diff=t;function r(s={},a={}){s=s||{};let o=Object.keys(a).reduce((l,h)=>(a[h]!==s[h]&&s[h]!==void 0&&(l[h]=a[h]),l),{});return Object.keys(s).reduce((l,h)=>(s[h]!==a[h]&&a[h]===void 0&&(l[h]=null),l),o)}i.invert=r;function n(s,a,o=!1){if(typeof s!="object")return a;if(typeof a!="object")return;if(!o)return a;let l=Object.keys(a).reduce((h,d)=>(s[d]===void 0&&(h[d]=a[d]),h),{});return Object.keys(l).length>0?l:void 0}i.transform=n})(um||(um={}));fm.default=um});var gm=tr(pm=>{"use strict";Object.defineProperty(pm,"__esModule",{value:!0});var mm;(function(i){function e(t){return typeof t.delete=="number"?t.delete:typeof t.retain=="number"?t.retain:typeof t.retain=="object"&&t.retain!==null?1:typeof t.insert=="string"?t.insert.length:1}i.length=e})(mm||(mm={}));pm.default=mm});var X9=tr(ym=>{"use strict";Object.defineProperty(ym,"__esModule",{value:!0});var Y9=gm(),xm=class{constructor(e){this.ops=e,this.index=0,this.offset=0}hasNext(){return this.peekLength()<1/0}next(e){e||(e=1/0);let t=this.ops[this.index];if(t){let r=this.offset,n=Y9.default.length(t);if(e>=n-r?(e=n-r,this.index+=1,this.offset=0):this.offset+=e,typeof t.delete=="number")return{delete:e};{let s={};return t.attributes&&(s.attributes=t.attributes),typeof t.retain=="number"?s.retain=e:typeof t.retain=="object"&&t.retain!==null?s.retain=t.retain:typeof t.insert=="string"?s.insert=t.insert.substr(r,e):s.insert=t.insert,s}}else return{retain:1/0}}peek(){return this.ops[this.index]}peekLength(){return this.ops[this.index]?Y9.default.length(this.ops[this.index])-this.offset:1/0}peekType(){let e=this.ops[this.index];return e?typeof e.delete=="number"?"delete":typeof e.retain=="number"||typeof e.retain=="object"&&e.retain!==null?"retain":"insert":"retain"}rest(){if(this.hasNext()){if(this.offset===0)return this.ops.slice(this.index);{let e=this.offset,t=this.index,r=this.next(),n=this.ops.slice(this.index);return this.offset=e,this.index=t,[r].concat(n)}}else return[]}};ym.default=xm});var xi=tr((Yr,Zd)=>{"use strict";Object.defineProperty(Yr,"__esModule",{value:!0});Yr.AttributeMap=Yr.OpIterator=Yr.Op=void 0;var Kd=Ex(),gB=Jf(),vm=cm(),As=V9();Yr.AttributeMap=As.default;var Vr=gm();Yr.Op=Vr.default;var zi=X9();Yr.OpIterator=zi.default;var xB="\0",K9=(i,e)=>{if(typeof i!="object"||i===null)throw new Error(`cannot retain a ${typeof i}`);if(typeof e!="object"||e===null)throw new Error(`cannot retain a ${typeof e}`);let t=Object.keys(i)[0];if(!t||t!==Object.keys(e)[0])throw new Error(`embed types not matched: ${t} != ${Object.keys(e)[0]}`);return[t,i[t],e[t]]},Xr=class i{constructor(e){Array.isArray(e)?this.ops=e:e!=null&&Array.isArray(e.ops)?this.ops=e.ops:this.ops=[]}static registerEmbed(e,t){this.handlers[e]=t}static unregisterEmbed(e){delete this.handlers[e]}static getHandler(e){let t=this.handlers[e];if(!t)throw new Error(`no handlers for embed type "${e}"`);return t}insert(e,t){let r={};return typeof e=="string"&&e.length===0?this:(r.insert=e,t!=null&&typeof t=="object"&&Object.keys(t).length>0&&(r.attributes=t),this.push(r))}delete(e){return e<=0?this:this.push({delete:e})}retain(e,t){if(typeof e=="number"&&e<=0)return this;let r={retain:e};return t!=null&&typeof t=="object"&&Object.keys(t).length>0&&(r.attributes=t),this.push(r)}push(e){let t=this.ops.length,r=this.ops[t-1];if(e=gB(e),typeof r=="object"){if(typeof e.delete=="number"&&typeof r.delete=="number")return this.ops[t-1]={delete:r.delete+e.delete},this;if(typeof r.delete=="number"&&e.insert!=null&&(t-=1,r=this.ops[t-1],typeof r!="object"))return this.ops.unshift(e),this;if(vm(e.attributes,r.attributes)){if(typeof e.insert=="string"&&typeof r.insert=="string")return this.ops[t-1]={insert:r.insert+e.insert},typeof e.attributes=="object"&&(this.ops[t-1].attributes=e.attributes),this;if(typeof e.retain=="number"&&typeof r.retain=="number")return this.ops[t-1]={retain:r.retain+e.retain},typeof e.attributes=="object"&&(this.ops[t-1].attributes=e.attributes),this}}return t===this.ops.length?this.ops.push(e):this.ops.splice(t,0,e),this}chop(){let e=this.ops[this.ops.length-1];return e&&typeof e.retain=="number"&&!e.attributes&&this.ops.pop(),this}filter(e){return this.ops.filter(e)}forEach(e){this.ops.forEach(e)}map(e){return this.ops.map(e)}partition(e){let t=[],r=[];return this.forEach(n=>{(e(n)?t:r).push(n)}),[t,r]}reduce(e,t){return this.ops.reduce(e,t)}changeLength(){return this.reduce((e,t)=>t.insert?e+Vr.default.length(t):t.delete?e-t.delete:e,0)}length(){return this.reduce((e,t)=>e+Vr.default.length(t),0)}slice(e=0,t=1/0){let r=[],n=new zi.default(this.ops),s=0;for(;s0&&r.next(s.retain-o)}let a=new i(n);for(;t.hasNext()||r.hasNext();)if(r.peekType()==="insert")a.push(r.next());else if(t.peekType()==="delete")a.push(t.next());else{let o=Math.min(t.peekLength(),r.peekLength()),l=t.next(o),h=r.next(o);if(h.retain){let d={};if(typeof l.retain=="number")d.retain=typeof h.retain=="number"?o:h.retain;else if(typeof h.retain=="number")l.retain==null?d.insert=l.insert:d.retain=l.retain;else{let f=l.retain==null?"insert":"retain",[m,g,x]=K9(l[f],h.retain),y=i.getHandler(m);d[f]={[m]:y.compose(g,x,f==="retain")}}let c=As.default.compose(l.attributes,h.attributes,typeof l.retain=="number");if(c&&(d.attributes=c),a.push(d),!r.hasNext()&&vm(a.ops[a.ops.length-1],d)){let f=new i(t.rest());return a.concat(f).chop()}}else typeof h.delete=="number"&&(typeof l.retain=="number"||typeof l.retain=="object"&&l.retain!==null)&&a.push(h)}return a.chop()}concat(e){let t=new i(this.ops.slice());return e.ops.length>0&&(t.push(e.ops[0]),t.ops=t.ops.concat(e.ops.slice(1))),t}diff(e,t){if(this.ops===e.ops)return new i;let r=[this,e].map(l=>l.map(h=>{if(h.insert!=null)return typeof h.insert=="string"?h.insert:xB;let d=l===e?"on":"with";throw new Error("diff() called "+d+" non-document")}).join("")),n=new i,s=Kd(r[0],r[1],t,!0),a=new zi.default(this.ops),o=new zi.default(e.ops);return s.forEach(l=>{let h=l[1].length;for(;h>0;){let d=0;switch(l[0]){case Kd.INSERT:d=Math.min(o.peekLength(),h),n.push(o.next(d));break;case Kd.DELETE:d=Math.min(h,a.peekLength()),a.next(d),n.delete(d);break;case Kd.EQUAL:d=Math.min(a.peekLength(),o.peekLength(),h);let c=a.next(d),f=o.next(d);vm(c.insert,f.insert)?n.retain(d,As.default.diff(c.attributes,f.attributes)):n.push(f).delete(d);break}h-=d}}),n.chop()}eachLine(e,t=` +`){let r=new zi.default(this.ops),n=new i,s=0;for(;r.hasNext();){if(r.peekType()!=="insert")return;let a=r.peek(),o=Vr.default.length(a)-r.peekLength(),l=typeof a.insert=="string"?a.insert.indexOf(t,o)-o:-1;if(l<0)n.push(r.next());else if(l>0)n.push(r.next(l));else{if(e(n,r.next(1).attributes||{},s)===!1)return;s+=1,n=new i}}n.length()>0&&e(n,{},s)}invert(e){let t=new i;return this.reduce((r,n)=>{if(n.insert)t.delete(Vr.default.length(n));else{if(typeof n.retain=="number"&&n.attributes==null)return t.retain(n.retain),r+n.retain;if(n.delete||typeof n.retain=="number"){let s=n.delete||n.retain;return e.slice(r,r+s).forEach(o=>{n.delete?t.push(o):n.retain&&n.attributes&&t.retain(Vr.default.length(o),As.default.invert(n.attributes,o.attributes))}),r+s}else if(typeof n.retain=="object"&&n.retain!==null){let s=e.slice(r,r+1),a=new zi.default(s.ops).next(),[o,l,h]=K9(n.retain,a.insert),d=i.getHandler(o);return t.retain({[o]:d.invert(l,h)},As.default.invert(n.attributes,a.attributes)),r+1}}return r},0),t.chop()}transform(e,t=!1){if(t=!!t,typeof e=="number")return this.transformPosition(e,t);let r=e,n=new zi.default(this.ops),s=new zi.default(r.ops),a=new i;for(;n.hasNext()||s.hasNext();)if(n.peekType()==="insert"&&(t||s.peekType()!=="insert"))a.retain(Vr.default.length(n.next()));else if(s.peekType()==="insert")a.push(s.next());else{let o=Math.min(n.peekLength(),s.peekLength()),l=n.next(o),h=s.next(o);if(l.delete)continue;if(h.delete)a.push(h);else{let d=l.retain,c=h.retain,f=typeof c=="object"&&c!==null?c:o;if(typeof d=="object"&&d!==null&&typeof c=="object"&&c!==null){let m=Object.keys(d)[0];if(m===Object.keys(c)[0]){let g=i.getHandler(m);g&&(f={[m]:g.transform(d[m],c[m],t)})}}a.retain(f,As.default.transform(l.attributes,h.attributes,t))}}return a.chop()}transformPosition(e,t=!1){t=!!t;let r=new zi.default(this.ops),n=0;for(;r.hasNext()&&n<=e;){let s=r.peekLength(),a=r.peekType();if(r.next(),a==="delete"){e-=Math.min(s,e-n);continue}else a==="insert"&&(n{De();Ol=class extends Ze{static value(){}optimize(){(this.prev||this.next)&&this.remove()}length(){return 0}value(){return""}};Ol.blotName="break";Ol.tagName="BR";At=Ol});function ks(i){return i.replace(/[&<>"']/g,e=>yB[e])}var nt,yB,Kr=T(()=>{De();nt=class extends Ua{},yB={"&":"&","<":"<",">":">",'"':""","'":"'"}});var yr,bm,kt,Zr=T(()=>{De();Rn();Kr();yr=class yr extends kd{static compare(e,t){let r=yr.order.indexOf(e),n=yr.order.indexOf(t);return r>=0||n>=0?r-n:e===t?0:e0){let t=this.parent.isolate(this.offset(),this.length());this.moveChildren(t),t.wrap(this)}}};U(yr,"allowedChildren",[yr,At,Ze,nt]),U(yr,"order",["cursor","inline","link","underline","strike","italic","bold","script","code"]);bm=yr,kt=bm});function Mm(i){let e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0;return i.descendants(ct).reduce((t,r)=>r.length()===0?t:t.insert(r.value(),li(r,{},e)),new wm.default).insert(` +`,li(i))}function li(i){let e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},t=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!0;return i==null||("formats"in i&&typeof i.formats=="function"&&(e={...e,...i.formats()},t&&delete e["code-token"]),i.parent==null||i.parent.statics.blotName==="scroll"||i.parent.statics.scope!==i.statics.scope)?e:li(i.parent,e,t)}var wm,Z9,Oe,gt,Ri=T(()=>{De();wm=yt(xi(),1);Rn();Zr();Kr();Z9=1,Oe=class extends vs{constructor(){super(...arguments);U(this,"cache",{})}delta(){return this.cache.delta==null&&(this.cache.delta=Mm(this)),this.cache.delta}deleteAt(t,r){super.deleteAt(t,r),this.cache={}}formatAt(t,r,n,s){r<=0||(this.scroll.query(n,X.BLOCK)?t+r===this.length()&&this.format(n,s):super.formatAt(t,Math.min(r,this.length()-t-1),n,s),this.cache={})}insertAt(t,r,n){if(n!=null){super.insertAt(t,r,n),this.cache={};return}if(r.length===0)return;let s=r.split(` +`),a=s.shift();a.length>0&&(t(o=o.split(l,!0),o.insertAt(0,h),h.length),t+a.length)}insertBefore(t,r){let{head:n}=this.children;super.insertBefore(t,r),n instanceof At&&n.remove(),this.cache={}}length(){return this.cache.length==null&&(this.cache.length=super.length()+Z9),this.cache.length}moveChildren(t,r){super.moveChildren(t,r),this.cache={}}optimize(t){super.optimize(t),this.cache={}}path(t){return super.path(t,!0)}removeChild(t){super.removeChild(t),this.cache={}}split(t){let r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;if(r&&(t===0||t>=this.length()-Z9)){let s=this.clone();return t===0?(this.parent.insertBefore(s,this),this):(this.parent.insertBefore(s,this.next),s)}let n=super.split(t,r);return this.cache={},n}};Oe.blotName="block";Oe.tagName="P";Oe.defaultChild=At;Oe.allowedChildren=[At,kt,Ze,nt];gt=class extends Ze{attach(){super.attach(),this.attributes=new Nl(this.domNode)}delta(){return new wm.default().insert(this.value(),{...this.formats(),...this.attributes.values()})}format(e,t){let r=this.scroll.query(e,X.BLOCK_ATTRIBUTE);r!=null&&this.attributes.attribute(r,t)}formatAt(e,t,r,n){this.format(r,n)}insertAt(e,t,r){if(r!=null){super.insertAt(e,t,r);return}let n=t.split(` +`),s=n.pop(),a=n.map(l=>{let h=this.scroll.create(Oe.blotName);return h.insertAt(0,l),h}),o=this.split(e);a.forEach(l=>{this.parent.insertBefore(l,o)}),s&&this.parent.insertBefore(this.scroll.create("text",s),o)}};gt.scope=X.BLOCK_BLOT});var Di,Tm,Dn,Bl=T(()=>{De();Kr();Di=class Di extends Ze{static value(){}constructor(e,t,r){super(e,t),this.selection=r,this.textNode=document.createTextNode(Di.CONTENTS),this.domNode.appendChild(this.textNode),this.savedLength=0}detach(){this.parent!=null&&this.parent.removeChild(this)}format(e,t){if(this.savedLength!==0){super.format(e,t);return}let r=this,n=0;for(;r!=null&&r.statics.scope!==X.BLOCK_BLOT;)n+=r.offset(r.parent),r=r.parent;r!=null&&(this.savedLength=Di.CONTENTS.length,r.optimize(),r.formatAt(n,Di.CONTENTS.length,e,t),this.savedLength=0)}index(e,t){return e===this.textNode?0:super.index(e,t)}length(){return this.savedLength}position(){return[this.textNode,this.textNode.data.length]}remove(){super.remove(),this.parent=null}restore(){if(this.selection.composing||this.parent==null)return null;let e=this.selection.getNativeRange();for(;this.domNode.lastChild!=null&&this.domNode.lastChild!==this.textNode;)this.domNode.parentNode.insertBefore(this.domNode.lastChild,this.domNode);let t=this.prev instanceof nt?this.prev:null,r=t?t.length():0,n=this.next instanceof nt?this.next:null,s=n?n.text:"",{textNode:a}=this,o=a.data.split(Di.CONTENTS).join("");a.data=Di.CONTENTS;let l;if(t)l=t,(o||n)&&(t.insertAt(t.length(),o+s),n&&n.remove());else if(n)l=n,n.insertAt(0,o);else{let h=document.createTextNode(o);l=this.scroll.create(h),this.parent.insertBefore(l,this)}if(this.remove(),e){let h=(f,m)=>t&&f===t.domNode?m:f===a?r+m-1:n&&f===n.domNode?r+o.length+m:null,d=h(e.start.node,e.start.offset),c=h(e.end.node,e.end.offset);if(d!==null&&c!==null)return{startNode:l.domNode,startOffset:d,endNode:l.domNode,endOffset:c}}return null}update(e,t){if(e.some(r=>r.type==="characterData"&&r.target===this.textNode)){let r=this.restore();r&&(t.range=r)}}optimize(e){super.optimize(e);let{parent:t}=this;for(;t;){if(t.domNode.tagName==="A"){this.savedLength=Di.CONTENTS.length,t.isolate(this.offset(t),this.length()).unwrap(),this.savedLength=0;break}t=t.parent}}value(){return""}};U(Di,"blotName","cursor"),U(Di,"className","ql-cursor"),U(Di,"tagName","span"),U(Di,"CONTENTS","\uFEFF");Tm=Di,Dn=Tm});var J9=tr((RQ,Nm)=>{"use strict";var vB=Object.prototype.hasOwnProperty,Ut="~";function Pl(){}Object.create&&(Pl.prototype=Object.create(null),new Pl().__proto__||(Ut=!1));function bB(i,e,t){this.fn=i,this.context=e,this.once=t||!1}function Q9(i,e,t,r,n){if(typeof t!="function")throw new TypeError("The listener must be a function");var s=new bB(t,r||i,n),a=Ut?Ut+e:e;return i._events[a]?i._events[a].fn?i._events[a]=[i._events[a],s]:i._events[a].push(s):(i._events[a]=s,i._eventsCount++),i}function Qd(i,e){--i._eventsCount===0?i._events=new Pl:delete i._events[e]}function Ct(){this._events=new Pl,this._eventsCount=0}Ct.prototype.eventNames=function(){var e=[],t,r;if(this._eventsCount===0)return e;for(r in t=this._events)vB.call(t,r)&&e.push(Ut?r.slice(1):r);return Object.getOwnPropertySymbols?e.concat(Object.getOwnPropertySymbols(t)):e};Ct.prototype.listeners=function(e){var t=Ut?Ut+e:e,r=this._events[t];if(!r)return[];if(r.fn)return[r.fn];for(var n=0,s=r.length,a=new Array(s);n{Em=yt(J9(),1)});var Fl,Sm=T(()=>{Fl=new WeakMap});function ty(i){if(km&&Am.indexOf(i)<=Am.indexOf(km)){for(var e=arguments.length,t=new Array(e>1?e-1:0),r=1;r(e[t]=ty.bind(console,t,i),e),{})}var Am,km,yi,Cs=T(()=>{Am=["error","warn","log","info"],km="warn";Cm.level=i=>{km=i};ty.level=Cm.level;yi=Cm});var _m,wB,ql,ee,Qr=T(()=>{ey();Sm();Cs();_m=yi("quill:events"),wB=["selectionchange","mousedown","mouseup","click"];wB.forEach(i=>{document.addEventListener(i,function(){for(var e=arguments.length,t=new Array(e),r=0;r{let s=Fl.get(n);s&&s.emitter&&s.emitter.handleDOM(...t)})})});ql=class extends Em.default{constructor(){super(),this.domListeners={},this.on("error",_m.error)}emit(){for(var e=arguments.length,t=new Array(e),r=0;r1?t-1:0),n=1;n{let{node:a,handler:o}=s;(e.target===a||a.contains(e.target))&&o(e,...r)})}listenDOM(e,t,r){this.domListeners[e]||(this.domListeners[e]=[]),this.domListeners[e].push({node:t,handler:r})}};U(ql,"events",{EDITOR_CHANGE:"editor-change",SCROLL_BEFORE_UPDATE:"scroll-before-update",SCROLL_BLOT_MOUNT:"scroll-blot-mount",SCROLL_BLOT_UNMOUNT:"scroll-blot-unmount",SCROLL_OPTIMIZE:"scroll-optimize",SCROLL_UPDATE:"scroll-update",SCROLL_EMBED_UPDATE:"scroll-embed-update",SELECTION_CHANGE:"selection-change",TEXT_CHANGE:"text-change",COMPOSITION_BEFORE_START:"composition-before-start",COMPOSITION_START:"composition-start",COMPOSITION_BEFORE_END:"composition-before-end",COMPOSITION_END:"composition-end"}),U(ql,"sources",{API:"api",SILENT:"silent",USER:"user"});ee=ql});function Im(i,e){try{e.parentNode}catch{return!1}return i.contains(e)}var Lm,$t,zm,iy,Hl=T(()=>{De();An();Qr();Cs();Lm=yi("quill:selection"),$t=class{constructor(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0;this.index=e,this.length=t}},zm=class{constructor(e,t){this.emitter=t,this.scroll=e,this.composing=!1,this.mouseDown=!1,this.root=this.scroll.domNode,this.cursor=this.scroll.create("cursor",this),this.savedRange=new $t(0,0),this.lastRange=this.savedRange,this.lastNative=null,this.handleComposition(),this.handleDragging(),this.emitter.listenDOM("selectionchange",document,()=>{!this.mouseDown&&!this.composing&&setTimeout(this.update.bind(this,ee.sources.USER),1)}),this.emitter.on(ee.events.SCROLL_BEFORE_UPDATE,()=>{if(!this.hasFocus())return;let r=this.getNativeRange();r!=null&&r.start.node!==this.cursor.textNode&&this.emitter.once(ee.events.SCROLL_UPDATE,(n,s)=>{try{this.root.contains(r.start.node)&&this.root.contains(r.end.node)&&this.setNativeRange(r.start.node,r.start.offset,r.end.node,r.end.offset);let a=s.some(o=>o.type==="characterData"||o.type==="childList"||o.type==="attributes"&&o.target===this.root);this.update(a?ee.sources.SILENT:n)}catch{}})}),this.emitter.on(ee.events.SCROLL_OPTIMIZE,(r,n)=>{if(n.range){let{startNode:s,startOffset:a,endNode:o,endOffset:l}=n.range;this.setNativeRange(s,a,o,l),this.update(ee.sources.SILENT)}}),this.update(ee.sources.SILENT)}handleComposition(){this.emitter.on(ee.events.COMPOSITION_BEFORE_START,()=>{this.composing=!0}),this.emitter.on(ee.events.COMPOSITION_END,()=>{if(this.composing=!1,this.cursor.parent){let e=this.cursor.restore();if(!e)return;setTimeout(()=>{this.setNativeRange(e.startNode,e.startOffset,e.endNode,e.endOffset)},1)}})}handleDragging(){this.emitter.listenDOM("mousedown",document.body,()=>{this.mouseDown=!0}),this.emitter.listenDOM("mouseup",document.body,()=>{this.mouseDown=!1,this.update(ee.sources.USER)})}focus(){this.hasFocus()||(this.root.focus({preventScroll:!0}),this.setRange(this.savedRange))}format(e,t){this.scroll.update();let r=this.getNativeRange();if(!(r==null||!r.native.collapsed||this.scroll.query(e,X.BLOCK))){if(r.start.node!==this.cursor.textNode){let n=this.scroll.find(r.start.node,!1);if(n==null)return;if(n instanceof ct){let s=n.split(r.start.offset);n.parent.insertBefore(this.cursor,s)}else n.insertBefore(this.cursor,r.start.node);this.cursor.attach()}this.cursor.format(e,t),this.scroll.optimize(),this.setNativeRange(this.cursor.textNode,this.cursor.textNode.data.length),this.update()}}getBounds(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,r=this.scroll.length();e=Math.min(e,r-1),t=Math.min(e+t,r-1)-e;let n,[s,a]=this.scroll.leaf(e);if(s==null)return null;if(t>0&&a===s.length()){let[d]=this.scroll.leaf(e+1);if(d){let[c]=this.scroll.line(e),[f]=this.scroll.line(e+1);c===f&&(s=d,a=0)}}[n,a]=s.position(a,!0);let o=document.createRange();if(t>0)return o.setStart(n,a),[s,a]=this.scroll.leaf(e+t),s==null?null:([n,a]=s.position(a,!0),o.setEnd(n,a),o.getBoundingClientRect());let l="left",h;if(n instanceof Text){if(!n.data.length)return null;a0&&(l="right")}return{bottom:h.top+h.height,height:h.height,left:h[l],right:h[l],top:h.top,width:0}}getNativeRange(){let e=document.getSelection();if(e==null||e.rangeCount<=0)return null;let t=e.getRangeAt(0);if(t==null)return null;let r=this.normalizeNative(t);return Lm.info("getNativeRange",r),r}getRange(){let e=this.scroll.domNode;if("isConnected"in e&&!e.isConnected)return[null,null];let t=this.getNativeRange();return t==null?[null,null]:[this.normalizedToRange(t),t]}hasFocus(){return document.activeElement===this.root||document.activeElement!=null&&Im(this.root,document.activeElement)}normalizedToRange(e){let t=[[e.start.node,e.start.offset]];e.native.collapsed||t.push([e.end.node,e.end.offset]);let r=t.map(a=>{let[o,l]=a,h=this.scroll.find(o,!0),d=h.offset(this.scroll);return l===0?d:h instanceof ct?d+h.index(o,l):d+h.length()}),n=Math.min(Math.max(...r),this.scroll.length()-1),s=Math.min(n,...r);return new $t(s,n-s)}normalizeNative(e){if(!Im(this.root,e.startContainer)||!e.collapsed&&!Im(this.root,e.endContainer))return null;let t={start:{node:e.startContainer,offset:e.startOffset},end:{node:e.endContainer,offset:e.endOffset},native:e};return[t.start,t.end].forEach(r=>{let{node:n,offset:s}=r;for(;!(n instanceof Text)&&n.childNodes.length>0;)if(n.childNodes.length>s)n=n.childNodes[s],s=0;else if(n.childNodes.length===s)n=n.lastChild,n instanceof Text?s=n.data.length:n.childNodes.length>0?s=n.childNodes.length:s=n.childNodes.length+1;else break;r.node=n,r.offset=s}),t}rangeToNative(e){let t=this.scroll.length(),r=(n,s)=>{n=Math.min(t-1,n);let[a,o]=this.scroll.leaf(n);return a?a.position(o,s):[null,-1]};return[...r(e.index,!1),...r(e.index+e.length,!0)]}setNativeRange(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e,n=arguments.length>3&&arguments[3]!==void 0?arguments[3]:t,s=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!1;if(Lm.info("setNativeRange",e,t,r,n),e!=null&&(this.root.parentNode==null||e.parentNode==null||r.parentNode==null))return;let a=document.getSelection();if(a!=null)if(e!=null){this.hasFocus()||this.root.focus({preventScroll:!0});let{native:o}=this.getNativeRange()||{};if(o==null||s||e!==o.startContainer||t!==o.startOffset||r!==o.endContainer||n!==o.endOffset){e instanceof Element&&e.tagName==="BR"&&(t=Array.from(e.parentNode.childNodes).indexOf(e),e=e.parentNode),r instanceof Element&&r.tagName==="BR"&&(n=Array.from(r.parentNode.childNodes).indexOf(r),r=r.parentNode);let l=document.createRange();l.setStart(e,t),l.setEnd(r,n),a.removeAllRanges(),a.addRange(l)}}else a.removeAllRanges(),this.root.blur()}setRange(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1,r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:ee.sources.API;if(typeof t=="string"&&(r=t,t=!1),Lm.info("setRange",e),e!=null){let n=this.rangeToNative(e);this.setNativeRange(...n,t)}else this.setNativeRange(null);this.update(r)}update(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:ee.sources.USER,t=this.lastRange,[r,n]=this.getRange();if(this.lastRange=r,this.lastNative=n,this.lastRange!=null&&(this.savedRange=this.lastRange),!ys(t,this.lastRange)){if(!this.composing&&n!=null&&n.native.collapsed&&n.start.node!==this.cursor.textNode){let a=this.cursor.restore();a&&this.setNativeRange(a.startNode,a.startOffset,a.endNode,a.endOffset)}let s=[ee.events.SELECTION_CHANGE,gr(this.lastRange),gr(t),e];this.emitter.emit(ee.events.EDITOR_CHANGE,...s),e!==ee.sources.SILENT&&this.emitter.emit(...s)}}};iy=zm});function Ka(i,e,t){if(i.length===0){let[m]=Rm(t.pop());return e<=0?``:`${Ka([],e-1,t)}`}let[{child:r,offset:n,length:s,indent:a,type:o},...l]=i,[h,d]=Rm(o);if(a>e)return t.push(o),a===e+1?`<${h}>${Ul(r,n,s)}${Ka(l,a,t)}`:`<${h}>
  • ${Ka(i,e+1,t)}`;let c=t[t.length-1];if(a===e&&o===c)return`
  • ${Ul(r,n,s)}${Ka(l,a,t)}`;let[f]=Rm(t.pop());return`${Ka(i,e-1,t)}`}function Ul(i,e,t){let r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1;if("html"in i&&typeof i.html=="function")return i.html(e,t);if(i instanceof nt)return ks(i.value().slice(e,e+t)).replaceAll(" "," ");if(i instanceof ai){if(i.statics.blotName==="list-container"){let h=[];return i.children.forEachAt(e,t,(d,c,f)=>{let m="formats"in d&&typeof d.formats=="function"?d.formats():{};h.push({child:d,offset:c,length:f,indent:m.indent||0,type:m.list})}),Ka(h,-1,[])}let n=[];if(i.children.forEachAt(e,t,(h,d,c)=>{n.push(Ul(h,d,c))}),r||i.statics.blotName==="list")return n.join("");let{outerHTML:s,innerHTML:a}=i.domNode,[o,l]=s.split(`>${a}<`);return o==="${n.join("")}<${l}`:`${o}>${n.join("")}<${l}`}return i.domNode instanceof Element?i.domNode.outerHTML:""}function TB(i,e){return Object.keys(e).reduce((t,r)=>{if(i[r]==null)return t;let n=e[r];return n===i[r]?t[r]=n:Array.isArray(n)?n.indexOf(i[r])<0?t[r]=n.concat([i[r]]):t[r]=n:t[r]=[n,i[r]],t},{})}function Rm(i){let e=i==="ordered"?"ol":"ul";switch(i){case"checked":return[e,' data-list="checked"'];case"unchecked":return[e,' data-list="unchecked"'];default:return[e,""]}}function ry(i){return i.reduce((e,t)=>{if(typeof t.insert=="string"){let r=t.insert.replace(/\r\n/g,` `).replace(/\r/g,` -`);return e.insert(r,t.attributes)}return e.push(t)},new Ze.default)}function j9(i,e){let{index:t,length:r}=i;return new $t(t+e,r)}function eB(i){let e=[];return i.forEach(t=>{typeof t.insert=="string"?t.insert.split(` +`);return e.insert(r,t.attributes)}return e.push(t)},new Qe.default)}function ny(i,e){let{index:t,length:r}=i;return new $t(t+e,r)}function NB(i){let e=[];return i.forEach(t=>{typeof t.insert=="string"?t.insert.split(` `).forEach((n,s)=>{s&&e.push({insert:` -`,attributes:t.attributes}),n&&e.push({insert:n,attributes:t.attributes})}):e.push(t)}),e}var Ze,QO,Nm,G9,V9=T(()=>{kn();Re();Ze=pt(pi(),1);zi();Dn();Ll();Kr();Dl();QO=/^[ -~]*$/,Nm=class{constructor(e){this.scroll=e,this.delta=this.getDelta()}applyDelta(e){this.scroll.update();let t=this.scroll.length();this.scroll.batchStart();let r=$9(e),n=new Ze.default;return eB(r.ops.slice()).reduce((a,o)=>{let l=Ze.Op.length(o),h=o.attributes||{},d=!1,c=!1;if(o.insert!=null){if(n.retain(l),typeof o.insert=="string"){let g=o.insert;c=!g.endsWith(` -`)&&(t<=a||!!this.scroll.descendant(ft,a)[0]),this.scroll.insertAt(a,g);let[x,y]=this.scroll.line(a),b=ri({},ai(x));if(x instanceof De){let[S]=x.descendant(dt,y);S&&(b=ri(b,ai(S)))}h=Ze.AttributeMap.diff(b,h)||{}}else if(typeof o.insert=="object"){let g=Object.keys(o.insert)[0];if(g==null)return a;let x=this.scroll.query(g,X.INLINE)!=null;if(x)(t<=a||this.scroll.descendant(ft,a)[0])&&(c=!0);else if(a>0){let[y,b]=this.scroll.descendant(dt,a-1);y instanceof nt?y.value()[b]!==` -`&&(d=!0):y instanceof Ke&&y.statics.scope===X.INLINE_BLOT&&(d=!0)}if(this.scroll.insertAt(a,g,o.insert[g]),x){let[y]=this.scroll.descendant(dt,a);if(y){let b=ri({},ai(y));h=Ze.AttributeMap.diff(b,h)||{}}}}t+=l}else if(n.push(o),o.retain!==null&&typeof o.retain=="object"){let g=Object.keys(o.retain)[0];if(g==null)return a;this.scroll.updateEmbedAt(a,g,o.retain[g])}Object.keys(h).forEach(g=>{this.scroll.formatAt(a,l,g,h[g])});let f=d?1:0,m=c?1:0;return t+=f+m,n.retain(f),n.delete(m),a+l+f+m},0),n.reduce((a,o)=>typeof o.delete=="number"?(this.scroll.deleteAt(a,o.delete),a):a+Ze.Op.length(o),0),this.scroll.batchEnd(),this.scroll.optimize(),this.update(r)}deleteText(e,t){return this.scroll.deleteAt(e,t),this.update(new Ze.default().retain(e).delete(t))}formatLine(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};this.scroll.update(),Object.keys(r).forEach(s=>{this.scroll.lines(e,Math.max(t,1)).forEach(a=>{a.format(s,r[s])})}),this.scroll.optimize();let n=new Ze.default().retain(e).retain(t,yr(r));return this.update(n)}formatText(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};Object.keys(r).forEach(s=>{this.scroll.formatAt(e,t,s,r[s])});let n=new Ze.default().retain(e).retain(t,yr(r));return this.update(n)}getContents(e,t){return this.delta.slice(e,e+t)}getDelta(){return this.scroll.lines().reduce((e,t)=>e.concat(t.delta()),new Ze.default)}getFormat(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,r=[],n=[];t===0?this.scroll.path(e).forEach(o=>{let[l]=o;l instanceof De?r.push(l):l instanceof dt&&n.push(l)}):(r=this.scroll.lines(e,t),n=this.scroll.descendants(dt,e,t));let[s,a]=[r,n].map(o=>{let l=o.shift();if(l==null)return{};let h=ai(l);for(;Object.keys(h).length>0;){let d=o.shift();if(d==null)return h;h=JO(ai(d),h)}return h});return{...s,...a}}getHTML(e,t){let[r,n]=this.scroll.line(e);if(r){let s=r.length();return r.length()>=n+t&&!(n===0&&t===s)?Ol(r,n,t,!0):Ol(this.scroll,e,t,!0)}return""}getText(e,t){return this.getContents(e,t).filter(r=>typeof r.insert=="string").map(r=>r.insert).join("")}insertContents(e,t){let r=$9(t),n=new Ze.default().retain(e).concat(r);return this.scroll.insertContents(e,r),this.update(n)}insertEmbed(e,t,r){return this.scroll.insertAt(e,t,r),this.update(new Ze.default().retain(e).insert({[t]:r}))}insertText(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return t=t.replace(/\r\n/g,` +`,attributes:t.attributes}),n&&e.push({insert:n,attributes:t.attributes})}):e.push(t)}),e}var Qe,MB,Dm,sy,ay=T(()=>{An();De();Qe=yt(xi(),1);Ri();Rn();Bl();Kr();Hl();MB=/^[ -~]*$/,Dm=class{constructor(e){this.scroll=e,this.delta=this.getDelta()}applyDelta(e){this.scroll.update();let t=this.scroll.length();this.scroll.batchStart();let r=ry(e),n=new Qe.default;return NB(r.ops.slice()).reduce((a,o)=>{let l=Qe.Op.length(o),h=o.attributes||{},d=!1,c=!1;if(o.insert!=null){if(n.retain(l),typeof o.insert=="string"){let g=o.insert;c=!g.endsWith(` +`)&&(t<=a||!!this.scroll.descendant(gt,a)[0]),this.scroll.insertAt(a,g);let[x,y]=this.scroll.line(a),b=si({},li(x));if(x instanceof Oe){let[E]=x.descendant(ct,y);E&&(b=si(b,li(E)))}h=Qe.AttributeMap.diff(b,h)||{}}else if(typeof o.insert=="object"){let g=Object.keys(o.insert)[0];if(g==null)return a;let x=this.scroll.query(g,X.INLINE)!=null;if(x)(t<=a||this.scroll.descendant(gt,a)[0])&&(c=!0);else if(a>0){let[y,b]=this.scroll.descendant(ct,a-1);y instanceof nt?y.value()[b]!==` +`&&(d=!0):y instanceof Ze&&y.statics.scope===X.INLINE_BLOT&&(d=!0)}if(this.scroll.insertAt(a,g,o.insert[g]),x){let[y]=this.scroll.descendant(ct,a);if(y){let b=si({},li(y));h=Qe.AttributeMap.diff(b,h)||{}}}}t+=l}else if(n.push(o),o.retain!==null&&typeof o.retain=="object"){let g=Object.keys(o.retain)[0];if(g==null)return a;this.scroll.updateEmbedAt(a,g,o.retain[g])}Object.keys(h).forEach(g=>{this.scroll.formatAt(a,l,g,h[g])});let f=d?1:0,m=c?1:0;return t+=f+m,n.retain(f),n.delete(m),a+l+f+m},0),n.reduce((a,o)=>typeof o.delete=="number"?(this.scroll.deleteAt(a,o.delete),a):a+Qe.Op.length(o),0),this.scroll.batchEnd(),this.scroll.optimize(),this.update(r)}deleteText(e,t){return this.scroll.deleteAt(e,t),this.update(new Qe.default().retain(e).delete(t))}formatLine(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};this.scroll.update(),Object.keys(r).forEach(s=>{this.scroll.lines(e,Math.max(t,1)).forEach(a=>{a.format(s,r[s])})}),this.scroll.optimize();let n=new Qe.default().retain(e).retain(t,gr(r));return this.update(n)}formatText(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};Object.keys(r).forEach(s=>{this.scroll.formatAt(e,t,s,r[s])});let n=new Qe.default().retain(e).retain(t,gr(r));return this.update(n)}getContents(e,t){return this.delta.slice(e,e+t)}getDelta(){return this.scroll.lines().reduce((e,t)=>e.concat(t.delta()),new Qe.default)}getFormat(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,r=[],n=[];t===0?this.scroll.path(e).forEach(o=>{let[l]=o;l instanceof Oe?r.push(l):l instanceof ct&&n.push(l)}):(r=this.scroll.lines(e,t),n=this.scroll.descendants(ct,e,t));let[s,a]=[r,n].map(o=>{let l=o.shift();if(l==null)return{};let h=li(l);for(;Object.keys(h).length>0;){let d=o.shift();if(d==null)return h;h=TB(li(d),h)}return h});return{...s,...a}}getHTML(e,t){let[r,n]=this.scroll.line(e);if(r){let s=r.length();return r.length()>=n+t&&!(n===0&&t===s)?Ul(r,n,t,!0):Ul(this.scroll,e,t,!0)}return""}getText(e,t){return this.getContents(e,t).filter(r=>typeof r.insert=="string").map(r=>r.insert).join("")}insertContents(e,t){let r=ry(t),n=new Qe.default().retain(e).concat(r);return this.scroll.insertContents(e,r),this.update(n)}insertEmbed(e,t,r){return this.scroll.insertAt(e,t,r),this.update(new Qe.default().retain(e).insert({[t]:r}))}insertText(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return t=t.replace(/\r\n/g,` `).replace(/\r/g,` -`),this.scroll.insertAt(e,t),Object.keys(r).forEach(n=>{this.scroll.formatAt(e,t.length,n,r[n])}),this.update(new Ze.default().retain(e).insert(t,yr(r)))}isBlank(){if(this.scroll.children.length===0)return!0;if(this.scroll.children.length>1)return!1;let e=this.scroll.children.head;if(e?.statics.blotName!==De.blotName)return!1;let t=e;return t.children.length>1?!1:t.children.head instanceof Et}removeFormat(e,t){let r=this.getText(e,t),[n,s]=this.scroll.line(e+t),a=0,o=new Ze.default;n!=null&&(a=n.length()-s,o=n.delta().slice(s,s+a-1).insert(` -`));let h=this.getContents(e,t+a).diff(new Ze.default().insert(r).concat(o)),d=new Ze.default().retain(e).concat(h);return this.applyDelta(d)}update(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[],r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:void 0,n=this.delta;if(t.length===1&&t[0].type==="characterData"&&t[0].target.data.match(QO)&&this.scroll.find(t[0].target)){let s=this.scroll.find(t[0].target),a=ai(s),o=s.offset(this.scroll),l=t[0].oldValue.replace(On.CONTENTS,""),h=new Ze.default().insert(l),d=new Ze.default().insert(s.value()),c=r&&{oldRange:j9(r.oldRange,-o),newRange:j9(r.newRange,-o)};e=new Ze.default().retain(o).concat(h.diff(d,c)).reduce((m,g)=>g.insert?m.insert(g.insert,a):m.push(g),new Ze.default),this.delta=n.compose(e)}else this.delta=this.getDelta(),(!e||!ys(n.compose(e),this.delta))&&(e=n.diff(this.delta,r));return e}};G9=Nm});var Gd,Qe,Di=T(()=>{Gd=class{constructor(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};this.quill=e,this.options=t}};U(Gd,"DEFAULTS",{});Qe=Gd});var Vd,Em,Ka,Wd=T(()=>{Re();Kr();Vd="\uFEFF",Em=class extends Ke{constructor(e,t){super(e,t),this.contentNode=document.createElement("span"),this.contentNode.setAttribute("contenteditable","false"),Array.from(this.domNode.childNodes).forEach(r=>{this.contentNode.appendChild(r)}),this.leftGuard=document.createTextNode(Vd),this.rightGuard=document.createTextNode(Vd),this.domNode.appendChild(this.leftGuard),this.domNode.appendChild(this.contentNode),this.domNode.appendChild(this.rightGuard)}index(e,t){return e===this.leftGuard?0:e===this.rightGuard?1:super.index(e,t)}restore(e){let t=null,r,n=e.data.split(Vd).join("");if(e===this.leftGuard)if(this.prev instanceof nt){let s=this.prev.length();this.prev.insertAt(s,n),t={startNode:this.prev.domNode,startOffset:s+n.length}}else r=document.createTextNode(n),this.parent.insertBefore(this.scroll.create(r),this),t={startNode:r,startOffset:n.length};else e===this.rightGuard&&(this.next instanceof nt?(this.next.insertAt(0,n),t={startNode:this.next.domNode,startOffset:n.length}):(r=document.createTextNode(n),this.parent.insertBefore(this.scroll.create(r),this.next),t={startNode:r,startOffset:n.length}));return e.data=Vd,t}update(e,t){e.forEach(r=>{if(r.type==="characterData"&&(r.target===this.leftGuard||r.target===this.rightGuard)){let n=this.restore(r.target);n&&(t.range=n)}})}},Ka=Em});var Sm,W9,Y9=T(()=>{Wd();Qr();Sm=class{constructor(e,t){U(this,"isComposing",!1);this.scroll=e,this.emitter=t,this.setupListeners()}setupListeners(){this.scroll.domNode.addEventListener("compositionstart",e=>{this.isComposing||this.handleCompositionStart(e)}),this.scroll.domNode.addEventListener("compositionend",e=>{this.isComposing&&queueMicrotask(()=>{this.handleCompositionEnd(e)})})}handleCompositionStart(e){let t=e.target instanceof Node?this.scroll.find(e.target,!0):null;t&&!(t instanceof Ka)&&(this.emitter.emit(J.events.COMPOSITION_BEFORE_START,e),this.scroll.batchStart(),this.emitter.emit(J.events.COMPOSITION_START,e),this.isComposing=!0)}handleCompositionEnd(e){this.emitter.emit(J.events.COMPOSITION_BEFORE_END,e),this.scroll.batchEnd(),this.emitter.emit(J.events.COMPOSITION_END,e),this.isComposing=!1}},W9=Sm});var Bl,Am,Za,km=T(()=>{Bl=class Bl{constructor(e,t){U(this,"modules",{});this.quill=e,this.options=t}init(){Object.keys(this.options.modules).forEach(e=>{this.modules[e]==null&&this.addModule(e)})}addModule(e){let t=this.quill.constructor.import(`modules/${e}`);return this.modules[e]=new t(this.quill,this.options.modules[e]||{}),this.modules[e]}};U(Bl,"DEFAULTS",{modules:{}}),U(Bl,"themes",{default:Bl});Am=Bl,Za=Am});var tB,iB,Yd,X9,rB,K9,Z9=T(()=>{tB=i=>i.parentElement||i.getRootNode().host||null,iB=i=>{let e=i.getBoundingClientRect(),t="offsetWidth"in i&&Math.abs(e.width)/i.offsetWidth||1,r="offsetHeight"in i&&Math.abs(e.height)/i.offsetHeight||1;return{top:e.top,right:e.left+i.clientWidth*t,bottom:e.top+i.clientHeight*r,left:e.left}},Yd=i=>{let e=parseInt(i,10);return Number.isNaN(e)?0:e},X9=(i,e,t,r,n,s)=>ir?0:ir?e-i>r-t?i+n-t:e-r+s:0,rB=(i,e)=>{let t=i.ownerDocument,r=e,n=i;for(;n;){let s=n===t.body,a=s?{top:0,right:window.visualViewport?.width??t.documentElement.clientWidth,bottom:window.visualViewport?.height??t.documentElement.clientHeight,left:0}:iB(n),o=getComputedStyle(n),l=X9(r.left,r.right,a.left,a.right,Yd(o.scrollPaddingLeft),Yd(o.scrollPaddingRight)),h=X9(r.top,r.bottom,a.top,a.bottom,Yd(o.scrollPaddingTop),Yd(o.scrollPaddingBottom));if(l||h)if(s)t.defaultView?.scrollBy(l,h);else{let{scrollLeft:d,scrollTop:c}=n;h&&(n.scrollTop+=h),l&&(n.scrollLeft+=l);let f=n.scrollLeft-d,m=n.scrollTop-c;r={left:r.left-f,top:r.top-m,right:r.right-f,bottom:r.bottom-m}}n=s||o.position==="fixed"?null:tB(n)}},K9=rB});var nB,sB,aB,Q9,J9=T(()=>{Re();nB=100,sB=["block","break","cursor","inline","scroll","text"],aB=(i,e,t)=>{let r=new Ln;return sB.forEach(n=>{let s=e.query(n);s&&r.register(s)}),i.forEach(n=>{let s=e.query(n);s||t.error(`Cannot register "${n}" specified in "formats" config. Are you sure it was registered?`);let a=0;for(;s;)if(r.register(s),s="blotName"in s?s.requiredContainer??null:null,a+=1,a>nB){t.error(`Cycle detected in registering blot requiredContainer: "${n}"`);break}}),r},Q9=aB});function ey(i){return typeof i=="string"?document.querySelector(i):i}function Cm(i){return Object.entries(i??{}).reduce((e,t)=>{let[r,n]=t;return{...e,[r]:n===!0?{}:n}},{})}function ty(i){return Object.fromEntries(Object.entries(i).filter(e=>e[1]!==void 0))}function oB(i,e){let t=ey(i);if(!t)throw new Error("Invalid Quill container");let n=!e.theme||e.theme===B.DEFAULTS.theme?Za:B.import(`themes/${e.theme}`);if(!n)throw new Error(`Invalid theme ${e.theme}. Did you register it?`);let{modules:s,...a}=B.DEFAULTS,{modules:o,...l}=n.DEFAULTS,h=Cm(e.modules);h!=null&&h.toolbar&&h.toolbar.constructor!==Object&&(h={...h,toolbar:{container:h.toolbar}});let d=ri({},Cm(s),Cm(o),h),c={...a,...ty(l),...ty(e)},f=e.registry;return f?e.formats&&Qa.warn('Ignoring "formats" option because "registry" is specified'):f=e.formats?Q9(e.formats,c.registry,Qa):c.registry,{...c,registry:f,container:t,theme:n,modules:Object.entries(d).reduce((m,g)=>{let[x,y]=g;if(!y)return m;let b=B.import(`modules/${x}`);return b==null?(Qa.error(`Cannot load ${x} module. Are you sure you registered it?`),m):{...m,[x]:ri({},b.DEFAULTS||{},y)}},{}),bounds:ey(c.bounds)}}function Qi(i,e,t,r){if(!this.isEnabled()&&e===J.sources.USER&&!this.allowReadOnlyEdits)return new Bn.default;let n=t==null?null:this.getSelection(),s=this.editor.delta,a=i();if(n!=null&&(t===!0&&(t=n.index),r==null?n=iy(n,a,e):r!==0&&(n=iy(n,t,r,e)),this.setSelection(n,J.sources.SILENT)),a.length()>0){let o=[J.events.TEXT_CHANGE,a,s,e];this.emitter.emit(J.events.EDITOR_CHANGE,...o),e!==J.sources.SILENT&&this.emitter.emit(...o)}return a}function Jr(i,e,t,r,n){let s={};return typeof i.index=="number"&&typeof i.length=="number"?typeof e!="number"?(n=r,r=t,t=e,e=i.length,i=i.index):(e=i.length,i=i.index):typeof e!="number"&&(n=r,r=t,t=e,e=0),typeof t=="object"?(s=t,n=r):typeof t=="string"&&(r!=null?s[t]=r:n=t),n=n||J.sources.API,[i,e,s,n]}function iy(i,e,t,r){let n=typeof t=="number"?t:0;if(i==null)return null;let s,a;return e&&typeof e.transformPosition=="function"?[s,a]=[i.index,i.index+i.length].map(o=>e.transformPosition(o,r!==J.sources.USER)):[s,a]=[i.index,i.index+i.length].map(o=>o=0?o+n:Math.max(e,o+n)),new $t(s,a-s)}var Bn,Qa,Xd,Ji,B,oi=T(()=>{kn();Re();Bn=pt(pi(),1);V9();Qr();pm();Cs();Di();Dl();Y9();km();Z9();J9();Qa=gi("quill"),Xd=new Ln;ni.uiClass="ql-ui";Ji=class Ji{static debug(e){e===!0&&(e="log"),gi.level(e)}static find(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;return zl.get(e)||Xd.find(e,t)}static import(e){return this.imports[e]==null&&Qa.error(`Cannot import ${e}. Are you sure it was registered?`),this.imports[e]}static register(){if(typeof(arguments.length<=0?void 0:arguments[0])!="string"){let e=arguments.length<=0?void 0:arguments[0],t=!!(!(arguments.length<=1)&&arguments[1]),r="attrName"in e?e.attrName:e.blotName;typeof r=="string"?this.register(`formats/${r}`,e,t):Object.keys(e).forEach(n=>{this.register(n,e[n],t)})}else{let e=arguments.length<=0?void 0:arguments[0],t=arguments.length<=1?void 0:arguments[1],r=!!(!(arguments.length<=2)&&arguments[2]);this.imports[e]!=null&&!r&&Qa.warn(`Overwriting ${e} with`,t),this.imports[e]=t,(e.startsWith("blots/")||e.startsWith("formats/"))&&t&&typeof t!="boolean"&&t.blotName!=="abstract"&&Xd.register(t),typeof t.register=="function"&&t.register(Xd)}}constructor(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(this.options=oB(e,t),this.container=this.options.container,this.container==null){Qa.error("Invalid Quill container",e);return}this.options.debug&&Ji.debug(this.options.debug);let r=this.container.innerHTML.trim();this.container.classList.add("ql-container"),this.container.innerHTML="",zl.set(this.container,this),this.root=this.addContainer("ql-editor"),this.root.classList.add("ql-blank"),this.emitter=new J;let n=vl.blotName,s=this.options.registry.query(n);if(!s||!("blotName"in s))throw new Error(`Cannot initialize Quill without "${n}" blot`);if(this.scroll=new s(this.options.registry,this.root,{emitter:this.emitter}),this.editor=new G9(this.scroll),this.selection=new U9(this.scroll,this.emitter),this.composition=new W9(this.scroll,this.emitter),this.theme=new this.options.theme(this,this.options),this.keyboard=this.theme.addModule("keyboard"),this.clipboard=this.theme.addModule("clipboard"),this.history=this.theme.addModule("history"),this.uploader=this.theme.addModule("uploader"),this.theme.addModule("input"),this.theme.addModule("uiNode"),this.theme.init(),this.emitter.on(J.events.EDITOR_CHANGE,a=>{a===J.events.TEXT_CHANGE&&this.root.classList.toggle("ql-blank",this.editor.isBlank())}),this.emitter.on(J.events.SCROLL_UPDATE,(a,o)=>{let l=this.selection.lastRange,[h]=this.selection.getRange(),d=l&&h?{oldRange:l,newRange:h}:void 0;Qi.call(this,()=>this.editor.update(null,o,d),a)}),this.emitter.on(J.events.SCROLL_EMBED_UPDATE,(a,o)=>{let l=this.selection.lastRange,[h]=this.selection.getRange(),d=l&&h?{oldRange:l,newRange:h}:void 0;Qi.call(this,()=>{let c=new Bn.default().retain(a.offset(this)).retain({[a.statics.blotName]:o});return this.editor.update(c,[],d)},Ji.sources.USER)}),r){let a=this.clipboard.convert({html:`${r}


    `,text:` -`});this.setContents(a)}this.history.clear(),this.options.placeholder&&this.root.setAttribute("data-placeholder",this.options.placeholder),this.options.readOnly&&this.disable(),this.allowReadOnlyEdits=!1}addContainer(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:null;if(typeof e=="string"){let r=e;e=document.createElement("div"),e.classList.add(r)}return this.container.insertBefore(e,t),e}blur(){this.selection.setRange(null)}deleteText(e,t,r){return[e,t,,r]=Jr(e,t,r),Qi.call(this,()=>this.editor.deleteText(e,t),r,e,-1*t)}disable(){this.enable(!1)}editReadOnly(e){this.allowReadOnlyEdits=!0;let t=e();return this.allowReadOnlyEdits=!1,t}enable(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;this.scroll.enable(e),this.container.classList.toggle("ql-disabled",!e)}focus(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.selection.focus(),e.preventScroll||this.scrollSelectionIntoView()}format(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:J.sources.API;return Qi.call(this,()=>{let n=this.getSelection(!0),s=new Bn.default;if(n==null)return s;if(this.scroll.query(e,X.BLOCK))s=this.editor.formatLine(n.index,n.length,{[e]:t});else{if(n.length===0)return this.selection.format(e,t),s;s=this.editor.formatText(n.index,n.length,{[e]:t})}return this.setSelection(n,J.sources.SILENT),s},r)}formatLine(e,t,r,n,s){let a;return[e,t,a,s]=Jr(e,t,r,n,s),Qi.call(this,()=>this.editor.formatLine(e,t,a),s,e,0)}formatText(e,t,r,n,s){let a;return[e,t,a,s]=Jr(e,t,r,n,s),Qi.call(this,()=>this.editor.formatText(e,t,a),s,e,0)}getBounds(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,r=null;if(typeof e=="number"?r=this.selection.getBounds(e,t):r=this.selection.getBounds(e.index,e.length),!r)return null;let n=this.container.getBoundingClientRect();return{bottom:r.bottom-n.top,height:r.height,left:r.left-n.left,right:r.right-n.left,top:r.top-n.top,width:r.width}}getContents(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:this.getLength()-e;return[e,t]=Jr(e,t),this.editor.getContents(e,t)}getFormat(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.getSelection(!0),t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0;return typeof e=="number"?this.editor.getFormat(e,t):this.editor.getFormat(e.index,e.length)}getIndex(e){return e.offset(this.scroll)}getLength(){return this.scroll.length()}getLeaf(e){return this.scroll.leaf(e)}getLine(e){return this.scroll.line(e)}getLines(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Number.MAX_VALUE;return typeof e!="number"?this.scroll.lines(e.index,e.length):this.scroll.lines(e,t)}getModule(e){return this.theme.modules[e]}getSelection(){return arguments.length>0&&arguments[0]!==void 0&&arguments[0]&&this.focus(),this.update(),this.selection.getRange()[0]}getSemanticHTML(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1?arguments[1]:void 0;return typeof e=="number"&&(t=t??this.getLength()-e),[e,t]=Jr(e,t),this.editor.getHTML(e,t)}getText(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1?arguments[1]:void 0;return typeof e=="number"&&(t=t??this.getLength()-e),[e,t]=Jr(e,t),this.editor.getText(e,t)}hasFocus(){return this.selection.hasFocus()}insertEmbed(e,t,r){let n=arguments.length>3&&arguments[3]!==void 0?arguments[3]:Ji.sources.API;return Qi.call(this,()=>this.editor.insertEmbed(e,t,r),n,e)}insertText(e,t,r,n,s){let a;return[e,,a,s]=Jr(e,0,r,n,s),Qi.call(this,()=>this.editor.insertText(e,t,a),s,e,t.length)}isEnabled(){return this.scroll.isEnabled()}off(){return this.emitter.off(...arguments)}on(){return this.emitter.on(...arguments)}once(){return this.emitter.once(...arguments)}removeFormat(e,t,r){return[e,t,,r]=Jr(e,t,r),Qi.call(this,()=>this.editor.removeFormat(e,t),r,e)}scrollRectIntoView(e){K9(this.root,e)}scrollIntoView(){console.warn("Quill#scrollIntoView() has been deprecated and will be removed in the near future. Please use Quill#scrollSelectionIntoView() instead."),this.scrollSelectionIntoView()}scrollSelectionIntoView(){let e=this.selection.lastRange,t=e&&this.selection.getBounds(e.index,e.length);t&&this.scrollRectIntoView(t)}setContents(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:J.sources.API;return Qi.call(this,()=>{e=new Bn.default(e);let r=this.getLength(),n=this.editor.deleteText(0,r),s=this.editor.insertContents(0,e),a=this.editor.deleteText(this.getLength()-1,1);return n.compose(s).compose(a)},t)}setSelection(e,t,r){e==null?this.selection.setRange(null,t||Ji.sources.API):([e,t,,r]=Jr(e,t,r),this.selection.setRange(new $t(Math.max(0,e),t),r),r!==J.sources.SILENT&&this.scrollSelectionIntoView())}setText(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:J.sources.API,r=new Bn.default().insert(e);return this.setContents(r,t)}update(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:J.sources.USER,t=this.scroll.update(e);return this.selection.update(e),t}updateContents(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:J.sources.API;return Qi.call(this,()=>(e=new Bn.default(e),this.editor.applyDelta(e)),t,!0)}};U(Ji,"DEFAULTS",{bounds:null,modules:{clipboard:!0,keyboard:!0,history:!0,uploader:!0},placeholder:"",readOnly:!1,registry:Xd,theme:"default"}),U(Ji,"events",J.events),U(Ji,"sources",J.sources),U(Ji,"version","2.0.3"),U(Ji,"imports",{delta:Bn.default,parchment:bl,"core/module":Qe,"core/theme":Za});B=Ji});var _m,Oi,Ja=T(()=>{Re();_m=class extends qa{},Oi=_m});function ry(i){return i instanceof De||i instanceof ft}function ny(i){return typeof i.updateContent=="function"}function Lm(i,e,t){t.reduce((r,n)=>{let s=Bi.Op.length(n),a=n.attributes||{};if(n.insert!=null){if(typeof n.insert=="string"){let o=n.insert;i.insertAt(r,o);let[l]=i.descendant(dt,r),h=ai(l);a=Bi.AttributeMap.diff(h,a)||{}}else if(typeof n.insert=="object"){let o=Object.keys(n.insert)[0];if(o==null)return r;if(i.insertAt(r,o,n.insert[o]),i.scroll.query(o,X.INLINE)!=null){let[h]=i.descendant(dt,r),d=ai(h);a=Bi.AttributeMap.diff(d,a)||{}}}}return Object.keys(a).forEach(o=>{i.formatAt(r,s,o,a[o])}),r+s},e)}var Bi,Pn,sy,ay=T(()=>{Re();Bi=pt(pi(),1);Qr();zi();Dn();Ja();Pn=class extends vl{constructor(e,t,r){let{emitter:n}=r;super(e,t),this.emitter=n,this.batch=!1,this.optimize(),this.enable(),this.domNode.addEventListener("dragstart",s=>this.handleDragStart(s))}batchStart(){Array.isArray(this.batch)||(this.batch=[])}batchEnd(){if(!this.batch)return;let e=this.batch;this.batch=!1,this.update(e)}emitMount(e){this.emitter.emit(J.events.SCROLL_BLOT_MOUNT,e)}emitUnmount(e){this.emitter.emit(J.events.SCROLL_BLOT_UNMOUNT,e)}emitEmbedUpdate(e,t){this.emitter.emit(J.events.SCROLL_EMBED_UPDATE,e,t)}deleteAt(e,t){let[r,n]=this.line(e),[s]=this.line(e+t);if(super.deleteAt(e,t),s!=null&&r!==s&&n>0){if(r instanceof ft||s instanceof ft){this.optimize();return}let a=s.children.head instanceof Et?null:s.children.head;r.moveChildren(s,a),r.remove()}this.optimize()}enable(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;this.domNode.setAttribute("contenteditable",e?"true":"false")}formatAt(e,t,r,n){super.formatAt(e,t,r,n),this.optimize()}insertAt(e,t,r){if(e>=this.length())if(r==null||this.scroll.query(t,X.BLOCK)==null){let n=this.scroll.create(this.statics.defaultChild.blotName);this.appendChild(n),r==null&&t.endsWith(` -`)?n.insertAt(0,t.slice(0,-1),r):n.insertAt(0,t,r)}else{let n=this.scroll.create(t,r);this.appendChild(n)}else super.insertAt(e,t,r);this.optimize()}insertBefore(e,t){if(e.statics.scope===X.INLINE_BLOT){let r=this.scroll.create(this.statics.defaultChild.blotName);r.appendChild(e),super.insertBefore(r,t)}else super.insertBefore(e,t)}insertContents(e,t){let r=this.deltaToRenderBlocks(t.concat(new Bi.default().insert(` -`))),n=r.pop();if(n==null)return;this.batchStart();let s=r.shift();if(s){let l=s.type==="block"&&(s.delta.length()===0||!this.descendant(ft,e)[0]&&e{this.formatAt(c-1,1,g,m[g])}),e=c}let[a,o]=this.children.find(e);if(r.length&&(a&&(a=a.split(o),o=0),r.forEach(l=>{if(l.type==="block"){let h=this.createBlock(l.attributes,a||void 0);Lm(h,0,l.delta)}else{let h=this.create(l.key,l.value);this.insertBefore(h,a||void 0),Object.keys(l.attributes).forEach(d=>{h.format(d,l.attributes[d])})}})),n.type==="block"&&n.delta.length()){let l=a?a.offset(a.scroll)+o:this.length();Lm(this,l,n.delta)}this.batchEnd(),this.optimize()}isEnabled(){return this.domNode.getAttribute("contenteditable")==="true"}leaf(e){let t=this.path(e).pop();if(!t)return[null,-1];let[r,n]=t;return r instanceof dt?[r,n]:[null,-1]}line(e){return e===this.length()?this.line(e-1):this.descendant(ry,e)}lines(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Number.MAX_VALUE,r=(n,s,a)=>{let o=[],l=a;return n.children.forEachAt(s,a,(h,d,c)=>{ry(h)?o.push(h):h instanceof qa&&(o=o.concat(r(h,d,l))),l-=c}),o};return r(this,e,t)}optimize(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};this.batch||(super.optimize(e,t),e.length>0&&this.emitter.emit(J.events.SCROLL_OPTIMIZE,e,t))}path(e){return super.path(e).slice(1)}remove(){}update(e){if(this.batch){Array.isArray(e)&&(this.batch=this.batch.concat(e));return}let t=J.sources.USER;typeof e=="string"&&(t=e),Array.isArray(e)||(e=this.observer.takeRecords()),e=e.filter(r=>{let{target:n}=r,s=this.find(n,!0);return s&&!ny(s)}),e.length>0&&this.emitter.emit(J.events.SCROLL_BEFORE_UPDATE,t,e),super.update(e.concat([])),e.length>0&&this.emitter.emit(J.events.SCROLL_UPDATE,t,e)}updateEmbedAt(e,t,r){let[n]=this.descendant(s=>s instanceof ft,e);n&&n.statics.blotName===t&&ny(n)&&n.updateContent(r)}handleDragStart(e){e.preventDefault()}deltaToRenderBlocks(e){let t=[],r=new Bi.default;return e.forEach(n=>{let s=n?.insert;if(s)if(typeof s=="string"){let a=s.split(` -`);a.slice(0,-1).forEach(l=>{r.insert(l,n.attributes),t.push({type:"block",delta:r,attributes:n.attributes??{}}),r=new Bi.default});let o=a[a.length-1];o&&r.insert(o,n.attributes)}else{let a=Object.keys(s)[0];if(!a)return;this.query(a,X.INLINE)?r.push(n):(r.length()&&t.push({type:"block",delta:r,attributes:{}}),r=new Bi.default,t.push({type:"blockEmbed",key:a,value:s[a],attributes:n.attributes??{}}))}}),r.length()&&t.push({type:"block",delta:r,attributes:{}}),t}createBlock(e,t){let r,n={};Object.entries(e).forEach(o=>{let[l,h]=o;this.query(l,X.BLOCK&X.BLOT)!=null?r=l:n[l]=h});let s=this.create(r||this.statics.defaultChild.blotName,r?e[r]:void 0);this.insertBefore(s,t||void 0);let a=s.length();return Object.entries(n).forEach(o=>{let[l,h]=o;s.formatAt(0,a,l,h)}),s}};U(Pn,"blotName","scroll"),U(Pn,"className","ql-editor"),U(Pn,"tagName","DIV"),U(Pn,"defaultChild",De),U(Pn,"allowedChildren",[De,ft,Oi]);sy=Pn});var Im,oy,zm,Kd,Rm=T(()=>{Re();Im={scope:X.BLOCK,whitelist:["right","center","justify"]},oy=new qt("align","align",Im),zm=new ht("align","ql-align",Im),Kd=new si("align","text-align",Im)});var Pl,ly,Fl,Zd=T(()=>{Re();Pl=class extends si{value(e){let t=super.value(e);return t.startsWith("rgb(")?(t=t.replace(/^[^\d]+/,"").replace(/[^\d]+$/,""),`#${t.split(",").map(n=>`00${parseInt(n,10).toString(16)}`.slice(-2)).join("")}`):t}},ly=new ht("color","ql-color",{scope:X.INLINE}),Fl=new Pl("color","color",{scope:X.INLINE})});var hy,ql,Dm=T(()=>{Re();Zd();hy=new ht("background","ql-bg",{scope:X.INLINE}),ql=new Pl("background","background-color",{scope:X.INLINE})});var wr,st,eo,Qd=T(()=>{zi();Dn();Ll();Zr();Kr();Ja();oi();wr=class extends Oi{static create(e){let t=super.create(e);return t.setAttribute("spellcheck","false"),t}code(e,t){return this.children.map(r=>r.length()<=1?"":r.domNode.innerText).join(` +`),this.scroll.insertAt(e,t),Object.keys(r).forEach(n=>{this.scroll.formatAt(e,t.length,n,r[n])}),this.update(new Qe.default().retain(e).insert(t,gr(r)))}isBlank(){if(this.scroll.children.length===0)return!0;if(this.scroll.children.length>1)return!1;let e=this.scroll.children.head;if(e?.statics.blotName!==Oe.blotName)return!1;let t=e;return t.children.length>1?!1:t.children.head instanceof At}removeFormat(e,t){let r=this.getText(e,t),[n,s]=this.scroll.line(e+t),a=0,o=new Qe.default;n!=null&&(a=n.length()-s,o=n.delta().slice(s,s+a-1).insert(` +`));let h=this.getContents(e,t+a).diff(new Qe.default().insert(r).concat(o)),d=new Qe.default().retain(e).concat(h);return this.applyDelta(d)}update(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[],r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:void 0,n=this.delta;if(t.length===1&&t[0].type==="characterData"&&t[0].target.data.match(MB)&&this.scroll.find(t[0].target)){let s=this.scroll.find(t[0].target),a=li(s),o=s.offset(this.scroll),l=t[0].oldValue.replace(Dn.CONTENTS,""),h=new Qe.default().insert(l),d=new Qe.default().insert(s.value()),c=r&&{oldRange:ny(r.oldRange,-o),newRange:ny(r.newRange,-o)};e=new Qe.default().retain(o).concat(h.diff(d,c)).reduce((m,g)=>g.insert?m.insert(g.insert,a):m.push(g),new Qe.default),this.delta=n.compose(e)}else this.delta=this.getDelta(),(!e||!ys(n.compose(e),this.delta))&&(e=n.diff(this.delta,r));return e}};sy=Dm});var Jd,Je,Oi=T(()=>{Jd=class{constructor(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};this.quill=e,this.options=t}};U(Jd,"DEFAULTS",{});Je=Jd});var ec,Om,Za,tc=T(()=>{De();Kr();ec="\uFEFF",Om=class extends Ze{constructor(e,t){super(e,t),this.contentNode=document.createElement("span"),this.contentNode.setAttribute("contenteditable","false"),Array.from(this.domNode.childNodes).forEach(r=>{this.contentNode.appendChild(r)}),this.leftGuard=document.createTextNode(ec),this.rightGuard=document.createTextNode(ec),this.domNode.appendChild(this.leftGuard),this.domNode.appendChild(this.contentNode),this.domNode.appendChild(this.rightGuard)}index(e,t){return e===this.leftGuard?0:e===this.rightGuard?1:super.index(e,t)}restore(e){let t=null,r,n=e.data.split(ec).join("");if(e===this.leftGuard)if(this.prev instanceof nt){let s=this.prev.length();this.prev.insertAt(s,n),t={startNode:this.prev.domNode,startOffset:s+n.length}}else r=document.createTextNode(n),this.parent.insertBefore(this.scroll.create(r),this),t={startNode:r,startOffset:n.length};else e===this.rightGuard&&(this.next instanceof nt?(this.next.insertAt(0,n),t={startNode:this.next.domNode,startOffset:n.length}):(r=document.createTextNode(n),this.parent.insertBefore(this.scroll.create(r),this.next),t={startNode:r,startOffset:n.length}));return e.data=ec,t}update(e,t){e.forEach(r=>{if(r.type==="characterData"&&(r.target===this.leftGuard||r.target===this.rightGuard)){let n=this.restore(r.target);n&&(t.range=n)}})}},Za=Om});var Bm,oy,ly=T(()=>{tc();Qr();Bm=class{constructor(e,t){U(this,"isComposing",!1);this.scroll=e,this.emitter=t,this.setupListeners()}setupListeners(){this.scroll.domNode.addEventListener("compositionstart",e=>{this.isComposing||this.handleCompositionStart(e)}),this.scroll.domNode.addEventListener("compositionend",e=>{this.isComposing&&queueMicrotask(()=>{this.handleCompositionEnd(e)})})}handleCompositionStart(e){let t=e.target instanceof Node?this.scroll.find(e.target,!0):null;t&&!(t instanceof Za)&&(this.emitter.emit(ee.events.COMPOSITION_BEFORE_START,e),this.scroll.batchStart(),this.emitter.emit(ee.events.COMPOSITION_START,e),this.isComposing=!0)}handleCompositionEnd(e){this.emitter.emit(ee.events.COMPOSITION_BEFORE_END,e),this.scroll.batchEnd(),this.emitter.emit(ee.events.COMPOSITION_END,e),this.isComposing=!1}},oy=Bm});var $l,Pm,Qa,Fm=T(()=>{$l=class $l{constructor(e,t){U(this,"modules",{});this.quill=e,this.options=t}init(){Object.keys(this.options.modules).forEach(e=>{this.modules[e]==null&&this.addModule(e)})}addModule(e){let t=this.quill.constructor.import(`modules/${e}`);return this.modules[e]=new t(this.quill,this.options.modules[e]||{}),this.modules[e]}};U($l,"DEFAULTS",{modules:{}}),U($l,"themes",{default:$l});Pm=$l,Qa=Pm});var EB,SB,ic,hy,AB,dy,cy=T(()=>{EB=i=>i.parentElement||i.getRootNode().host||null,SB=i=>{let e=i.getBoundingClientRect(),t="offsetWidth"in i&&Math.abs(e.width)/i.offsetWidth||1,r="offsetHeight"in i&&Math.abs(e.height)/i.offsetHeight||1;return{top:e.top,right:e.left+i.clientWidth*t,bottom:e.top+i.clientHeight*r,left:e.left}},ic=i=>{let e=parseInt(i,10);return Number.isNaN(e)?0:e},hy=(i,e,t,r,n,s)=>ir?0:ir?e-i>r-t?i+n-t:e-r+s:0,AB=(i,e)=>{let t=i.ownerDocument,r=e,n=i;for(;n;){let s=n===t.body,a=s?{top:0,right:window.visualViewport?.width??t.documentElement.clientWidth,bottom:window.visualViewport?.height??t.documentElement.clientHeight,left:0}:SB(n),o=getComputedStyle(n),l=hy(r.left,r.right,a.left,a.right,ic(o.scrollPaddingLeft),ic(o.scrollPaddingRight)),h=hy(r.top,r.bottom,a.top,a.bottom,ic(o.scrollPaddingTop),ic(o.scrollPaddingBottom));if(l||h)if(s)t.defaultView?.scrollBy(l,h);else{let{scrollLeft:d,scrollTop:c}=n;h&&(n.scrollTop+=h),l&&(n.scrollLeft+=l);let f=n.scrollLeft-d,m=n.scrollTop-c;r={left:r.left-f,top:r.top-m,right:r.right-f,bottom:r.bottom-m}}n=s||o.position==="fixed"?null:EB(n)}},dy=AB});var kB,CB,_B,uy,fy=T(()=>{De();kB=100,CB=["block","break","cursor","inline","scroll","text"],_B=(i,e,t)=>{let r=new _n;return CB.forEach(n=>{let s=e.query(n);s&&r.register(s)}),i.forEach(n=>{let s=e.query(n);s||t.error(`Cannot register "${n}" specified in "formats" config. Are you sure it was registered?`);let a=0;for(;s;)if(r.register(s),s="blotName"in s?s.requiredContainer??null:null,a+=1,a>kB){t.error(`Cycle detected in registering blot requiredContainer: "${n}"`);break}}),r},uy=_B});function my(i){return typeof i=="string"?document.querySelector(i):i}function qm(i){return Object.entries(i??{}).reduce((e,t)=>{let[r,n]=t;return{...e,[r]:n===!0?{}:n}},{})}function py(i){return Object.fromEntries(Object.entries(i).filter(e=>e[1]!==void 0))}function LB(i,e){let t=my(i);if(!t)throw new Error("Invalid Quill container");let n=!e.theme||e.theme===B.DEFAULTS.theme?Qa:B.import(`themes/${e.theme}`);if(!n)throw new Error(`Invalid theme ${e.theme}. Did you register it?`);let{modules:s,...a}=B.DEFAULTS,{modules:o,...l}=n.DEFAULTS,h=qm(e.modules);h!=null&&h.toolbar&&h.toolbar.constructor!==Object&&(h={...h,toolbar:{container:h.toolbar}});let d=si({},qm(s),qm(o),h),c={...a,...py(l),...py(e)},f=e.registry;return f?e.formats&&Ja.warn('Ignoring "formats" option because "registry" is specified'):f=e.formats?uy(e.formats,c.registry,Ja):c.registry,{...c,registry:f,container:t,theme:n,modules:Object.entries(d).reduce((m,g)=>{let[x,y]=g;if(!y)return m;let b=B.import(`modules/${x}`);return b==null?(Ja.error(`Cannot load ${x} module. Are you sure you registered it?`),m):{...m,[x]:si({},b.DEFAULTS||{},y)}},{}),bounds:my(c.bounds)}}function Zi(i,e,t,r){if(!this.isEnabled()&&e===ee.sources.USER&&!this.allowReadOnlyEdits)return new On.default;let n=t==null?null:this.getSelection(),s=this.editor.delta,a=i();if(n!=null&&(t===!0&&(t=n.index),r==null?n=gy(n,a,e):r!==0&&(n=gy(n,t,r,e)),this.setSelection(n,ee.sources.SILENT)),a.length()>0){let o=[ee.events.TEXT_CHANGE,a,s,e];this.emitter.emit(ee.events.EDITOR_CHANGE,...o),e!==ee.sources.SILENT&&this.emitter.emit(...o)}return a}function Jr(i,e,t,r,n){let s={};return typeof i.index=="number"&&typeof i.length=="number"?typeof e!="number"?(n=r,r=t,t=e,e=i.length,i=i.index):(e=i.length,i=i.index):typeof e!="number"&&(n=r,r=t,t=e,e=0),typeof t=="object"?(s=t,n=r):typeof t=="string"&&(r!=null?s[t]=r:n=t),n=n||ee.sources.API,[i,e,s,n]}function gy(i,e,t,r){let n=typeof t=="number"?t:0;if(i==null)return null;let s,a;return e&&typeof e.transformPosition=="function"?[s,a]=[i.index,i.index+i.length].map(o=>e.transformPosition(o,r!==ee.sources.USER)):[s,a]=[i.index,i.index+i.length].map(o=>o=0?o+n:Math.max(e,o+n)),new $t(s,a-s)}var On,Ja,rc,Qi,B,hi=T(()=>{An();De();On=yt(xi(),1);ay();Qr();Sm();Cs();Oi();Hl();ly();Fm();cy();fy();Ja=yi("quill"),rc=new _n;ai.uiClass="ql-ui";Qi=class Qi{static debug(e){e===!0&&(e="log"),yi.level(e)}static find(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;return Fl.get(e)||rc.find(e,t)}static import(e){return this.imports[e]==null&&Ja.error(`Cannot import ${e}. Are you sure it was registered?`),this.imports[e]}static register(){if(typeof(arguments.length<=0?void 0:arguments[0])!="string"){let e=arguments.length<=0?void 0:arguments[0],t=!!(!(arguments.length<=1)&&arguments[1]),r="attrName"in e?e.attrName:e.blotName;typeof r=="string"?this.register(`formats/${r}`,e,t):Object.keys(e).forEach(n=>{this.register(n,e[n],t)})}else{let e=arguments.length<=0?void 0:arguments[0],t=arguments.length<=1?void 0:arguments[1],r=!!(!(arguments.length<=2)&&arguments[2]);this.imports[e]!=null&&!r&&Ja.warn(`Overwriting ${e} with`,t),this.imports[e]=t,(e.startsWith("blots/")||e.startsWith("formats/"))&&t&&typeof t!="boolean"&&t.blotName!=="abstract"&&rc.register(t),typeof t.register=="function"&&t.register(rc)}}constructor(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(this.options=LB(e,t),this.container=this.options.container,this.container==null){Ja.error("Invalid Quill container",e);return}this.options.debug&&Qi.debug(this.options.debug);let r=this.container.innerHTML.trim();this.container.classList.add("ql-container"),this.container.innerHTML="",Fl.set(this.container,this),this.root=this.addContainer("ql-editor"),this.root.classList.add("ql-blank"),this.emitter=new ee;let n=El.blotName,s=this.options.registry.query(n);if(!s||!("blotName"in s))throw new Error(`Cannot initialize Quill without "${n}" blot`);if(this.scroll=new s(this.options.registry,this.root,{emitter:this.emitter}),this.editor=new sy(this.scroll),this.selection=new iy(this.scroll,this.emitter),this.composition=new oy(this.scroll,this.emitter),this.theme=new this.options.theme(this,this.options),this.keyboard=this.theme.addModule("keyboard"),this.clipboard=this.theme.addModule("clipboard"),this.history=this.theme.addModule("history"),this.uploader=this.theme.addModule("uploader"),this.theme.addModule("input"),this.theme.addModule("uiNode"),this.theme.init(),this.emitter.on(ee.events.EDITOR_CHANGE,a=>{a===ee.events.TEXT_CHANGE&&this.root.classList.toggle("ql-blank",this.editor.isBlank())}),this.emitter.on(ee.events.SCROLL_UPDATE,(a,o)=>{let l=this.selection.lastRange,[h]=this.selection.getRange(),d=l&&h?{oldRange:l,newRange:h}:void 0;Zi.call(this,()=>this.editor.update(null,o,d),a)}),this.emitter.on(ee.events.SCROLL_EMBED_UPDATE,(a,o)=>{let l=this.selection.lastRange,[h]=this.selection.getRange(),d=l&&h?{oldRange:l,newRange:h}:void 0;Zi.call(this,()=>{let c=new On.default().retain(a.offset(this)).retain({[a.statics.blotName]:o});return this.editor.update(c,[],d)},Qi.sources.USER)}),r){let a=this.clipboard.convert({html:`${r}


    `,text:` +`});this.setContents(a)}this.history.clear(),this.options.placeholder&&this.root.setAttribute("data-placeholder",this.options.placeholder),this.options.readOnly&&this.disable(),this.allowReadOnlyEdits=!1}addContainer(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:null;if(typeof e=="string"){let r=e;e=document.createElement("div"),e.classList.add(r)}return this.container.insertBefore(e,t),e}blur(){this.selection.setRange(null)}deleteText(e,t,r){return[e,t,,r]=Jr(e,t,r),Zi.call(this,()=>this.editor.deleteText(e,t),r,e,-1*t)}disable(){this.enable(!1)}editReadOnly(e){this.allowReadOnlyEdits=!0;let t=e();return this.allowReadOnlyEdits=!1,t}enable(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;this.scroll.enable(e),this.container.classList.toggle("ql-disabled",!e)}focus(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.selection.focus(),e.preventScroll||this.scrollSelectionIntoView()}format(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:ee.sources.API;return Zi.call(this,()=>{let n=this.getSelection(!0),s=new On.default;if(n==null)return s;if(this.scroll.query(e,X.BLOCK))s=this.editor.formatLine(n.index,n.length,{[e]:t});else{if(n.length===0)return this.selection.format(e,t),s;s=this.editor.formatText(n.index,n.length,{[e]:t})}return this.setSelection(n,ee.sources.SILENT),s},r)}formatLine(e,t,r,n,s){let a;return[e,t,a,s]=Jr(e,t,r,n,s),Zi.call(this,()=>this.editor.formatLine(e,t,a),s,e,0)}formatText(e,t,r,n,s){let a;return[e,t,a,s]=Jr(e,t,r,n,s),Zi.call(this,()=>this.editor.formatText(e,t,a),s,e,0)}getBounds(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,r=null;if(typeof e=="number"?r=this.selection.getBounds(e,t):r=this.selection.getBounds(e.index,e.length),!r)return null;let n=this.container.getBoundingClientRect();return{bottom:r.bottom-n.top,height:r.height,left:r.left-n.left,right:r.right-n.left,top:r.top-n.top,width:r.width}}getContents(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:this.getLength()-e;return[e,t]=Jr(e,t),this.editor.getContents(e,t)}getFormat(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.getSelection(!0),t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0;return typeof e=="number"?this.editor.getFormat(e,t):this.editor.getFormat(e.index,e.length)}getIndex(e){return e.offset(this.scroll)}getLength(){return this.scroll.length()}getLeaf(e){return this.scroll.leaf(e)}getLine(e){return this.scroll.line(e)}getLines(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Number.MAX_VALUE;return typeof e!="number"?this.scroll.lines(e.index,e.length):this.scroll.lines(e,t)}getModule(e){return this.theme.modules[e]}getSelection(){return arguments.length>0&&arguments[0]!==void 0&&arguments[0]&&this.focus(),this.update(),this.selection.getRange()[0]}getSemanticHTML(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1?arguments[1]:void 0;return typeof e=="number"&&(t=t??this.getLength()-e),[e,t]=Jr(e,t),this.editor.getHTML(e,t)}getText(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1?arguments[1]:void 0;return typeof e=="number"&&(t=t??this.getLength()-e),[e,t]=Jr(e,t),this.editor.getText(e,t)}hasFocus(){return this.selection.hasFocus()}insertEmbed(e,t,r){let n=arguments.length>3&&arguments[3]!==void 0?arguments[3]:Qi.sources.API;return Zi.call(this,()=>this.editor.insertEmbed(e,t,r),n,e)}insertText(e,t,r,n,s){let a;return[e,,a,s]=Jr(e,0,r,n,s),Zi.call(this,()=>this.editor.insertText(e,t,a),s,e,t.length)}isEnabled(){return this.scroll.isEnabled()}off(){return this.emitter.off(...arguments)}on(){return this.emitter.on(...arguments)}once(){return this.emitter.once(...arguments)}removeFormat(e,t,r){return[e,t,,r]=Jr(e,t,r),Zi.call(this,()=>this.editor.removeFormat(e,t),r,e)}scrollRectIntoView(e){dy(this.root,e)}scrollIntoView(){console.warn("Quill#scrollIntoView() has been deprecated and will be removed in the near future. Please use Quill#scrollSelectionIntoView() instead."),this.scrollSelectionIntoView()}scrollSelectionIntoView(){let e=this.selection.lastRange,t=e&&this.selection.getBounds(e.index,e.length);t&&this.scrollRectIntoView(t)}setContents(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:ee.sources.API;return Zi.call(this,()=>{e=new On.default(e);let r=this.getLength(),n=this.editor.deleteText(0,r),s=this.editor.insertContents(0,e),a=this.editor.deleteText(this.getLength()-1,1);return n.compose(s).compose(a)},t)}setSelection(e,t,r){e==null?this.selection.setRange(null,t||Qi.sources.API):([e,t,,r]=Jr(e,t,r),this.selection.setRange(new $t(Math.max(0,e),t),r),r!==ee.sources.SILENT&&this.scrollSelectionIntoView())}setText(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:ee.sources.API,r=new On.default().insert(e);return this.setContents(r,t)}update(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:ee.sources.USER,t=this.scroll.update(e);return this.selection.update(e),t}updateContents(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:ee.sources.API;return Zi.call(this,()=>(e=new On.default(e),this.editor.applyDelta(e)),t,!0)}};U(Qi,"DEFAULTS",{bounds:null,modules:{clipboard:!0,keyboard:!0,history:!0,uploader:!0},placeholder:"",readOnly:!1,registry:rc,theme:"default"}),U(Qi,"events",ee.events),U(Qi,"sources",ee.sources),U(Qi,"version","2.0.3"),U(Qi,"imports",{delta:On.default,parchment:Sl,"core/module":Je,"core/theme":Qa});B=Qi});var Hm,Bi,eo=T(()=>{De();Hm=class extends Ha{},Bi=Hm});function xy(i){return i instanceof Oe||i instanceof gt}function yy(i){return typeof i.updateContent=="function"}function Um(i,e,t){t.reduce((r,n)=>{let s=Pi.Op.length(n),a=n.attributes||{};if(n.insert!=null){if(typeof n.insert=="string"){let o=n.insert;i.insertAt(r,o);let[l]=i.descendant(ct,r),h=li(l);a=Pi.AttributeMap.diff(h,a)||{}}else if(typeof n.insert=="object"){let o=Object.keys(n.insert)[0];if(o==null)return r;if(i.insertAt(r,o,n.insert[o]),i.scroll.query(o,X.INLINE)!=null){let[h]=i.descendant(ct,r),d=li(h);a=Pi.AttributeMap.diff(d,a)||{}}}}return Object.keys(a).forEach(o=>{i.formatAt(r,s,o,a[o])}),r+s},e)}var Pi,Bn,vy,by=T(()=>{De();Pi=yt(xi(),1);Qr();Ri();Rn();eo();Bn=class extends El{constructor(e,t,r){let{emitter:n}=r;super(e,t),this.emitter=n,this.batch=!1,this.optimize(),this.enable(),this.domNode.addEventListener("dragstart",s=>this.handleDragStart(s))}batchStart(){Array.isArray(this.batch)||(this.batch=[])}batchEnd(){if(!this.batch)return;let e=this.batch;this.batch=!1,this.update(e)}emitMount(e){this.emitter.emit(ee.events.SCROLL_BLOT_MOUNT,e)}emitUnmount(e){this.emitter.emit(ee.events.SCROLL_BLOT_UNMOUNT,e)}emitEmbedUpdate(e,t){this.emitter.emit(ee.events.SCROLL_EMBED_UPDATE,e,t)}deleteAt(e,t){let[r,n]=this.line(e),[s]=this.line(e+t);if(super.deleteAt(e,t),s!=null&&r!==s&&n>0){if(r instanceof gt||s instanceof gt){this.optimize();return}let a=s.children.head instanceof At?null:s.children.head;r.moveChildren(s,a),r.remove()}this.optimize()}enable(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;this.domNode.setAttribute("contenteditable",e?"true":"false")}formatAt(e,t,r,n){super.formatAt(e,t,r,n),this.optimize()}insertAt(e,t,r){if(e>=this.length())if(r==null||this.scroll.query(t,X.BLOCK)==null){let n=this.scroll.create(this.statics.defaultChild.blotName);this.appendChild(n),r==null&&t.endsWith(` +`)?n.insertAt(0,t.slice(0,-1),r):n.insertAt(0,t,r)}else{let n=this.scroll.create(t,r);this.appendChild(n)}else super.insertAt(e,t,r);this.optimize()}insertBefore(e,t){if(e.statics.scope===X.INLINE_BLOT){let r=this.scroll.create(this.statics.defaultChild.blotName);r.appendChild(e),super.insertBefore(r,t)}else super.insertBefore(e,t)}insertContents(e,t){let r=this.deltaToRenderBlocks(t.concat(new Pi.default().insert(` +`))),n=r.pop();if(n==null)return;this.batchStart();let s=r.shift();if(s){let l=s.type==="block"&&(s.delta.length()===0||!this.descendant(gt,e)[0]&&e{this.formatAt(c-1,1,g,m[g])}),e=c}let[a,o]=this.children.find(e);if(r.length&&(a&&(a=a.split(o),o=0),r.forEach(l=>{if(l.type==="block"){let h=this.createBlock(l.attributes,a||void 0);Um(h,0,l.delta)}else{let h=this.create(l.key,l.value);this.insertBefore(h,a||void 0),Object.keys(l.attributes).forEach(d=>{h.format(d,l.attributes[d])})}})),n.type==="block"&&n.delta.length()){let l=a?a.offset(a.scroll)+o:this.length();Um(this,l,n.delta)}this.batchEnd(),this.optimize()}isEnabled(){return this.domNode.getAttribute("contenteditable")==="true"}leaf(e){let t=this.path(e).pop();if(!t)return[null,-1];let[r,n]=t;return r instanceof ct?[r,n]:[null,-1]}line(e){return e===this.length()?this.line(e-1):this.descendant(xy,e)}lines(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:0,t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Number.MAX_VALUE,r=(n,s,a)=>{let o=[],l=a;return n.children.forEachAt(s,a,(h,d,c)=>{xy(h)?o.push(h):h instanceof Ha&&(o=o.concat(r(h,d,l))),l-=c}),o};return r(this,e,t)}optimize(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};this.batch||(super.optimize(e,t),e.length>0&&this.emitter.emit(ee.events.SCROLL_OPTIMIZE,e,t))}path(e){return super.path(e).slice(1)}remove(){}update(e){if(this.batch){Array.isArray(e)&&(this.batch=this.batch.concat(e));return}let t=ee.sources.USER;typeof e=="string"&&(t=e),Array.isArray(e)||(e=this.observer.takeRecords()),e=e.filter(r=>{let{target:n}=r,s=this.find(n,!0);return s&&!yy(s)}),e.length>0&&this.emitter.emit(ee.events.SCROLL_BEFORE_UPDATE,t,e),super.update(e.concat([])),e.length>0&&this.emitter.emit(ee.events.SCROLL_UPDATE,t,e)}updateEmbedAt(e,t,r){let[n]=this.descendant(s=>s instanceof gt,e);n&&n.statics.blotName===t&&yy(n)&&n.updateContent(r)}handleDragStart(e){e.preventDefault()}deltaToRenderBlocks(e){let t=[],r=new Pi.default;return e.forEach(n=>{let s=n?.insert;if(s)if(typeof s=="string"){let a=s.split(` +`);a.slice(0,-1).forEach(l=>{r.insert(l,n.attributes),t.push({type:"block",delta:r,attributes:n.attributes??{}}),r=new Pi.default});let o=a[a.length-1];o&&r.insert(o,n.attributes)}else{let a=Object.keys(s)[0];if(!a)return;this.query(a,X.INLINE)?r.push(n):(r.length()&&t.push({type:"block",delta:r,attributes:{}}),r=new Pi.default,t.push({type:"blockEmbed",key:a,value:s[a],attributes:n.attributes??{}}))}}),r.length()&&t.push({type:"block",delta:r,attributes:{}}),t}createBlock(e,t){let r,n={};Object.entries(e).forEach(o=>{let[l,h]=o;this.query(l,X.BLOCK&X.BLOT)!=null?r=l:n[l]=h});let s=this.create(r||this.statics.defaultChild.blotName,r?e[r]:void 0);this.insertBefore(s,t||void 0);let a=s.length();return Object.entries(n).forEach(o=>{let[l,h]=o;s.formatAt(0,a,l,h)}),s}};U(Bn,"blotName","scroll"),U(Bn,"className","ql-editor"),U(Bn,"tagName","DIV"),U(Bn,"defaultChild",Oe),U(Bn,"allowedChildren",[Oe,gt,Bi]);vy=Bn});var $m,wy,jm,nc,Gm=T(()=>{De();$m={scope:X.BLOCK,whitelist:["right","center","justify"]},wy=new qt("align","align",$m),jm=new dt("align","ql-align",$m),nc=new oi("align","text-align",$m)});var jl,My,Gl,sc=T(()=>{De();jl=class extends oi{value(e){let t=super.value(e);return t.startsWith("rgb(")?(t=t.replace(/^[^\d]+/,"").replace(/[^\d]+$/,""),`#${t.split(",").map(n=>`00${parseInt(n,10).toString(16)}`.slice(-2)).join("")}`):t}},My=new dt("color","ql-color",{scope:X.INLINE}),Gl=new jl("color","color",{scope:X.INLINE})});var Ty,Wl,Wm=T(()=>{De();sc();Ty=new dt("background","ql-bg",{scope:X.INLINE}),Wl=new jl("background","background-color",{scope:X.INLINE})});var vr,st,to,ac=T(()=>{Ri();Rn();Bl();Zr();Kr();eo();hi();vr=class extends Bi{static create(e){let t=super.create(e);return t.setAttribute("spellcheck","false"),t}code(e,t){return this.children.map(r=>r.length()<=1?"":r.domNode.innerText).join(` `).slice(e,e+t)}html(e,t){return`
     ${ks(this.code(e,t))}
    -
    `}},st=class extends De{static register(){B.register(wr)}};U(st,"TAB"," ");eo=class extends St{};eo.blotName="code";eo.tagName="CODE";st.blotName="code-block";st.className="ql-code-block";st.tagName="DIV";wr.blotName="code-block-container";wr.className="ql-code-block-container";wr.tagName="DIV";wr.allowedChildren=[st];st.allowedChildren=[nt,Et,On];st.requiredContainer=wr});var Om,Jd,Bm,ec,Pm=T(()=>{Re();Om={scope:X.BLOCK,whitelist:["rtl"]},Jd=new qt("direction","dir",Om),Bm=new ht("direction","ql-direction",Om),ec=new si("direction","direction",Om)});var dy,qm,Fm,tc,Hm=T(()=>{Re();dy={scope:X.INLINE,whitelist:["serif","monospace"]},qm=new ht("font","ql-font",dy),Fm=class extends si{value(e){return super.value(e).replace(/["']/g,"")}},tc=new Fm("font","font-family",dy)});var Um,ic,$m=T(()=>{Re();Um=new ht("size","ql-size",{scope:X.INLINE,whitelist:["small","large","huge"]}),ic=new si("size","font-size",{scope:X.INLINE,whitelist:["10px","18px","32px"]})});function cy(i){return{key:"Tab",shiftKey:!i,format:{"code-block":!0},handler(e,t){let{event:r}=t,n=this.quill.scroll.query("code-block"),{TAB:s}=n;if(e.length===0&&!r.shiftKey){this.quill.insertText(e.index,s,B.sources.USER),this.quill.setSelection(e.index+s.length,B.sources.SILENT);return}let a=e.length===0?this.quill.getLines(e.index,1):this.quill.getLines(e),{index:o,length:l}=e;a.forEach((h,d)=>{i?(h.insertAt(0,s),d===0?o+=s.length:l+=s.length):h.domNode.textContent.startsWith(s)&&(h.deleteAt(0,s.length),d===0?o-=s.length:l-=s.length)}),this.quill.update(B.sources.USER),this.quill.setSelection(o,l,B.sources.SILENT)}}}function rc(i,e){return{key:i,shiftKey:e,altKey:null,[i==="ArrowLeft"?"prefix":"suffix"]:/^$/,handler(r){let{index:n}=r;i==="ArrowRight"&&(n+=r.length+1);let[s]=this.quill.getLeaf(n);return s instanceof Ke?(i==="ArrowLeft"?e?this.quill.setSelection(r.index-1,r.length+1,B.sources.USER):this.quill.setSelection(r.index-1,B.sources.USER):e?this.quill.setSelection(r.index,r.length+1,B.sources.USER):this.quill.setSelection(r.index+r.length+1,B.sources.USER),!1):!0}}}function jm(i){return{key:i[0],shortKey:!0,handler(e,t){this.quill.format(i,!t.format[i],B.sources.USER)}}}function uy(i){return{key:i?"ArrowUp":"ArrowDown",collapsed:!0,format:["table"],handler(e,t){let r=i?"prev":"next",n=t.line,s=n.parent[r];if(s!=null){if(s.statics.blotName==="table-row"){let a=s.children.head,o=n;for(;o.prev!=null;)o=o.prev,a=a.next;let l=a.offset(this.quill.scroll)+Math.min(t.offset,a.length()-1);this.quill.setSelection(l,0,B.sources.USER)}}else{let a=n.table()[r];a!=null&&(i?this.quill.setSelection(a.offset(this.quill.scroll)+a.length()-1,0,B.sources.USER):this.quill.setSelection(a.offset(this.quill.scroll),0,B.sources.USER))}return!1}}}function cB(i){if(typeof i=="string"||typeof i=="number")i={key:i};else if(typeof i=="object")i=yr(i);else return null;return i.shortKey&&(i[hB]=i.shortKey,delete i.shortKey),i}function Ul(i){let{quill:e,range:t}=i,r=e.getLines(t),n={};if(r.length>1){let s=r[0].formats(),a=r[r.length-1].formats();n=kt.AttributeMap.diff(a,s)||{}}e.deleteText(t,B.sources.USER),Object.keys(n).length>0&&e.formatLine(t.index,1,n,B.sources.USER),e.setSelection(t.index,B.sources.SILENT)}function uB(i,e,t,r){return e.prev==null&&e.next==null?t.prev==null&&t.next==null?r===0?-1:1:t.prev==null?-1:1:e.prev==null?-1:e.next==null?1:null}var kt,lB,hB,Hl,dB,nc=T(()=>{kn();kt=pt(pi(),1);Re();oi();Cs();Di();lB=gi("quill:keyboard"),hB=/Mac/i.test(navigator.platform)?"metaKey":"ctrlKey",Hl=class i extends Qe{static match(e,t){return["altKey","ctrlKey","metaKey","shiftKey"].some(r=>!!t[r]!==e[r]&&t[r]!==null)?!1:t.key===e.key||t.key===e.which}constructor(e,t){super(e,t),this.bindings={},Object.keys(this.options.bindings).forEach(r=>{this.options.bindings[r]&&this.addBinding(this.options.bindings[r])}),this.addBinding({key:"Enter",shiftKey:null},this.handleEnter),this.addBinding({key:"Enter",metaKey:null,ctrlKey:null,altKey:null},()=>{}),/Firefox/i.test(navigator.userAgent)?(this.addBinding({key:"Backspace"},{collapsed:!0},this.handleBackspace),this.addBinding({key:"Delete"},{collapsed:!0},this.handleDelete)):(this.addBinding({key:"Backspace"},{collapsed:!0,prefix:/^.?$/},this.handleBackspace),this.addBinding({key:"Delete"},{collapsed:!0,suffix:/^.?$/},this.handleDelete)),this.addBinding({key:"Backspace"},{collapsed:!1},this.handleDeleteRange),this.addBinding({key:"Delete"},{collapsed:!1},this.handleDeleteRange),this.addBinding({key:"Backspace",altKey:null,ctrlKey:null,metaKey:null,shiftKey:null},{collapsed:!0,offset:0},this.handleBackspace),this.listen()}addBinding(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},n=cB(e);if(n==null){lB.warn("Attempted to add invalid keyboard binding",n);return}typeof t=="function"&&(t={handler:t}),typeof r=="function"&&(r={handler:r}),(Array.isArray(n.key)?n.key:[n.key]).forEach(a=>{let o={...n,key:a,...t,...r};this.bindings[o.key]=this.bindings[o.key]||[],this.bindings[o.key].push(o)})}listen(){this.quill.root.addEventListener("keydown",e=>{if(e.defaultPrevented||e.isComposing||e.keyCode===229&&(e.key==="Enter"||e.key==="Backspace"))return;let n=(this.bindings[e.key]||[]).concat(this.bindings[e.which]||[]).filter(b=>i.match(e,b));if(n.length===0)return;let s=B.find(e.target,!0);if(s&&s.scroll!==this.quill.scroll)return;let a=this.quill.getSelection();if(a==null||!this.quill.hasFocus())return;let[o,l]=this.quill.getLine(a.index),[h,d]=this.quill.getLeaf(a.index),[c,f]=a.length===0?[h,d]:this.quill.getLeaf(a.index+a.length),m=h instanceof Ha?h.value().slice(0,d):"",g=c instanceof Ha?c.value().slice(f):"",x={collapsed:a.length===0,empty:a.length===0&&o.length()<=1,format:this.quill.getFormat(a),line:o,offset:l,prefix:m,suffix:g,event:e};n.some(b=>{if(b.collapsed!=null&&b.collapsed!==x.collapsed||b.empty!=null&&b.empty!==x.empty||b.offset!=null&&b.offset!==x.offset)return!1;if(Array.isArray(b.format)){if(b.format.every(S=>x.format[S]==null))return!1}else if(typeof b.format=="object"&&!Object.keys(b.format).every(S=>b.format[S]===!0?x.format[S]!=null:b.format[S]===!1?x.format[S]==null:ys(b.format[S],x.format[S])))return!1;return b.prefix!=null&&!b.prefix.test(x.prefix)||b.suffix!=null&&!b.suffix.test(x.suffix)?!1:b.handler.call(this,a,x,b)!==!0})&&e.preventDefault()})}handleBackspace(e,t){let r=/[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test(t.prefix)?2:1;if(e.index===0||this.quill.getLength()<=1)return;let n={},[s]=this.quill.getLine(e.index),a=new kt.default().retain(e.index-r).delete(r);if(t.offset===0){let[o]=this.quill.getLine(e.index-1);if(o&&!(o.statics.blotName==="block"&&o.length()<=1)){let h=s.formats(),d=this.quill.getFormat(e.index-1,1);if(n=kt.AttributeMap.diff(h,d)||{},Object.keys(n).length>0){let c=new kt.default().retain(e.index+s.length()-2).retain(1,n);a=a.compose(c)}}}this.quill.updateContents(a,B.sources.USER),this.quill.focus()}handleDelete(e,t){let r=/^[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(t.suffix)?2:1;if(e.index>=this.quill.getLength()-r)return;let n={},[s]=this.quill.getLine(e.index),a=new kt.default().retain(e.index).delete(r);if(t.offset>=s.length()-1){let[o]=this.quill.getLine(e.index+1);if(o){let l=s.formats(),h=this.quill.getFormat(e.index,1);n=kt.AttributeMap.diff(l,h)||{},Object.keys(n).length>0&&(a=a.retain(o.length()-1).retain(1,n))}}this.quill.updateContents(a,B.sources.USER),this.quill.focus()}handleDeleteRange(e){Ul({range:e,quill:this.quill}),this.quill.focus()}handleEnter(e,t){let r=Object.keys(t.format).reduce((s,a)=>(this.quill.scroll.query(a,X.BLOCK)&&!Array.isArray(t.format[a])&&(s[a]=t.format[a]),s),{}),n=new kt.default().retain(e.index).delete(e.length).insert(` -`,r);this.quill.updateContents(n,B.sources.USER),this.quill.setSelection(e.index+1,B.sources.SILENT),this.quill.focus()}},dB={bindings:{bold:jm("bold"),italic:jm("italic"),underline:jm("underline"),indent:{key:"Tab",format:["blockquote","indent","list"],handler(i,e){return e.collapsed&&e.offset!==0?!0:(this.quill.format("indent","+1",B.sources.USER),!1)}},outdent:{key:"Tab",shiftKey:!0,format:["blockquote","indent","list"],handler(i,e){return e.collapsed&&e.offset!==0?!0:(this.quill.format("indent","-1",B.sources.USER),!1)}},"outdent backspace":{key:"Backspace",collapsed:!0,shiftKey:null,metaKey:null,ctrlKey:null,altKey:null,format:["indent","list"],offset:0,handler(i,e){e.format.indent!=null?this.quill.format("indent","-1",B.sources.USER):e.format.list!=null&&this.quill.format("list",!1,B.sources.USER)}},"indent code-block":cy(!0),"outdent code-block":cy(!1),"remove tab":{key:"Tab",shiftKey:!0,collapsed:!0,prefix:/\t$/,handler(i){this.quill.deleteText(i.index-1,1,B.sources.USER)}},tab:{key:"Tab",handler(i,e){if(e.format.table)return!0;this.quill.history.cutoff();let t=new kt.default().retain(i.index).delete(i.length).insert(" ");return this.quill.updateContents(t,B.sources.USER),this.quill.history.cutoff(),this.quill.setSelection(i.index+1,B.sources.SILENT),!1}},"blockquote empty enter":{key:"Enter",collapsed:!0,format:["blockquote"],empty:!0,handler(){this.quill.format("blockquote",!1,B.sources.USER)}},"list empty enter":{key:"Enter",collapsed:!0,format:["list"],empty:!0,handler(i,e){let t={list:!1};e.format.indent&&(t.indent=!1),this.quill.formatLine(i.index,i.length,t,B.sources.USER)}},"checklist enter":{key:"Enter",collapsed:!0,format:{list:"checked"},handler(i){let[e,t]=this.quill.getLine(i.index),r={...e.formats(),list:"checked"},n=new kt.default().retain(i.index).insert(` -`,r).retain(e.length()-t-1).retain(1,{list:"unchecked"});this.quill.updateContents(n,B.sources.USER),this.quill.setSelection(i.index+1,B.sources.SILENT),this.quill.scrollSelectionIntoView()}},"header enter":{key:"Enter",collapsed:!0,format:["header"],suffix:/^$/,handler(i,e){let[t,r]=this.quill.getLine(i.index),n=new kt.default().retain(i.index).insert(` -`,e.format).retain(t.length()-r-1).retain(1,{header:null});this.quill.updateContents(n,B.sources.USER),this.quill.setSelection(i.index+1,B.sources.SILENT),this.quill.scrollSelectionIntoView()}},"table backspace":{key:"Backspace",format:["table"],collapsed:!0,offset:0,handler(){}},"table delete":{key:"Delete",format:["table"],collapsed:!0,suffix:/^$/,handler(){}},"table enter":{key:"Enter",shiftKey:null,format:["table"],handler(i){let e=this.quill.getModule("table");if(e){let[t,r,n,s]=e.getTable(i),a=uB(t,r,n,s);if(a==null)return;let o=t.offset();if(a<0){let l=new kt.default().retain(o).insert(` -`);this.quill.updateContents(l,B.sources.USER),this.quill.setSelection(i.index+1,i.length,B.sources.SILENT)}else if(a>0){o+=t.length();let l=new kt.default().retain(o).insert(` -`);this.quill.updateContents(l,B.sources.USER),this.quill.setSelection(o,B.sources.USER)}}}},"table tab":{key:"Tab",shiftKey:null,format:["table"],handler(i,e){let{event:t,line:r}=e,n=r.offset(this.quill.scroll);t.shiftKey?this.quill.setSelection(n-1,B.sources.USER):this.quill.setSelection(n+r.length(),B.sources.USER)}},"list autofill":{key:" ",shiftKey:null,collapsed:!0,format:{"code-block":!1,blockquote:!1,table:!1},prefix:/^\s*?(\d+\.|-|\*|\[ ?\]|\[x\])$/,handler(i,e){if(this.quill.scroll.query("list")==null)return!0;let{length:t}=e.prefix,[r,n]=this.quill.getLine(i.index);if(n>t)return!0;let s;switch(e.prefix.trim()){case"[]":case"[ ]":s="unchecked";break;case"[x]":s="checked";break;case"-":case"*":s="bullet";break;default:s="ordered"}this.quill.insertText(i.index," ",B.sources.USER),this.quill.history.cutoff();let a=new kt.default().retain(i.index-n).delete(t+1).retain(r.length()-2-n).retain(1,{list:s});return this.quill.updateContents(a,B.sources.USER),this.quill.history.cutoff(),this.quill.setSelection(i.index-t,B.sources.SILENT),!1}},"code exit":{key:"Enter",collapsed:!0,format:["code-block"],prefix:/^$/,suffix:/^\s*$/,handler(i){let[e,t]=this.quill.getLine(i.index),r=2,n=e;for(;n!=null&&n.length()<=1&&n.formats()["code-block"];)if(n=n.prev,r-=1,r<=0){let s=new kt.default().retain(i.index+e.length()-t-2).retain(1,{"code-block":null}).delete(1);return this.quill.updateContents(s,B.sources.USER),this.quill.setSelection(i.index-1,B.sources.SILENT),!1}return!0}},"embed left":rc("ArrowLeft",!1),"embed left shift":rc("ArrowLeft",!0),"embed right":rc("ArrowRight",!1),"embed right shift":rc("ArrowRight",!0),"table down":uy(!1),"table up":uy(!0)}};Hl.DEFAULTS=dB});function Gm(i){i.querySelector('[id^="docs-internal-guid-"]')&&(gB(i),pB(i))}var fB,mB,fy,pB,gB,my=T(()=>{fB=/font-weight:\s*normal/,mB=["P","OL","UL"],fy=i=>i&&mB.includes(i.tagName),pB=i=>{Array.from(i.querySelectorAll("br")).filter(e=>fy(e.previousElementSibling)&&fy(e.nextElementSibling)).forEach(e=>{e.parentNode?.removeChild(e)})},gB=i=>{Array.from(i.querySelectorAll('b[style*="font-weight"]')).filter(e=>e.getAttribute("style")?.match(fB)).forEach(e=>{let t=i.createDocumentFragment();t.append(...e.childNodes),e.parentNode?.replaceChild(t,e)})}});function Vm(i){i.documentElement.getAttribute("xmlns:w")==="urn:schemas-microsoft-com:office:word"&&wB(i)}var xB,yB,vB,bB,wB,py=T(()=>{xB=/\bmso-list:[^;]*ignore/i,yB=/\bmso-list:[^;]*\bl(\d+)/i,vB=/\bmso-list:[^;]*\blevel(\d+)/i,bB=(i,e)=>{let t=i.getAttribute("style"),r=t?.match(yB);if(!r)return null;let n=Number(r[1]),s=t?.match(vB),a=s?Number(s[1]):1,o=new RegExp(`@list l${n}:level${a}\\s*\\{[^\\}]*mso-level-number-format:\\s*([\\w-]+)`,"i"),l=e.match(o),h=l&&l[1]==="bullet"?"bullet":"ordered";return{id:n,indent:a,type:h,element:i}},wB=i=>{let e=Array.from(i.querySelectorAll("[style*=mso-list]")),t=[],r=[];e.forEach(a=>{(a.getAttribute("style")||"").match(xB)?t.push(a):r.push(a)}),t.forEach(a=>a.parentNode?.removeChild(a));let n=i.documentElement.innerHTML,s=r.map(a=>bB(a,n)).filter(a=>a);for(;s.length;){let a=[],o=s.shift();for(;o;)a.push(o),o=s.length&&s[0]?.element===o.element.nextElementSibling&&s[0].id===o.id?s.shift():null;let l=document.createElement("ul");a.forEach(c=>{let f=document.createElement("li");f.setAttribute("data-list",c.type),c.indent>1&&f.setAttribute("class",`ql-indent-${c.indent-1}`),f.innerHTML=c.element.innerHTML,l.appendChild(f)});let h=a[0]?.element,{parentNode:d}=h??{};h&&d?.replaceChild(l,h),a.slice(1).forEach(c=>{let{element:f}=c;d?.removeChild(f)})}}});var MB,TB,gy,xy=T(()=>{my();py();MB=[Vm,Gm],TB=i=>{i.documentElement&&MB.forEach(e=>{e(i)})},gy=TB});function _s(i,e,t,r){return r.query(e)?i.reduce((n,s)=>{if(!s.insert)return n;if(s.attributes&&s.attributes[e])return n.push(s);let a=t?{[e]:t}:{};return n.insert(s.insert,{...a,...s.attributes})},new li.default):i}function jl(i,e){let t="";for(let r=i.ops.length-1;r>=0&&t.lengtha(e,s,i),new li.default):e.nodeType===e.ELEMENT_NODE?Array.from(e.childNodes||[]).reduce((s,a)=>{let o=ac(i,a,t,r,n);return a.nodeType===e.ELEMENT_NODE&&(o=t.reduce((l,h)=>h(a,l,i),o),o=(n.get(a)||[]).reduce((l,h)=>h(a,l,i),o)),s.concat(o)},new li.default):new li.default}function Wm(i){return(e,t,r)=>_s(t,i,!0,r)}function kB(i,e,t){let r=qt.keys(i),n=ht.keys(i),s=si.keys(i),a={};return r.concat(n).concat(s).forEach(o=>{let l=t.query(o,X.ATTRIBUTE);l!=null&&(a[l.attrName]=l.value(i),a[l.attrName])||(l=SB[o],l!=null&&(l.attrName===o||l.keyName===o)&&(a[l.attrName]=l.value(i)||void 0),l=yy[o],l!=null&&(l.attrName===o||l.keyName===o)&&(l=yy[o],a[l.attrName]=l.value(i)||void 0))}),Object.entries(a).reduce((o,l)=>{let[h,d]=l;return _s(o,h,d,t)},e)}function CB(i,e,t){let r=t.query(i);if(r==null)return e;if(r.prototype instanceof Ke){let n={},s=r.value(i);if(s!=null)return n[r.blotName]=s,new li.default().insert(n,r.formats(i,t))}else if(r.prototype instanceof vs&&!jl(e,` +`}},st=class extends Oe{static register(){B.register(vr)}};U(st,"TAB"," ");to=class extends kt{};to.blotName="code";to.tagName="CODE";st.blotName="code-block";st.className="ql-code-block";st.tagName="DIV";vr.blotName="code-block-container";vr.className="ql-code-block-container";vr.tagName="DIV";vr.allowedChildren=[st];st.allowedChildren=[nt,At,Dn];st.requiredContainer=vr});var Vm,oc,Ym,lc,Xm=T(()=>{De();Vm={scope:X.BLOCK,whitelist:["rtl"]},oc=new qt("direction","dir",Vm),Ym=new dt("direction","ql-direction",Vm),lc=new oi("direction","direction",Vm)});var Ny,Zm,Km,hc,Qm=T(()=>{De();Ny={scope:X.INLINE,whitelist:["serif","monospace"]},Zm=new dt("font","ql-font",Ny),Km=class extends oi{value(e){return super.value(e).replace(/["']/g,"")}},hc=new Km("font","font-family",Ny)});var Jm,dc,ep=T(()=>{De();Jm=new dt("size","ql-size",{scope:X.INLINE,whitelist:["small","large","huge"]}),dc=new oi("size","font-size",{scope:X.INLINE,whitelist:["10px","18px","32px"]})});function Ey(i){return{key:"Tab",shiftKey:!i,format:{"code-block":!0},handler(e,t){let{event:r}=t,n=this.quill.scroll.query("code-block"),{TAB:s}=n;if(e.length===0&&!r.shiftKey){this.quill.insertText(e.index,s,B.sources.USER),this.quill.setSelection(e.index+s.length,B.sources.SILENT);return}let a=e.length===0?this.quill.getLines(e.index,1):this.quill.getLines(e),{index:o,length:l}=e;a.forEach((h,d)=>{i?(h.insertAt(0,s),d===0?o+=s.length:l+=s.length):h.domNode.textContent.startsWith(s)&&(h.deleteAt(0,s.length),d===0?o-=s.length:l-=s.length)}),this.quill.update(B.sources.USER),this.quill.setSelection(o,l,B.sources.SILENT)}}}function cc(i,e){return{key:i,shiftKey:e,altKey:null,[i==="ArrowLeft"?"prefix":"suffix"]:/^$/,handler(r){let{index:n}=r;i==="ArrowRight"&&(n+=r.length+1);let[s]=this.quill.getLeaf(n);return s instanceof Ze?(i==="ArrowLeft"?e?this.quill.setSelection(r.index-1,r.length+1,B.sources.USER):this.quill.setSelection(r.index-1,B.sources.USER):e?this.quill.setSelection(r.index,r.length+1,B.sources.USER):this.quill.setSelection(r.index+r.length+1,B.sources.USER),!1):!0}}}function tp(i){return{key:i[0],shortKey:!0,handler(e,t){this.quill.format(i,!t.format[i],B.sources.USER)}}}function Sy(i){return{key:i?"ArrowUp":"ArrowDown",collapsed:!0,format:["table"],handler(e,t){let r=i?"prev":"next",n=t.line,s=n.parent[r];if(s!=null){if(s.statics.blotName==="table-row"){let a=s.children.head,o=n;for(;o.prev!=null;)o=o.prev,a=a.next;let l=a.offset(this.quill.scroll)+Math.min(t.offset,a.length()-1);this.quill.setSelection(l,0,B.sources.USER)}}else{let a=n.table()[r];a!=null&&(i?this.quill.setSelection(a.offset(this.quill.scroll)+a.length()-1,0,B.sources.USER):this.quill.setSelection(a.offset(this.quill.scroll),0,B.sources.USER))}return!1}}}function DB(i){if(typeof i=="string"||typeof i=="number")i={key:i};else if(typeof i=="object")i=gr(i);else return null;return i.shortKey&&(i[zB]=i.shortKey,delete i.shortKey),i}function Yl(i){let{quill:e,range:t}=i,r=e.getLines(t),n={};if(r.length>1){let s=r[0].formats(),a=r[r.length-1].formats();n=_t.AttributeMap.diff(a,s)||{}}e.deleteText(t,B.sources.USER),Object.keys(n).length>0&&e.formatLine(t.index,1,n,B.sources.USER),e.setSelection(t.index,B.sources.SILENT)}function OB(i,e,t,r){return e.prev==null&&e.next==null?t.prev==null&&t.next==null?r===0?-1:1:t.prev==null?-1:1:e.prev==null?-1:e.next==null?1:null}var _t,IB,zB,Vl,RB,uc=T(()=>{An();_t=yt(xi(),1);De();hi();Cs();Oi();IB=yi("quill:keyboard"),zB=/Mac/i.test(navigator.platform)?"metaKey":"ctrlKey",Vl=class i extends Je{static match(e,t){return["altKey","ctrlKey","metaKey","shiftKey"].some(r=>!!t[r]!==e[r]&&t[r]!==null)?!1:t.key===e.key||t.key===e.which}constructor(e,t){super(e,t),this.bindings={},Object.keys(this.options.bindings).forEach(r=>{this.options.bindings[r]&&this.addBinding(this.options.bindings[r])}),this.addBinding({key:"Enter",shiftKey:null},this.handleEnter),this.addBinding({key:"Enter",metaKey:null,ctrlKey:null,altKey:null},()=>{}),/Firefox/i.test(navigator.userAgent)?(this.addBinding({key:"Backspace"},{collapsed:!0},this.handleBackspace),this.addBinding({key:"Delete"},{collapsed:!0},this.handleDelete)):(this.addBinding({key:"Backspace"},{collapsed:!0,prefix:/^.?$/},this.handleBackspace),this.addBinding({key:"Delete"},{collapsed:!0,suffix:/^.?$/},this.handleDelete)),this.addBinding({key:"Backspace"},{collapsed:!1},this.handleDeleteRange),this.addBinding({key:"Delete"},{collapsed:!1},this.handleDeleteRange),this.addBinding({key:"Backspace",altKey:null,ctrlKey:null,metaKey:null,shiftKey:null},{collapsed:!0,offset:0},this.handleBackspace),this.listen()}addBinding(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},n=DB(e);if(n==null){IB.warn("Attempted to add invalid keyboard binding",n);return}typeof t=="function"&&(t={handler:t}),typeof r=="function"&&(r={handler:r}),(Array.isArray(n.key)?n.key:[n.key]).forEach(a=>{let o={...n,key:a,...t,...r};this.bindings[o.key]=this.bindings[o.key]||[],this.bindings[o.key].push(o)})}listen(){this.quill.root.addEventListener("keydown",e=>{if(e.defaultPrevented||e.isComposing||e.keyCode===229&&(e.key==="Enter"||e.key==="Backspace"))return;let n=(this.bindings[e.key]||[]).concat(this.bindings[e.which]||[]).filter(b=>i.match(e,b));if(n.length===0)return;let s=B.find(e.target,!0);if(s&&s.scroll!==this.quill.scroll)return;let a=this.quill.getSelection();if(a==null||!this.quill.hasFocus())return;let[o,l]=this.quill.getLine(a.index),[h,d]=this.quill.getLeaf(a.index),[c,f]=a.length===0?[h,d]:this.quill.getLeaf(a.index+a.length),m=h instanceof Ua?h.value().slice(0,d):"",g=c instanceof Ua?c.value().slice(f):"",x={collapsed:a.length===0,empty:a.length===0&&o.length()<=1,format:this.quill.getFormat(a),line:o,offset:l,prefix:m,suffix:g,event:e};n.some(b=>{if(b.collapsed!=null&&b.collapsed!==x.collapsed||b.empty!=null&&b.empty!==x.empty||b.offset!=null&&b.offset!==x.offset)return!1;if(Array.isArray(b.format)){if(b.format.every(E=>x.format[E]==null))return!1}else if(typeof b.format=="object"&&!Object.keys(b.format).every(E=>b.format[E]===!0?x.format[E]!=null:b.format[E]===!1?x.format[E]==null:ys(b.format[E],x.format[E])))return!1;return b.prefix!=null&&!b.prefix.test(x.prefix)||b.suffix!=null&&!b.suffix.test(x.suffix)?!1:b.handler.call(this,a,x,b)!==!0})&&e.preventDefault()})}handleBackspace(e,t){let r=/[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test(t.prefix)?2:1;if(e.index===0||this.quill.getLength()<=1)return;let n={},[s]=this.quill.getLine(e.index),a=new _t.default().retain(e.index-r).delete(r);if(t.offset===0){let[o]=this.quill.getLine(e.index-1);if(o&&!(o.statics.blotName==="block"&&o.length()<=1)){let h=s.formats(),d=this.quill.getFormat(e.index-1,1);if(n=_t.AttributeMap.diff(h,d)||{},Object.keys(n).length>0){let c=new _t.default().retain(e.index+s.length()-2).retain(1,n);a=a.compose(c)}}}this.quill.updateContents(a,B.sources.USER),this.quill.focus()}handleDelete(e,t){let r=/^[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(t.suffix)?2:1;if(e.index>=this.quill.getLength()-r)return;let n={},[s]=this.quill.getLine(e.index),a=new _t.default().retain(e.index).delete(r);if(t.offset>=s.length()-1){let[o]=this.quill.getLine(e.index+1);if(o){let l=s.formats(),h=this.quill.getFormat(e.index,1);n=_t.AttributeMap.diff(l,h)||{},Object.keys(n).length>0&&(a=a.retain(o.length()-1).retain(1,n))}}this.quill.updateContents(a,B.sources.USER),this.quill.focus()}handleDeleteRange(e){Yl({range:e,quill:this.quill}),this.quill.focus()}handleEnter(e,t){let r=Object.keys(t.format).reduce((s,a)=>(this.quill.scroll.query(a,X.BLOCK)&&!Array.isArray(t.format[a])&&(s[a]=t.format[a]),s),{}),n=new _t.default().retain(e.index).delete(e.length).insert(` +`,r);this.quill.updateContents(n,B.sources.USER),this.quill.setSelection(e.index+1,B.sources.SILENT),this.quill.focus()}},RB={bindings:{bold:tp("bold"),italic:tp("italic"),underline:tp("underline"),indent:{key:"Tab",format:["blockquote","indent","list"],handler(i,e){return e.collapsed&&e.offset!==0?!0:(this.quill.format("indent","+1",B.sources.USER),!1)}},outdent:{key:"Tab",shiftKey:!0,format:["blockquote","indent","list"],handler(i,e){return e.collapsed&&e.offset!==0?!0:(this.quill.format("indent","-1",B.sources.USER),!1)}},"outdent backspace":{key:"Backspace",collapsed:!0,shiftKey:null,metaKey:null,ctrlKey:null,altKey:null,format:["indent","list"],offset:0,handler(i,e){e.format.indent!=null?this.quill.format("indent","-1",B.sources.USER):e.format.list!=null&&this.quill.format("list",!1,B.sources.USER)}},"indent code-block":Ey(!0),"outdent code-block":Ey(!1),"remove tab":{key:"Tab",shiftKey:!0,collapsed:!0,prefix:/\t$/,handler(i){this.quill.deleteText(i.index-1,1,B.sources.USER)}},tab:{key:"Tab",handler(i,e){if(e.format.table)return!0;this.quill.history.cutoff();let t=new _t.default().retain(i.index).delete(i.length).insert(" ");return this.quill.updateContents(t,B.sources.USER),this.quill.history.cutoff(),this.quill.setSelection(i.index+1,B.sources.SILENT),!1}},"blockquote empty enter":{key:"Enter",collapsed:!0,format:["blockquote"],empty:!0,handler(){this.quill.format("blockquote",!1,B.sources.USER)}},"list empty enter":{key:"Enter",collapsed:!0,format:["list"],empty:!0,handler(i,e){let t={list:!1};e.format.indent&&(t.indent=!1),this.quill.formatLine(i.index,i.length,t,B.sources.USER)}},"checklist enter":{key:"Enter",collapsed:!0,format:{list:"checked"},handler(i){let[e,t]=this.quill.getLine(i.index),r={...e.formats(),list:"checked"},n=new _t.default().retain(i.index).insert(` +`,r).retain(e.length()-t-1).retain(1,{list:"unchecked"});this.quill.updateContents(n,B.sources.USER),this.quill.setSelection(i.index+1,B.sources.SILENT),this.quill.scrollSelectionIntoView()}},"header enter":{key:"Enter",collapsed:!0,format:["header"],suffix:/^$/,handler(i,e){let[t,r]=this.quill.getLine(i.index),n=new _t.default().retain(i.index).insert(` +`,e.format).retain(t.length()-r-1).retain(1,{header:null});this.quill.updateContents(n,B.sources.USER),this.quill.setSelection(i.index+1,B.sources.SILENT),this.quill.scrollSelectionIntoView()}},"table backspace":{key:"Backspace",format:["table"],collapsed:!0,offset:0,handler(){}},"table delete":{key:"Delete",format:["table"],collapsed:!0,suffix:/^$/,handler(){}},"table enter":{key:"Enter",shiftKey:null,format:["table"],handler(i){let e=this.quill.getModule("table");if(e){let[t,r,n,s]=e.getTable(i),a=OB(t,r,n,s);if(a==null)return;let o=t.offset();if(a<0){let l=new _t.default().retain(o).insert(` +`);this.quill.updateContents(l,B.sources.USER),this.quill.setSelection(i.index+1,i.length,B.sources.SILENT)}else if(a>0){o+=t.length();let l=new _t.default().retain(o).insert(` +`);this.quill.updateContents(l,B.sources.USER),this.quill.setSelection(o,B.sources.USER)}}}},"table tab":{key:"Tab",shiftKey:null,format:["table"],handler(i,e){let{event:t,line:r}=e,n=r.offset(this.quill.scroll);t.shiftKey?this.quill.setSelection(n-1,B.sources.USER):this.quill.setSelection(n+r.length(),B.sources.USER)}},"list autofill":{key:" ",shiftKey:null,collapsed:!0,format:{"code-block":!1,blockquote:!1,table:!1},prefix:/^\s*?(\d+\.|-|\*|\[ ?\]|\[x\])$/,handler(i,e){if(this.quill.scroll.query("list")==null)return!0;let{length:t}=e.prefix,[r,n]=this.quill.getLine(i.index);if(n>t)return!0;let s;switch(e.prefix.trim()){case"[]":case"[ ]":s="unchecked";break;case"[x]":s="checked";break;case"-":case"*":s="bullet";break;default:s="ordered"}this.quill.insertText(i.index," ",B.sources.USER),this.quill.history.cutoff();let a=new _t.default().retain(i.index-n).delete(t+1).retain(r.length()-2-n).retain(1,{list:s});return this.quill.updateContents(a,B.sources.USER),this.quill.history.cutoff(),this.quill.setSelection(i.index-t,B.sources.SILENT),!1}},"code exit":{key:"Enter",collapsed:!0,format:["code-block"],prefix:/^$/,suffix:/^\s*$/,handler(i){let[e,t]=this.quill.getLine(i.index),r=2,n=e;for(;n!=null&&n.length()<=1&&n.formats()["code-block"];)if(n=n.prev,r-=1,r<=0){let s=new _t.default().retain(i.index+e.length()-t-2).retain(1,{"code-block":null}).delete(1);return this.quill.updateContents(s,B.sources.USER),this.quill.setSelection(i.index-1,B.sources.SILENT),!1}return!0}},"embed left":cc("ArrowLeft",!1),"embed left shift":cc("ArrowLeft",!0),"embed right":cc("ArrowRight",!1),"embed right shift":cc("ArrowRight",!0),"table down":Sy(!1),"table up":Sy(!0)}};Vl.DEFAULTS=RB});function ip(i){i.querySelector('[id^="docs-internal-guid-"]')&&(qB(i),FB(i))}var BB,PB,Ay,FB,qB,ky=T(()=>{BB=/font-weight:\s*normal/,PB=["P","OL","UL"],Ay=i=>i&&PB.includes(i.tagName),FB=i=>{Array.from(i.querySelectorAll("br")).filter(e=>Ay(e.previousElementSibling)&&Ay(e.nextElementSibling)).forEach(e=>{e.parentNode?.removeChild(e)})},qB=i=>{Array.from(i.querySelectorAll('b[style*="font-weight"]')).filter(e=>e.getAttribute("style")?.match(BB)).forEach(e=>{let t=i.createDocumentFragment();t.append(...e.childNodes),e.parentNode?.replaceChild(t,e)})}});function rp(i){i.documentElement.getAttribute("xmlns:w")==="urn:schemas-microsoft-com:office:word"&&GB(i)}var HB,UB,$B,jB,GB,Cy=T(()=>{HB=/\bmso-list:[^;]*ignore/i,UB=/\bmso-list:[^;]*\bl(\d+)/i,$B=/\bmso-list:[^;]*\blevel(\d+)/i,jB=(i,e)=>{let t=i.getAttribute("style"),r=t?.match(UB);if(!r)return null;let n=Number(r[1]),s=t?.match($B),a=s?Number(s[1]):1,o=new RegExp(`@list l${n}:level${a}\\s*\\{[^\\}]*mso-level-number-format:\\s*([\\w-]+)`,"i"),l=e.match(o),h=l&&l[1]==="bullet"?"bullet":"ordered";return{id:n,indent:a,type:h,element:i}},GB=i=>{let e=Array.from(i.querySelectorAll("[style*=mso-list]")),t=[],r=[];e.forEach(a=>{(a.getAttribute("style")||"").match(HB)?t.push(a):r.push(a)}),t.forEach(a=>a.parentNode?.removeChild(a));let n=i.documentElement.innerHTML,s=r.map(a=>jB(a,n)).filter(a=>a);for(;s.length;){let a=[],o=s.shift();for(;o;)a.push(o),o=s.length&&s[0]?.element===o.element.nextElementSibling&&s[0].id===o.id?s.shift():null;let l=document.createElement("ul");a.forEach(c=>{let f=document.createElement("li");f.setAttribute("data-list",c.type),c.indent>1&&f.setAttribute("class",`ql-indent-${c.indent-1}`),f.innerHTML=c.element.innerHTML,l.appendChild(f)});let h=a[0]?.element,{parentNode:d}=h??{};h&&d?.replaceChild(l,h),a.slice(1).forEach(c=>{let{element:f}=c;d?.removeChild(f)})}}});var WB,VB,_y,Ly=T(()=>{ky();Cy();WB=[rp,ip],VB=i=>{i.documentElement&&WB.forEach(e=>{e(i)})},_y=VB});function _s(i,e,t,r){return r.query(e)?i.reduce((n,s)=>{if(!s.insert)return n;if(s.attributes&&s.attributes[e])return n.push(s);let a=t?{[e]:t}:{};return n.insert(s.insert,{...a,...s.attributes})},new di.default):i}function Kl(i,e){let t="";for(let r=i.ops.length-1;r>=0&&t.lengtha(e,s,i),new di.default):e.nodeType===e.ELEMENT_NODE?Array.from(e.childNodes||[]).reduce((s,a)=>{let o=mc(i,a,t,r,n);return a.nodeType===e.ELEMENT_NODE&&(o=t.reduce((l,h)=>h(a,l,i),o),o=(n.get(a)||[]).reduce((l,h)=>h(a,l,i),o)),s.concat(o)},new di.default):new di.default}function np(i){return(e,t,r)=>_s(t,i,!0,r)}function QB(i,e,t){let r=qt.keys(i),n=dt.keys(i),s=oi.keys(i),a={};return r.concat(n).concat(s).forEach(o=>{let l=t.query(o,X.ATTRIBUTE);l!=null&&(a[l.attrName]=l.value(i),a[l.attrName])||(l=KB[o],l!=null&&(l.attrName===o||l.keyName===o)&&(a[l.attrName]=l.value(i)||void 0),l=Iy[o],l!=null&&(l.attrName===o||l.keyName===o)&&(l=Iy[o],a[l.attrName]=l.value(i)||void 0))}),Object.entries(a).reduce((o,l)=>{let[h,d]=l;return _s(o,h,d,t)},e)}function JB(i,e,t){let r=t.query(i);if(r==null)return e;if(r.prototype instanceof Ze){let n={},s=r.value(i);if(s!=null)return n[r.blotName]=s,new di.default().insert(n,r.formats(i,t))}else if(r.prototype instanceof vs&&!Kl(e,` `)&&e.insert(` -`),"blotName"in r&&"formats"in r&&typeof r.formats=="function")return _s(e,r.blotName,r.formats(i,t),t);return e}function _B(i,e){return jl(e,` +`),"blotName"in r&&"formats"in r&&typeof r.formats=="function")return _s(e,r.blotName,r.formats(i,t),t);return e}function eP(i,e){return Kl(e,` `)||e.insert(` -`),e}function LB(i,e,t){let r=t.query("code-block"),n=r&&"formats"in r&&typeof r.formats=="function"?r.formats(i,t):!0;return _s(e,"code-block",n,t)}function IB(){return new li.default}function zB(i,e,t){let r=t.query(i);if(r==null||r.blotName!=="list"||!jl(e,` -`))return e;let n=-1,s=i.parentNode;for(;s!=null;)["OL","UL"].includes(s.tagName)&&(n+=1),s=s.parentNode;return n<=0?e:e.reduce((a,o)=>o.insert?o.attributes&&typeof o.attributes.indent=="number"?a.push(o):a.insert(o.insert,{indent:n,...o.attributes||{}}):a,new li.default)}function RB(i,e,t){let r=i,n=r.tagName==="OL"?"ordered":"bullet",s=r.getAttribute("data-checked");return s&&(n=s==="true"?"checked":"unchecked"),_s(e,"list",n,t)}function vy(i,e,t){if(!jl(e,` -`)){if(Fn(i,t)&&(i.childNodes.length>0||i instanceof HTMLParagraphElement))return e.insert(` -`);if(e.length()>0&&i.nextSibling){let r=i.nextSibling;for(;r!=null;){if(Fn(r,t))return e.insert(` -`);let n=t.query(r);if(n&&n.prototype instanceof ft)return e.insert(` -`);r=r.firstChild}}}return e}function DB(i,e,t){let r={},n=i.style||{};return n.fontStyle==="italic"&&(r.italic=!0),n.textDecoration==="underline"&&(r.underline=!0),n.textDecoration==="line-through"&&(r.strike=!0),(n.fontWeight?.startsWith("bold")||parseInt(n.fontWeight,10)>=700)&&(r.bold=!0),e=Object.entries(r).reduce((s,a)=>{let[o,l]=a;return _s(s,o,l,t)},e),parseFloat(n.textIndent||0)>0?new li.default().insert(" ").concat(e):e}function OB(i,e,t){let r=i.parentElement?.tagName==="TABLE"?i.parentElement:i.parentElement?.parentElement;if(r!=null){let s=Array.from(r.querySelectorAll("tr")).indexOf(i)+1;return _s(e,"table",s,t)}return e}function BB(i,e,t){let r=i.data;if(i.parentElement?.tagName==="O:P")return e.insert(r.trim());if(!by(i)){if(r.trim().length===0&&r.includes(` -`)&&!AB(i,t))return e;r=r.replace(/[^\S\u00a0]/g," "),r=r.replace(/ {2,}/g," "),(i.previousSibling==null&&i.parentElement!=null&&Fn(i.parentElement,t)||i.previousSibling instanceof Element&&Fn(i.previousSibling,t))&&(r=r.replace(/^ /,"")),(i.nextSibling==null&&i.parentElement!=null&&Fn(i.parentElement,t)||i.nextSibling instanceof Element&&Fn(i.nextSibling,t))&&(r=r.replace(/ $/,"")),r=r.replaceAll("\xA0"," ")}return e.insert(r)}var li,NB,EB,SB,yy,$l,sc,Ym=T(()=>{Re();li=pt(pi(),1);zi();Cs();Di();oi();Rm();Dm();Qd();Zd();Pm();Hm();$m();nc();xy();NB=gi("quill:clipboard"),EB=[[Node.TEXT_NODE,BB],[Node.TEXT_NODE,vy],["br",_B],[Node.ELEMENT_NODE,vy],[Node.ELEMENT_NODE,CB],[Node.ELEMENT_NODE,kB],[Node.ELEMENT_NODE,DB],["li",zB],["ol, ul",RB],["pre",LB],["tr",OB],["b",Wm("bold")],["i",Wm("italic")],["strike",Wm("strike")],["style",IB]],SB=[oy,Jd].reduce((i,e)=>(i[e.keyName]=e,i),{}),yy=[Kd,ql,Fl,ec,tc,ic].reduce((i,e)=>(i[e.keyName]=e,i),{}),$l=class extends Qe{constructor(e,t){super(e,t),this.quill.root.addEventListener("copy",r=>this.onCaptureCopy(r,!1)),this.quill.root.addEventListener("cut",r=>this.onCaptureCopy(r,!0)),this.quill.root.addEventListener("paste",this.onCapturePaste.bind(this)),this.matchers=[],EB.concat(this.options.matchers??[]).forEach(r=>{let[n,s]=r;this.addMatcher(n,s)})}addMatcher(e,t){this.matchers.push([e,t])}convert(e){let{html:t,text:r}=e,n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(n[st.blotName])return new li.default().insert(r||"",{[st.blotName]:n[st.blotName]});if(!t)return new li.default().insert(r||"",n);let s=this.convertHTML(t);return jl(s,` -`)&&(s.ops[s.ops.length-1].attributes==null||n.table)?s.compose(new li.default().retain(s.length()-1).delete(1)):s}normalizeHTML(e){gy(e)}convertHTML(e){let t=new DOMParser().parseFromString(e,"text/html");this.normalizeHTML(t);let r=t.body,n=new WeakMap,[s,a]=this.prepareMatching(r,n);return ac(this.quill.scroll,r,s,a,n)}dangerouslyPasteHTML(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:B.sources.API;if(typeof e=="string"){let n=this.convert({html:e,text:""});this.quill.setContents(n,t),this.quill.setSelection(0,B.sources.SILENT)}else{let n=this.convert({html:t,text:""});this.quill.updateContents(new li.default().retain(e).concat(n),r),this.quill.setSelection(e+n.length(),B.sources.SILENT)}}onCaptureCopy(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;if(e.defaultPrevented)return;e.preventDefault();let[r]=this.quill.selection.getRange();if(r==null)return;let{html:n,text:s}=this.onCopy(r,t);e.clipboardData?.setData("text/plain",s),e.clipboardData?.setData("text/html",n),t&&Ul({range:r,quill:this.quill})}normalizeURIList(e){return e.split(/\r?\n/).filter(t=>t[0]!=="#").join(` -`)}onCapturePaste(e){if(e.defaultPrevented||!this.quill.isEnabled())return;e.preventDefault();let t=this.quill.getSelection(!0);if(t==null)return;let r=e.clipboardData?.getData("text/html"),n=e.clipboardData?.getData("text/plain");if(!r&&!n){let a=e.clipboardData?.getData("text/uri-list");a&&(n=this.normalizeURIList(a))}let s=Array.from(e.clipboardData?.files||[]);if(!r&&s.length>0){this.quill.uploader.upload(t,s);return}if(r&&s.length>0){let a=new DOMParser().parseFromString(r,"text/html");if(a.body.childElementCount===1&&a.body.firstElementChild?.tagName==="IMG"){this.quill.uploader.upload(t,s);return}}this.onPaste(t,{html:r,text:n})}onCopy(e){let t=this.quill.getText(e);return{html:this.quill.getSemanticHTML(e),text:t}}onPaste(e,t){let{text:r,html:n}=t,s=this.quill.getFormat(e.index),a=this.convert({text:r,html:n},s);NB.log("onPaste",a,{text:r,html:n});let o=new li.default().retain(e.index).delete(e.length).concat(a);this.quill.updateContents(o,B.sources.USER),this.quill.setSelection(o.length()-e.length,B.sources.SILENT),this.quill.scrollSelectionIntoView()}prepareMatching(e,t){let r=[],n=[];return this.matchers.forEach(s=>{let[a,o]=s;switch(a){case Node.TEXT_NODE:n.push(o);break;case Node.ELEMENT_NODE:r.push(o);break;default:Array.from(e.querySelectorAll(a)).forEach(l=>{t.has(l)?t.get(l)?.push(o):t.set(l,[o])});break}}),[r,n]}};U($l,"DEFAULTS",{matchers:[]});sc=new WeakMap});function wy(i,e){let t=e;for(let r=i.length-1;r>=0;r-=1){let n=i[r];i[r]={delta:t.transform(n.delta,!0),range:n.range&&Xm(n.range,t)},t=n.delta.transform(t),i[r].delta.length()===0&&i.splice(r,1)}}function PB(i,e){let t=e.ops[e.ops.length-1];return t==null?!1:t.insert!=null?typeof t.insert=="string"&&t.insert.endsWith(` -`):t.attributes!=null?Object.keys(t.attributes).some(r=>i.query(r,X.BLOCK)!=null):!1}function FB(i,e){let t=e.reduce((n,s)=>n+(s.delete||0),0),r=e.length()-t;return PB(i,e)&&(r-=1),r}function Xm(i,e){if(!i)return i;let t=e.transformPosition(i.index),r=e.transformPosition(i.index+i.length);return{index:t,length:r-t}}var Gl,My=T(()=>{Re();Di();oi();Gl=class extends Qe{constructor(t,r){super(t,r);U(this,"lastRecorded",0);U(this,"ignoreChange",!1);U(this,"stack",{undo:[],redo:[]});U(this,"currentRange",null);this.quill.on(B.events.EDITOR_CHANGE,(n,s,a,o)=>{n===B.events.SELECTION_CHANGE?s&&o!==B.sources.SILENT&&(this.currentRange=s):n===B.events.TEXT_CHANGE&&(this.ignoreChange||(!this.options.userOnly||o===B.sources.USER?this.record(s,a):this.transform(s)),this.currentRange=Xm(this.currentRange,s))}),this.quill.keyboard.addBinding({key:"z",shortKey:!0},this.undo.bind(this)),this.quill.keyboard.addBinding({key:["z","Z"],shortKey:!0,shiftKey:!0},this.redo.bind(this)),/Win/i.test(navigator.platform)&&this.quill.keyboard.addBinding({key:"y",shortKey:!0},this.redo.bind(this)),this.quill.root.addEventListener("beforeinput",n=>{n.inputType==="historyUndo"?(this.undo(),n.preventDefault()):n.inputType==="historyRedo"&&(this.redo(),n.preventDefault())})}change(t,r){if(this.stack[t].length===0)return;let n=this.stack[t].pop();if(!n)return;let s=this.quill.getContents(),a=n.delta.invert(s);this.stack[r].push({delta:a,range:Xm(n.range,a)}),this.lastRecorded=0,this.ignoreChange=!0,this.quill.updateContents(n.delta,B.sources.USER),this.ignoreChange=!1,this.restoreSelection(n)}clear(){this.stack={undo:[],redo:[]}}cutoff(){this.lastRecorded=0}record(t,r){if(t.ops.length===0)return;this.stack.redo=[];let n=t.invert(r),s=this.currentRange,a=Date.now();if(this.lastRecorded+this.options.delay>a&&this.stack.undo.length>0){let o=this.stack.undo.pop();o&&(n=n.compose(o.delta),s=o.range)}else this.lastRecorded=a;n.length()!==0&&(this.stack.undo.push({delta:n,range:s}),this.stack.undo.length>this.options.maxStack&&this.stack.undo.shift())}redo(){this.change("redo","undo")}transform(t){wy(this.stack.undo,t),wy(this.stack.redo,t)}undo(){this.change("undo","redo")}restoreSelection(t){if(t.range)this.quill.setSelection(t.range,B.sources.USER);else{let r=FB(this.quill.scroll,t.delta);this.quill.setSelection(r,B.sources.USER)}}};U(Gl,"DEFAULTS",{delay:1e3,maxStack:100,userOnly:!1})});var Ty,oc,Ny,Ey=T(()=>{Ty=pt(pi(),1);Qr();Di();oc=class extends Qe{constructor(e,t){super(e,t),e.root.addEventListener("drop",r=>{r.preventDefault();let n=null;if(document.caretRangeFromPoint)n=document.caretRangeFromPoint(r.clientX,r.clientY);else if(document.caretPositionFromPoint){let a=document.caretPositionFromPoint(r.clientX,r.clientY);n=document.createRange(),n.setStart(a.offsetNode,a.offset),n.setEnd(a.offsetNode,a.offset)}let s=n&&e.selection.normalizeNative(n);if(s){let a=e.selection.normalizedToRange(s);r.dataTransfer?.files&&this.upload(a,r.dataTransfer.files)}})}upload(e,t){let r=[];Array.from(t).forEach(n=>{n&&this.options.mimetypes?.includes(n.type)&&r.push(n)}),r.length>0&&this.options.handler.call(this,e,r)}};oc.DEFAULTS={mimetypes:["image/png","image/jpeg"],handler(i,e){if(!this.quill.scroll.query("image"))return;let t=e.map(r=>new Promise(n=>{let s=new FileReader;s.onload=()=>{n(s.result)},s.readAsDataURL(r)}));Promise.all(t).then(r=>{let n=r.reduce((s,a)=>s.insert({image:a}),new Ty.default().retain(i.index).delete(i.length));this.quill.updateContents(n,J.sources.USER),this.quill.setSelection(i.index+r.length,J.sources.SILENT)})}};Ny=oc});function HB(i){return typeof i.data=="string"?i.data:i.dataTransfer?.types.includes("text/plain")?i.dataTransfer.getData("text/plain"):null}var Sy,qB,Km,Ay,ky=T(()=>{Sy=pt(pi(),1);Di();oi();nc();qB=["insertText","insertReplacementText"],Km=class extends Qe{constructor(e,t){super(e,t),e.root.addEventListener("beforeinput",r=>{this.handleBeforeInput(r)}),/Android/i.test(navigator.userAgent)||e.on(B.events.COMPOSITION_BEFORE_START,()=>{this.handleCompositionStart()})}deleteRange(e){Ul({range:e,quill:this.quill})}replaceText(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"";if(e.length===0)return!1;if(t){let r=this.quill.getFormat(e.index,1);this.deleteRange(e),this.quill.updateContents(new Sy.default().retain(e.index).insert(t,r),B.sources.USER)}else this.deleteRange(e);return this.quill.setSelection(e.index+t.length,0,B.sources.SILENT),!0}handleBeforeInput(e){if(this.quill.composition.isComposing||e.defaultPrevented||!qB.includes(e.inputType))return;let t=e.getTargetRanges?e.getTargetRanges()[0]:null;if(!t||t.collapsed===!0)return;let r=HB(e);if(r==null)return;let n=this.quill.selection.normalizeNative(t),s=n?this.quill.selection.normalizedToRange(n):null;s&&this.replaceText(s,r)&&e.preventDefault()}handleCompositionStart(){let e=this.quill.getSelection();e&&this.replaceText(e)}};Ay=Km});var UB,$B,jB,Zm,Cy,_y=T(()=>{Re();Di();oi();UB=/Mac/i.test(navigator.platform),$B=100,jB=i=>!!(i.key==="ArrowLeft"||i.key==="ArrowRight"||i.key==="ArrowUp"||i.key==="ArrowDown"||i.key==="Home"||UB&&i.key==="a"&&i.ctrlKey===!0),Zm=class extends Qe{constructor(t,r){super(t,r);U(this,"isListening",!1);U(this,"selectionChangeDeadline",0);this.handleArrowKeys(),this.handleNavigationShortcuts()}handleArrowKeys(){this.quill.keyboard.addBinding({key:["ArrowLeft","ArrowRight"],offset:0,shiftKey:null,handler(t,r){let{line:n,event:s}=r;if(!(n instanceof ni)||!n.uiNode)return!0;let a=getComputedStyle(n.domNode).direction==="rtl";return a&&s.key!=="ArrowRight"||!a&&s.key!=="ArrowLeft"?!0:(this.quill.setSelection(t.index-1,t.length+(s.shiftKey?1:0),B.sources.USER),!1)}})}handleNavigationShortcuts(){this.quill.root.addEventListener("keydown",t=>{!t.defaultPrevented&&jB(t)&&this.ensureListeningToSelectionChange()})}ensureListeningToSelectionChange(){if(this.selectionChangeDeadline=Date.now()+$B,this.isListening)return;this.isListening=!0;let t=()=>{this.isListening=!1,Date.now()<=this.selectionChangeDeadline&&this.handleSelectionChange()};document.addEventListener("selectionchange",t,{once:!0})}handleSelectionChange(){let t=document.getSelection();if(!t)return;let r=t.getRangeAt(0);if(r.collapsed!==!0||r.startOffset!==0)return;let n=this.quill.scroll.find(r.startContainer);if(!(n instanceof ni)||!n.uiNode)return;let s=document.createRange();s.setStartAfter(n.uiNode),s.setEndAfter(n.uiNode),t.removeAllRanges(),t.addRange(s)}},Cy=Zm});var qn,lc,Qm=T(()=>{oi();zi();Dn();Ja();Ll();Wd();Zr();ay();Kr();Ym();My();nc();Ey();qn=pt(pi(),1);ky();_y();Di();B.register({"blots/block":De,"blots/block/embed":ft,"blots/break":Et,"blots/container":Oi,"blots/cursor":On,"blots/embed":Ka,"blots/inline":St,"blots/scroll":sy,"blots/text":nt,"modules/clipboard":$l,"modules/history":Gl,"modules/keyboard":Hl,"modules/uploader":Ny,"modules/input":Ay,"modules/uiNode":Cy});lc=B});var Jm,GB,Ly,Iy=T(()=>{Re();Jm=class extends ht{add(e,t){let r=0;if(t==="+1"||t==="-1"){let n=this.value(e)||0;r=t==="+1"?n+1:n-1}else typeof t=="number"&&(r=t);return r===0?(this.remove(e),!0):super.add(e,r.toString())}canAdd(e,t){return super.canAdd(e,t)||super.canAdd(e,parseInt(t,10))}value(e){return parseInt(super.value(e),10)||void 0}},GB=new Jm("indent","ql-indent",{scope:X.BLOCK,whitelist:[1,2,3,4,5,6,7,8]}),Ly=GB});var Vl,zy,Ry=T(()=>{zi();Vl=class extends De{};U(Vl,"blotName","blockquote"),U(Vl,"tagName","blockquote");zy=Vl});var Wl,Dy,Oy=T(()=>{zi();Wl=class extends De{static formats(e){return this.tagName.indexOf(e.tagName)+1}};U(Wl,"blotName","header"),U(Wl,"tagName",["H1","H2","H3","H4","H5","H6"]);Dy=Wl});var Ls,Hn,By=T(()=>{zi();Ja();oi();Ls=class extends Oi{};Ls.blotName="list-container";Ls.tagName="OL";Hn=class extends De{static create(e){let t=super.create();return t.setAttribute("data-list",e),t}static formats(e){return e.getAttribute("data-list")||void 0}static register(){B.register(Ls)}constructor(e,t){super(e,t);let r=t.ownerDocument.createElement("span"),n=s=>{if(!e.isEnabled())return;let a=this.statics.formats(t,e);a==="checked"?(this.format("list","unchecked"),s.preventDefault()):a==="unchecked"&&(this.format("list","checked"),s.preventDefault())};r.addEventListener("mousedown",n),r.addEventListener("touchstart",n),this.attachUI(r)}format(e,t){e===this.statics.blotName&&t?this.domNode.setAttribute("data-list",t):super.format(e,t)}};Hn.blotName="list";Hn.tagName="LI";Ls.allowedChildren=[Hn];Hn.requiredContainer=Ls});var Yl,to,hc=T(()=>{Zr();Yl=class extends St{static create(){return super.create()}static formats(){return!0}optimize(e){super.optimize(e),this.domNode.tagName!==this.statics.tagName[0]&&this.replaceWith(this.statics.blotName)}};U(Yl,"blotName","bold"),U(Yl,"tagName",["STRONG","B"]);to=Yl});var Xl,Py,Fy=T(()=>{hc();Xl=class extends to{};U(Xl,"blotName","italic"),U(Xl,"tagName",["EM","I"]);Py=Xl});function ep(i,e){let t=document.createElement("a");t.href=i;let r=t.href.slice(0,t.href.indexOf(":"));return e.indexOf(r)>-1}var xi,Kl=T(()=>{Zr();xi=class extends St{static create(e){let t=super.create(e);return t.setAttribute("href",this.sanitize(e)),t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank"),t}static formats(e){return e.getAttribute("href")}static sanitize(e){return ep(e,this.PROTOCOL_WHITELIST)?e:this.SANITIZED_URL}format(e,t){e!==this.statics.blotName||!t?super.format(e,t):this.domNode.setAttribute("href",this.constructor.sanitize(t))}};U(xi,"blotName","link"),U(xi,"tagName","A"),U(xi,"SANITIZED_URL","about:blank"),U(xi,"PROTOCOL_WHITELIST",["http","https","mailto","tel","sms"])});var Zl,qy,Hy=T(()=>{Zr();Zl=class extends St{static create(e){return e==="super"?document.createElement("sup"):e==="sub"?document.createElement("sub"):super.create(e)}static formats(e){if(e.tagName==="SUB")return"sub";if(e.tagName==="SUP")return"super"}};U(Zl,"blotName","script"),U(Zl,"tagName",["SUB","SUP"]);qy=Zl});var Ql,Uy,$y=T(()=>{hc();Ql=class extends to{};U(Ql,"blotName","strike"),U(Ql,"tagName",["S","STRIKE"]);Uy=Ql});var Jl,jy,Gy=T(()=>{Zr();Jl=class extends St{};U(Jl,"blotName","underline"),U(Jl,"tagName","U");jy=Jl});var io,Vy,Wy=T(()=>{Wd();io=class extends Ka{static create(e){if(window.katex==null)throw new Error("Formula module requires KaTeX.");let t=super.create(e);return typeof e=="string"&&(window.katex.render(e,t,{throwOnError:!1,errorColor:"#f00"}),t.setAttribute("data-value",e)),t}static value(e){return e.getAttribute("data-value")}html(){let{formula:e}=this.value();return`${e}`}};U(io,"blotName","formula"),U(io,"className","ql-formula"),U(io,"tagName","SPAN");Vy=io});var Yy,e0,Xy,Ky=T(()=>{Re();Kl();Yy=["alt","height","width"],e0=class extends Ke{static create(e){let t=super.create(e);return typeof e=="string"&&t.setAttribute("src",this.sanitize(e)),t}static formats(e){return Yy.reduce((t,r)=>(e.hasAttribute(r)&&(t[r]=e.getAttribute(r)),t),{})}static match(e){return/\.(jpe?g|gif|png)$/.test(e)||/^data:image\/.+;base64/.test(e)}static sanitize(e){return ep(e,["http","https","data"])?e:"//:0"}static value(e){return e.getAttribute("src")}format(e,t){Yy.indexOf(e)>-1?t?this.domNode.setAttribute(e,t):this.domNode.removeAttribute(e):super.format(e,t)}};U(e0,"blotName","image"),U(e0,"tagName","IMG");Xy=e0});var Zy,ro,Qy,Jy=T(()=>{zi();Kl();Zy=["height","width"],ro=class extends ft{static create(e){let t=super.create(e);return t.setAttribute("frameborder","0"),t.setAttribute("allowfullscreen","true"),t.setAttribute("src",this.sanitize(e)),t}static formats(e){return Zy.reduce((t,r)=>(e.hasAttribute(r)&&(t[r]=e.getAttribute(r)),t),{})}static sanitize(e){return xi.sanitize(e)}static value(e){return e.getAttribute("src")}format(e,t){Zy.indexOf(e)>-1?t?this.domNode.setAttribute(e,t):this.domNode.removeAttribute(e):super.format(e,t)}html(){let{video:e}=this.value();return`${e}`}};U(ro,"blotName","video"),U(ro,"className","ql-video"),U(ro,"tagName","IFRAME");Qy=ro});var dc,t0,en,hi,Is,VB,i0,ev=T(()=>{dc=pt(pi(),1);Re();Zr();oi();Di();zi();Dn();Ll();Kr();Qd();Ym();t0=new ht("code-token","hljs",{scope:X.INLINE}),en=class i extends St{static formats(e,t){for(;e!=null&&e!==t.domNode;){if(e.classList&&e.classList.contains(st.className))return super.formats(e,t);e=e.parentNode}}constructor(e,t,r){super(e,t,r),t0.add(this.domNode,r)}format(e,t){e!==i.blotName?super.format(e,t):t?t0.add(this.domNode,t):(t0.remove(this.domNode),this.domNode.classList.remove(this.statics.className))}optimize(){super.optimize(...arguments),t0.value(this.domNode)||this.unwrap()}};en.blotName="code-token";en.className="ql-token";hi=class extends st{static create(e){let t=super.create(e);return typeof e=="string"&&t.setAttribute("data-language",e),t}static formats(e){return e.getAttribute("data-language")||"plain"}static register(){}format(e,t){e===this.statics.blotName&&t?this.domNode.setAttribute("data-language",t):super.format(e,t)}replaceWith(e,t){return this.formatAt(0,this.length(),en.blotName,!1),super.replaceWith(e,t)}},Is=class extends wr{attach(){super.attach(),this.forceNext=!1,this.scroll.emitMount(this)}format(e,t){e===hi.blotName&&(this.forceNext=!0,this.children.forEach(r=>{r.format(e,t)}))}formatAt(e,t,r,n){r===hi.blotName&&(this.forceNext=!0),super.formatAt(e,t,r,n)}highlight(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;if(this.children.head==null)return;let n=`${Array.from(this.domNode.childNodes).filter(a=>a!==this.uiNode).map(a=>a.textContent).join(` +`),e}function tP(i,e,t){let r=t.query("code-block"),n=r&&"formats"in r&&typeof r.formats=="function"?r.formats(i,t):!0;return _s(e,"code-block",n,t)}function iP(){return new di.default}function rP(i,e,t){let r=t.query(i);if(r==null||r.blotName!=="list"||!Kl(e,` +`))return e;let n=-1,s=i.parentNode;for(;s!=null;)["OL","UL"].includes(s.tagName)&&(n+=1),s=s.parentNode;return n<=0?e:e.reduce((a,o)=>o.insert?o.attributes&&typeof o.attributes.indent=="number"?a.push(o):a.insert(o.insert,{indent:n,...o.attributes||{}}):a,new di.default)}function nP(i,e,t){let r=i,n=r.tagName==="OL"?"ordered":"bullet",s=r.getAttribute("data-checked");return s&&(n=s==="true"?"checked":"unchecked"),_s(e,"list",n,t)}function zy(i,e,t){if(!Kl(e,` +`)){if(Pn(i,t)&&(i.childNodes.length>0||i instanceof HTMLParagraphElement))return e.insert(` +`);if(e.length()>0&&i.nextSibling){let r=i.nextSibling;for(;r!=null;){if(Pn(r,t))return e.insert(` +`);let n=t.query(r);if(n&&n.prototype instanceof gt)return e.insert(` +`);r=r.firstChild}}}return e}function sP(i,e,t){let r={},n=i.style||{};return n.fontStyle==="italic"&&(r.italic=!0),n.textDecoration==="underline"&&(r.underline=!0),n.textDecoration==="line-through"&&(r.strike=!0),(n.fontWeight?.startsWith("bold")||parseInt(n.fontWeight,10)>=700)&&(r.bold=!0),e=Object.entries(r).reduce((s,a)=>{let[o,l]=a;return _s(s,o,l,t)},e),parseFloat(n.textIndent||0)>0?new di.default().insert(" ").concat(e):e}function aP(i,e,t){let r=i.parentElement?.tagName==="TABLE"?i.parentElement:i.parentElement?.parentElement;if(r!=null){let s=Array.from(r.querySelectorAll("tr")).indexOf(i)+1;return _s(e,"table",s,t)}return e}function oP(i,e,t){let r=i.data;if(i.parentElement?.tagName==="O:P")return e.insert(r.trim());if(!Ry(i)){if(r.trim().length===0&&r.includes(` +`)&&!ZB(i,t))return e;r=r.replace(/[^\S\u00a0]/g," "),r=r.replace(/ {2,}/g," "),(i.previousSibling==null&&i.parentElement!=null&&Pn(i.parentElement,t)||i.previousSibling instanceof Element&&Pn(i.previousSibling,t))&&(r=r.replace(/^ /,"")),(i.nextSibling==null&&i.parentElement!=null&&Pn(i.parentElement,t)||i.nextSibling instanceof Element&&Pn(i.nextSibling,t))&&(r=r.replace(/ $/,"")),r=r.replaceAll("\xA0"," ")}return e.insert(r)}var di,YB,XB,KB,Iy,Xl,fc,sp=T(()=>{De();di=yt(xi(),1);Ri();Cs();Oi();hi();Gm();Wm();ac();sc();Xm();Qm();ep();uc();Ly();YB=yi("quill:clipboard"),XB=[[Node.TEXT_NODE,oP],[Node.TEXT_NODE,zy],["br",eP],[Node.ELEMENT_NODE,zy],[Node.ELEMENT_NODE,JB],[Node.ELEMENT_NODE,QB],[Node.ELEMENT_NODE,sP],["li",rP],["ol, ul",nP],["pre",tP],["tr",aP],["b",np("bold")],["i",np("italic")],["strike",np("strike")],["style",iP]],KB=[wy,oc].reduce((i,e)=>(i[e.keyName]=e,i),{}),Iy=[nc,Wl,Gl,lc,hc,dc].reduce((i,e)=>(i[e.keyName]=e,i),{}),Xl=class extends Je{constructor(e,t){super(e,t),this.quill.root.addEventListener("copy",r=>this.onCaptureCopy(r,!1)),this.quill.root.addEventListener("cut",r=>this.onCaptureCopy(r,!0)),this.quill.root.addEventListener("paste",this.onCapturePaste.bind(this)),this.matchers=[],XB.concat(this.options.matchers??[]).forEach(r=>{let[n,s]=r;this.addMatcher(n,s)})}addMatcher(e,t){this.matchers.push([e,t])}convert(e){let{html:t,text:r}=e,n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(n[st.blotName])return new di.default().insert(r||"",{[st.blotName]:n[st.blotName]});if(!t)return new di.default().insert(r||"",n);let s=this.convertHTML(t);return Kl(s,` +`)&&(s.ops[s.ops.length-1].attributes==null||n.table)?s.compose(new di.default().retain(s.length()-1).delete(1)):s}normalizeHTML(e){_y(e)}convertHTML(e){let t=new DOMParser().parseFromString(e,"text/html");this.normalizeHTML(t);let r=t.body,n=new WeakMap,[s,a]=this.prepareMatching(r,n);return mc(this.quill.scroll,r,s,a,n)}dangerouslyPasteHTML(e,t){let r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:B.sources.API;if(typeof e=="string"){let n=this.convert({html:e,text:""});this.quill.setContents(n,t),this.quill.setSelection(0,B.sources.SILENT)}else{let n=this.convert({html:t,text:""});this.quill.updateContents(new di.default().retain(e).concat(n),r),this.quill.setSelection(e+n.length(),B.sources.SILENT)}}onCaptureCopy(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;if(e.defaultPrevented)return;e.preventDefault();let[r]=this.quill.selection.getRange();if(r==null)return;let{html:n,text:s}=this.onCopy(r,t);e.clipboardData?.setData("text/plain",s),e.clipboardData?.setData("text/html",n),t&&Yl({range:r,quill:this.quill})}normalizeURIList(e){return e.split(/\r?\n/).filter(t=>t[0]!=="#").join(` +`)}onCapturePaste(e){if(e.defaultPrevented||!this.quill.isEnabled())return;e.preventDefault();let t=this.quill.getSelection(!0);if(t==null)return;let r=e.clipboardData?.getData("text/html"),n=e.clipboardData?.getData("text/plain");if(!r&&!n){let a=e.clipboardData?.getData("text/uri-list");a&&(n=this.normalizeURIList(a))}let s=Array.from(e.clipboardData?.files||[]);if(!r&&s.length>0){this.quill.uploader.upload(t,s);return}if(r&&s.length>0){let a=new DOMParser().parseFromString(r,"text/html");if(a.body.childElementCount===1&&a.body.firstElementChild?.tagName==="IMG"){this.quill.uploader.upload(t,s);return}}this.onPaste(t,{html:r,text:n})}onCopy(e){let t=this.quill.getText(e);return{html:this.quill.getSemanticHTML(e),text:t}}onPaste(e,t){let{text:r,html:n}=t,s=this.quill.getFormat(e.index),a=this.convert({text:r,html:n},s);YB.log("onPaste",a,{text:r,html:n});let o=new di.default().retain(e.index).delete(e.length).concat(a);this.quill.updateContents(o,B.sources.USER),this.quill.setSelection(o.length()-e.length,B.sources.SILENT),this.quill.scrollSelectionIntoView()}prepareMatching(e,t){let r=[],n=[];return this.matchers.forEach(s=>{let[a,o]=s;switch(a){case Node.TEXT_NODE:n.push(o);break;case Node.ELEMENT_NODE:r.push(o);break;default:Array.from(e.querySelectorAll(a)).forEach(l=>{t.has(l)?t.get(l)?.push(o):t.set(l,[o])});break}}),[r,n]}};U(Xl,"DEFAULTS",{matchers:[]});fc=new WeakMap});function Dy(i,e){let t=e;for(let r=i.length-1;r>=0;r-=1){let n=i[r];i[r]={delta:t.transform(n.delta,!0),range:n.range&&ap(n.range,t)},t=n.delta.transform(t),i[r].delta.length()===0&&i.splice(r,1)}}function lP(i,e){let t=e.ops[e.ops.length-1];return t==null?!1:t.insert!=null?typeof t.insert=="string"&&t.insert.endsWith(` +`):t.attributes!=null?Object.keys(t.attributes).some(r=>i.query(r,X.BLOCK)!=null):!1}function hP(i,e){let t=e.reduce((n,s)=>n+(s.delete||0),0),r=e.length()-t;return lP(i,e)&&(r-=1),r}function ap(i,e){if(!i)return i;let t=e.transformPosition(i.index),r=e.transformPosition(i.index+i.length);return{index:t,length:r-t}}var Zl,Oy=T(()=>{De();Oi();hi();Zl=class extends Je{constructor(t,r){super(t,r);U(this,"lastRecorded",0);U(this,"ignoreChange",!1);U(this,"stack",{undo:[],redo:[]});U(this,"currentRange",null);this.quill.on(B.events.EDITOR_CHANGE,(n,s,a,o)=>{n===B.events.SELECTION_CHANGE?s&&o!==B.sources.SILENT&&(this.currentRange=s):n===B.events.TEXT_CHANGE&&(this.ignoreChange||(!this.options.userOnly||o===B.sources.USER?this.record(s,a):this.transform(s)),this.currentRange=ap(this.currentRange,s))}),this.quill.keyboard.addBinding({key:"z",shortKey:!0},this.undo.bind(this)),this.quill.keyboard.addBinding({key:["z","Z"],shortKey:!0,shiftKey:!0},this.redo.bind(this)),/Win/i.test(navigator.platform)&&this.quill.keyboard.addBinding({key:"y",shortKey:!0},this.redo.bind(this)),this.quill.root.addEventListener("beforeinput",n=>{n.inputType==="historyUndo"?(this.undo(),n.preventDefault()):n.inputType==="historyRedo"&&(this.redo(),n.preventDefault())})}change(t,r){if(this.stack[t].length===0)return;let n=this.stack[t].pop();if(!n)return;let s=this.quill.getContents(),a=n.delta.invert(s);this.stack[r].push({delta:a,range:ap(n.range,a)}),this.lastRecorded=0,this.ignoreChange=!0,this.quill.updateContents(n.delta,B.sources.USER),this.ignoreChange=!1,this.restoreSelection(n)}clear(){this.stack={undo:[],redo:[]}}cutoff(){this.lastRecorded=0}record(t,r){if(t.ops.length===0)return;this.stack.redo=[];let n=t.invert(r),s=this.currentRange,a=Date.now();if(this.lastRecorded+this.options.delay>a&&this.stack.undo.length>0){let o=this.stack.undo.pop();o&&(n=n.compose(o.delta),s=o.range)}else this.lastRecorded=a;n.length()!==0&&(this.stack.undo.push({delta:n,range:s}),this.stack.undo.length>this.options.maxStack&&this.stack.undo.shift())}redo(){this.change("redo","undo")}transform(t){Dy(this.stack.undo,t),Dy(this.stack.redo,t)}undo(){this.change("undo","redo")}restoreSelection(t){if(t.range)this.quill.setSelection(t.range,B.sources.USER);else{let r=hP(this.quill.scroll,t.delta);this.quill.setSelection(r,B.sources.USER)}}};U(Zl,"DEFAULTS",{delay:1e3,maxStack:100,userOnly:!1})});var By,pc,Py,Fy=T(()=>{By=yt(xi(),1);Qr();Oi();pc=class extends Je{constructor(e,t){super(e,t),e.root.addEventListener("drop",r=>{r.preventDefault();let n=null;if(document.caretRangeFromPoint)n=document.caretRangeFromPoint(r.clientX,r.clientY);else if(document.caretPositionFromPoint){let a=document.caretPositionFromPoint(r.clientX,r.clientY);n=document.createRange(),n.setStart(a.offsetNode,a.offset),n.setEnd(a.offsetNode,a.offset)}let s=n&&e.selection.normalizeNative(n);if(s){let a=e.selection.normalizedToRange(s);r.dataTransfer?.files&&this.upload(a,r.dataTransfer.files)}})}upload(e,t){let r=[];Array.from(t).forEach(n=>{n&&this.options.mimetypes?.includes(n.type)&&r.push(n)}),r.length>0&&this.options.handler.call(this,e,r)}};pc.DEFAULTS={mimetypes:["image/png","image/jpeg"],handler(i,e){if(!this.quill.scroll.query("image"))return;let t=e.map(r=>new Promise(n=>{let s=new FileReader;s.onload=()=>{n(s.result)},s.readAsDataURL(r)}));Promise.all(t).then(r=>{let n=r.reduce((s,a)=>s.insert({image:a}),new By.default().retain(i.index).delete(i.length));this.quill.updateContents(n,ee.sources.USER),this.quill.setSelection(i.index+r.length,ee.sources.SILENT)})}};Py=pc});function cP(i){return typeof i.data=="string"?i.data:i.dataTransfer?.types.includes("text/plain")?i.dataTransfer.getData("text/plain"):null}var qy,dP,op,Hy,Uy=T(()=>{qy=yt(xi(),1);Oi();hi();uc();dP=["insertText","insertReplacementText"],op=class extends Je{constructor(e,t){super(e,t),e.root.addEventListener("beforeinput",r=>{this.handleBeforeInput(r)}),/Android/i.test(navigator.userAgent)||e.on(B.events.COMPOSITION_BEFORE_START,()=>{this.handleCompositionStart()})}deleteRange(e){Yl({range:e,quill:this.quill})}replaceText(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"";if(e.length===0)return!1;if(t){let r=this.quill.getFormat(e.index,1);this.deleteRange(e),this.quill.updateContents(new qy.default().retain(e.index).insert(t,r),B.sources.USER)}else this.deleteRange(e);return this.quill.setSelection(e.index+t.length,0,B.sources.SILENT),!0}handleBeforeInput(e){if(this.quill.composition.isComposing||e.defaultPrevented||!dP.includes(e.inputType))return;let t=e.getTargetRanges?e.getTargetRanges()[0]:null;if(!t||t.collapsed===!0)return;let r=cP(e);if(r==null)return;let n=this.quill.selection.normalizeNative(t),s=n?this.quill.selection.normalizedToRange(n):null;s&&this.replaceText(s,r)&&e.preventDefault()}handleCompositionStart(){let e=this.quill.getSelection();e&&this.replaceText(e)}};Hy=op});var uP,fP,mP,lp,$y,jy=T(()=>{De();Oi();hi();uP=/Mac/i.test(navigator.platform),fP=100,mP=i=>!!(i.key==="ArrowLeft"||i.key==="ArrowRight"||i.key==="ArrowUp"||i.key==="ArrowDown"||i.key==="Home"||uP&&i.key==="a"&&i.ctrlKey===!0),lp=class extends Je{constructor(t,r){super(t,r);U(this,"isListening",!1);U(this,"selectionChangeDeadline",0);this.handleArrowKeys(),this.handleNavigationShortcuts()}handleArrowKeys(){this.quill.keyboard.addBinding({key:["ArrowLeft","ArrowRight"],offset:0,shiftKey:null,handler(t,r){let{line:n,event:s}=r;if(!(n instanceof ai)||!n.uiNode)return!0;let a=getComputedStyle(n.domNode).direction==="rtl";return a&&s.key!=="ArrowRight"||!a&&s.key!=="ArrowLeft"?!0:(this.quill.setSelection(t.index-1,t.length+(s.shiftKey?1:0),B.sources.USER),!1)}})}handleNavigationShortcuts(){this.quill.root.addEventListener("keydown",t=>{!t.defaultPrevented&&mP(t)&&this.ensureListeningToSelectionChange()})}ensureListeningToSelectionChange(){if(this.selectionChangeDeadline=Date.now()+fP,this.isListening)return;this.isListening=!0;let t=()=>{this.isListening=!1,Date.now()<=this.selectionChangeDeadline&&this.handleSelectionChange()};document.addEventListener("selectionchange",t,{once:!0})}handleSelectionChange(){let t=document.getSelection();if(!t)return;let r=t.getRangeAt(0);if(r.collapsed!==!0||r.startOffset!==0)return;let n=this.quill.scroll.find(r.startContainer);if(!(n instanceof ai)||!n.uiNode)return;let s=document.createRange();s.setStartAfter(n.uiNode),s.setEndAfter(n.uiNode),t.removeAllRanges(),t.addRange(s)}},$y=lp});var Fn,gc,hp=T(()=>{hi();Ri();Rn();eo();Bl();tc();Zr();by();Kr();sp();Oy();uc();Fy();Fn=yt(xi(),1);Uy();jy();Oi();B.register({"blots/block":Oe,"blots/block/embed":gt,"blots/break":At,"blots/container":Bi,"blots/cursor":Dn,"blots/embed":Za,"blots/inline":kt,"blots/scroll":vy,"blots/text":nt,"modules/clipboard":Xl,"modules/history":Zl,"modules/keyboard":Vl,"modules/uploader":Py,"modules/input":Hy,"modules/uiNode":$y});gc=B});var dp,pP,Gy,Wy=T(()=>{De();dp=class extends dt{add(e,t){let r=0;if(t==="+1"||t==="-1"){let n=this.value(e)||0;r=t==="+1"?n+1:n-1}else typeof t=="number"&&(r=t);return r===0?(this.remove(e),!0):super.add(e,r.toString())}canAdd(e,t){return super.canAdd(e,t)||super.canAdd(e,parseInt(t,10))}value(e){return parseInt(super.value(e),10)||void 0}},pP=new dp("indent","ql-indent",{scope:X.BLOCK,whitelist:[1,2,3,4,5,6,7,8]}),Gy=pP});var Ql,Vy,Yy=T(()=>{Ri();Ql=class extends Oe{};U(Ql,"blotName","blockquote"),U(Ql,"tagName","blockquote");Vy=Ql});var Jl,Xy,Ky=T(()=>{Ri();Jl=class extends Oe{static formats(e){return this.tagName.indexOf(e.tagName)+1}};U(Jl,"blotName","header"),U(Jl,"tagName",["H1","H2","H3","H4","H5","H6"]);Xy=Jl});var Ls,qn,Zy=T(()=>{Ri();eo();hi();Ls=class extends Bi{};Ls.blotName="list-container";Ls.tagName="OL";qn=class extends Oe{static create(e){let t=super.create();return t.setAttribute("data-list",e),t}static formats(e){return e.getAttribute("data-list")||void 0}static register(){B.register(Ls)}constructor(e,t){super(e,t);let r=t.ownerDocument.createElement("span"),n=s=>{if(!e.isEnabled())return;let a=this.statics.formats(t,e);a==="checked"?(this.format("list","unchecked"),s.preventDefault()):a==="unchecked"&&(this.format("list","checked"),s.preventDefault())};r.addEventListener("mousedown",n),r.addEventListener("touchstart",n),this.attachUI(r)}format(e,t){e===this.statics.blotName&&t?this.domNode.setAttribute("data-list",t):super.format(e,t)}};qn.blotName="list";qn.tagName="LI";Ls.allowedChildren=[qn];qn.requiredContainer=Ls});var e0,io,xc=T(()=>{Zr();e0=class extends kt{static create(){return super.create()}static formats(){return!0}optimize(e){super.optimize(e),this.domNode.tagName!==this.statics.tagName[0]&&this.replaceWith(this.statics.blotName)}};U(e0,"blotName","bold"),U(e0,"tagName",["STRONG","B"]);io=e0});var t0,Qy,Jy=T(()=>{xc();t0=class extends io{};U(t0,"blotName","italic"),U(t0,"tagName",["EM","I"]);Qy=t0});function cp(i,e){let t=document.createElement("a");t.href=i;let r=t.href.slice(0,t.href.indexOf(":"));return e.indexOf(r)>-1}var vi,i0=T(()=>{Zr();vi=class extends kt{static create(e){let t=super.create(e);return t.setAttribute("href",this.sanitize(e)),t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank"),t}static formats(e){return e.getAttribute("href")}static sanitize(e){return cp(e,this.PROTOCOL_WHITELIST)?e:this.SANITIZED_URL}format(e,t){e!==this.statics.blotName||!t?super.format(e,t):this.domNode.setAttribute("href",this.constructor.sanitize(t))}};U(vi,"blotName","link"),U(vi,"tagName","A"),U(vi,"SANITIZED_URL","about:blank"),U(vi,"PROTOCOL_WHITELIST",["http","https","mailto","tel","sms"])});var r0,ev,tv=T(()=>{Zr();r0=class extends kt{static create(e){return e==="super"?document.createElement("sup"):e==="sub"?document.createElement("sub"):super.create(e)}static formats(e){if(e.tagName==="SUB")return"sub";if(e.tagName==="SUP")return"super"}};U(r0,"blotName","script"),U(r0,"tagName",["SUB","SUP"]);ev=r0});var n0,iv,rv=T(()=>{xc();n0=class extends io{};U(n0,"blotName","strike"),U(n0,"tagName",["S","STRIKE"]);iv=n0});var s0,nv,sv=T(()=>{Zr();s0=class extends kt{};U(s0,"blotName","underline"),U(s0,"tagName","U");nv=s0});var ro,av,ov=T(()=>{tc();ro=class extends Za{static create(e){if(window.katex==null)throw new Error("Formula module requires KaTeX.");let t=super.create(e);return typeof e=="string"&&(window.katex.render(e,t,{throwOnError:!1,errorColor:"#f00"}),t.setAttribute("data-value",e)),t}static value(e){return e.getAttribute("data-value")}html(){let{formula:e}=this.value();return`${e}`}};U(ro,"blotName","formula"),U(ro,"className","ql-formula"),U(ro,"tagName","SPAN");av=ro});var lv,a0,hv,dv=T(()=>{De();i0();lv=["alt","height","width"],a0=class extends Ze{static create(e){let t=super.create(e);return typeof e=="string"&&t.setAttribute("src",this.sanitize(e)),t}static formats(e){return lv.reduce((t,r)=>(e.hasAttribute(r)&&(t[r]=e.getAttribute(r)),t),{})}static match(e){return/\.(jpe?g|gif|png)$/.test(e)||/^data:image\/.+;base64/.test(e)}static sanitize(e){return cp(e,["http","https","data"])?e:"//:0"}static value(e){return e.getAttribute("src")}format(e,t){lv.indexOf(e)>-1?t?this.domNode.setAttribute(e,t):this.domNode.removeAttribute(e):super.format(e,t)}};U(a0,"blotName","image"),U(a0,"tagName","IMG");hv=a0});var cv,no,uv,fv=T(()=>{Ri();i0();cv=["height","width"],no=class extends gt{static create(e){let t=super.create(e);return t.setAttribute("frameborder","0"),t.setAttribute("allowfullscreen","true"),t.setAttribute("src",this.sanitize(e)),t}static formats(e){return cv.reduce((t,r)=>(e.hasAttribute(r)&&(t[r]=e.getAttribute(r)),t),{})}static sanitize(e){return vi.sanitize(e)}static value(e){return e.getAttribute("src")}format(e,t){cv.indexOf(e)>-1?t?this.domNode.setAttribute(e,t):this.domNode.removeAttribute(e):super.format(e,t)}html(){let{video:e}=this.value();return`${e}`}};U(no,"blotName","video"),U(no,"className","ql-video"),U(no,"tagName","IFRAME");uv=no});var yc,o0,en,ci,Is,gP,l0,mv=T(()=>{yc=yt(xi(),1);De();Zr();hi();Oi();Ri();Rn();Bl();Kr();ac();sp();o0=new dt("code-token","hljs",{scope:X.INLINE}),en=class i extends kt{static formats(e,t){for(;e!=null&&e!==t.domNode;){if(e.classList&&e.classList.contains(st.className))return super.formats(e,t);e=e.parentNode}}constructor(e,t,r){super(e,t,r),o0.add(this.domNode,r)}format(e,t){e!==i.blotName?super.format(e,t):t?o0.add(this.domNode,t):(o0.remove(this.domNode),this.domNode.classList.remove(this.statics.className))}optimize(){super.optimize(...arguments),o0.value(this.domNode)||this.unwrap()}};en.blotName="code-token";en.className="ql-token";ci=class extends st{static create(e){let t=super.create(e);return typeof e=="string"&&t.setAttribute("data-language",e),t}static formats(e){return e.getAttribute("data-language")||"plain"}static register(){}format(e,t){e===this.statics.blotName&&t?this.domNode.setAttribute("data-language",t):super.format(e,t)}replaceWith(e,t){return this.formatAt(0,this.length(),en.blotName,!1),super.replaceWith(e,t)}},Is=class extends vr{attach(){super.attach(),this.forceNext=!1,this.scroll.emitMount(this)}format(e,t){e===ci.blotName&&(this.forceNext=!0,this.children.forEach(r=>{r.format(e,t)}))}formatAt(e,t,r,n){r===ci.blotName&&(this.forceNext=!0),super.formatAt(e,t,r,n)}highlight(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;if(this.children.head==null)return;let n=`${Array.from(this.domNode.childNodes).filter(a=>a!==this.uiNode).map(a=>a.textContent).join(` `)} -`,s=hi.formats(this.children.head.domNode);if(t||this.forceNext||this.cachedText!==n){if(n.trim().length>0||this.cachedText==null){let a=this.children.reduce((l,h)=>l.concat(cm(h,!1)),new dc.default),o=e(n,s);a.diff(o).reduce((l,h)=>{let{retain:d,attributes:c}=h;return d?(c&&Object.keys(c).forEach(f=>{[hi.blotName,en.blotName].includes(f)&&this.formatAt(l,d,f,c[f])}),l+d):l},0)}this.cachedText=n,this.forceNext=!1}}html(e,t){let[r]=this.children.find(e);return`
    +`,s=ci.formats(this.children.head.domNode);if(t||this.forceNext||this.cachedText!==n){if(n.trim().length>0||this.cachedText==null){let a=this.children.reduce((l,h)=>l.concat(Mm(h,!1)),new yc.default),o=e(n,s);a.diff(o).reduce((l,h)=>{let{retain:d,attributes:c}=h;return d?(c&&Object.keys(c).forEach(f=>{[ci.blotName,en.blotName].includes(f)&&this.formatAt(l,d,f,c[f])}),l+d):l},0)}this.cachedText=n,this.forceNext=!1}}html(e,t){let[r]=this.children.find(e);return`
     ${ks(this.code(e,t))}
    -
    `}optimize(e){if(super.optimize(e),this.parent!=null&&this.children.head!=null&&this.uiNode!=null){let t=hi.formats(this.children.head.domNode);t!==this.uiNode.value&&(this.uiNode.value=t)}}};Is.allowedChildren=[hi];hi.requiredContainer=Is;hi.allowedChildren=[en,On,nt,Et];VB=(i,e,t)=>{if(typeof i.versionString=="string"){let r=i.versionString.split(".")[0];if(parseInt(r,10)>=11)return i.highlight(t,{language:e}).value}return i.highlight(e,t).value},i0=class extends Qe{static register(){B.register(en,!0),B.register(hi,!0),B.register(Is,!0)}constructor(e,t){if(super(e,t),this.options.hljs==null)throw new Error("Syntax module requires highlight.js. Please include the library on the page before Quill.");this.languages=this.options.languages.reduce((r,n)=>{let{key:s}=n;return r[s]=!0,r},{}),this.highlightBlot=this.highlightBlot.bind(this),this.initListener(),this.initTimer()}initListener(){this.quill.on(B.events.SCROLL_BLOT_MOUNT,e=>{if(!(e instanceof Is))return;let t=this.quill.root.ownerDocument.createElement("select");this.options.languages.forEach(r=>{let{key:n,label:s}=r,a=t.ownerDocument.createElement("option");a.textContent=s,a.setAttribute("value",n),t.appendChild(a)}),t.addEventListener("change",()=>{e.format(hi.blotName,t.value),this.quill.root.focus(),this.highlight(e,!0)}),e.uiNode==null&&(e.attachUI(t),e.children.head&&(t.value=hi.formats(e.children.head.domNode)))})}initTimer(){let e=null;this.quill.on(B.events.SCROLL_OPTIMIZE,()=>{e&&clearTimeout(e),e=setTimeout(()=>{this.highlight(),e=null},this.options.interval)})}highlight(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:null,t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;if(this.quill.selection.composing)return;this.quill.update(B.sources.USER);let r=this.quill.getSelection();(e==null?this.quill.scroll.descendants(Is):[e]).forEach(s=>{s.highlight(this.highlightBlot,t)}),this.quill.update(B.sources.SILENT),r!=null&&this.quill.setSelection(r,B.sources.SILENT)}highlightBlot(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"plain";if(t=this.languages[t]?t:"plain",t==="plain")return ks(e).split(` +
    `}optimize(e){if(super.optimize(e),this.parent!=null&&this.children.head!=null&&this.uiNode!=null){let t=ci.formats(this.children.head.domNode);t!==this.uiNode.value&&(this.uiNode.value=t)}}};Is.allowedChildren=[ci];ci.requiredContainer=Is;ci.allowedChildren=[en,Dn,nt,At];gP=(i,e,t)=>{if(typeof i.versionString=="string"){let r=i.versionString.split(".")[0];if(parseInt(r,10)>=11)return i.highlight(t,{language:e}).value}return i.highlight(e,t).value},l0=class extends Je{static register(){B.register(en,!0),B.register(ci,!0),B.register(Is,!0)}constructor(e,t){if(super(e,t),this.options.hljs==null)throw new Error("Syntax module requires highlight.js. Please include the library on the page before Quill.");this.languages=this.options.languages.reduce((r,n)=>{let{key:s}=n;return r[s]=!0,r},{}),this.highlightBlot=this.highlightBlot.bind(this),this.initListener(),this.initTimer()}initListener(){this.quill.on(B.events.SCROLL_BLOT_MOUNT,e=>{if(!(e instanceof Is))return;let t=this.quill.root.ownerDocument.createElement("select");this.options.languages.forEach(r=>{let{key:n,label:s}=r,a=t.ownerDocument.createElement("option");a.textContent=s,a.setAttribute("value",n),t.appendChild(a)}),t.addEventListener("change",()=>{e.format(ci.blotName,t.value),this.quill.root.focus(),this.highlight(e,!0)}),e.uiNode==null&&(e.attachUI(t),e.children.head&&(t.value=ci.formats(e.children.head.domNode)))})}initTimer(){let e=null;this.quill.on(B.events.SCROLL_OPTIMIZE,()=>{e&&clearTimeout(e),e=setTimeout(()=>{this.highlight(),e=null},this.options.interval)})}highlight(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:null,t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;if(this.quill.selection.composing)return;this.quill.update(B.sources.USER);let r=this.quill.getSelection();(e==null?this.quill.scroll.descendants(Is):[e]).forEach(s=>{s.highlight(this.highlightBlot,t)}),this.quill.update(B.sources.SILENT),r!=null&&this.quill.setSelection(r,B.sources.SILENT)}highlightBlot(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"plain";if(t=this.languages[t]?t:"plain",t==="plain")return ks(e).split(` `).reduce((n,s,a)=>(a!==0&&n.insert(` -`,{[st.blotName]:t}),n.insert(s)),new dc.default);let r=this.quill.root.ownerDocument.createElement("div");return r.classList.add(st.className),r.innerHTML=VB(this.options.hljs,t,e),ac(this.quill.scroll,r,[(n,s)=>{let a=t0.value(n);return a?s.compose(new dc.default().retain(s.length(),{[en.blotName]:a})):s}],[(n,s)=>n.data.split(` +`,{[st.blotName]:t}),n.insert(s)),new yc.default);let r=this.quill.root.ownerDocument.createElement("div");return r.classList.add(st.className),r.innerHTML=gP(this.options.hljs,t,e),mc(this.quill.scroll,r,[(n,s)=>{let a=o0.value(n);return a?s.compose(new yc.default().retain(s.length(),{[en.blotName]:a})):s}],[(n,s)=>n.data.split(` `).reduce((a,o,l)=>(l!==0&&a.insert(` -`,{[st.blotName]:t}),a.insert(o)),s)],new WeakMap)}};i0.DEFAULTS={hljs:window.hljs,interval:1e3,languages:[{key:"plain",label:"Plain"},{key:"bash",label:"Bash"},{key:"cpp",label:"C++"},{key:"cs",label:"C#"},{key:"css",label:"CSS"},{key:"diff",label:"Diff"},{key:"xml",label:"HTML/XML"},{key:"java",label:"Java"},{key:"javascript",label:"JavaScript"},{key:"markdown",label:"Markdown"},{key:"php",label:"PHP"},{key:"python",label:"Python"},{key:"ruby",label:"Ruby"},{key:"sql",label:"SQL"}]}});function cc(){return`row-${Math.random().toString(36).slice(2,6)}`}var r0,Pi,Fi,yi,tn,tv=T(()=>{zi();Ja();r0=class r0 extends De{static create(e){let t=super.create();return e?t.setAttribute("data-row",e):t.setAttribute("data-row",cc()),t}static formats(e){if(e.hasAttribute("data-row"))return e.getAttribute("data-row")}cellOffset(){return this.parent?this.parent.children.indexOf(this):-1}format(e,t){e===r0.blotName&&t?this.domNode.setAttribute("data-row",t):super.format(e,t)}row(){return this.parent}rowOffset(){return this.row()?this.row().rowOffset():-1}table(){return this.row()&&this.row().table()}};U(r0,"blotName","table"),U(r0,"tagName","TD");Pi=r0,Fi=class extends Oi{checkMerge(){if(super.checkMerge()&&this.next.children.head!=null){let e=this.children.head.formats(),t=this.children.tail.formats(),r=this.next.children.head.formats(),n=this.next.children.tail.formats();return e.table===t.table&&e.table===r.table&&e.table===n.table}return!1}optimize(e){super.optimize(e),this.children.forEach(t=>{if(t.next==null)return;let r=t.formats(),n=t.next.formats();if(r.table!==n.table){let s=this.splitAfter(t);s&&s.optimize(),this.prev&&this.prev.optimize()}})}rowOffset(){return this.parent?this.parent.children.indexOf(this):-1}table(){return this.parent&&this.parent.parent}};U(Fi,"blotName","table-row"),U(Fi,"tagName","TR");yi=class extends Oi{};U(yi,"blotName","table-body"),U(yi,"tagName","TBODY");tn=class extends Oi{balanceCells(){let e=this.descendants(Fi),t=e.reduce((r,n)=>Math.max(n.children.length,r),0);e.forEach(r=>{new Array(t-r.children.length).fill(0).forEach(()=>{let n;r.children.head!=null&&(n=Pi.formats(r.children.head.domNode));let s=this.scroll.create(Pi.blotName,n);r.appendChild(s),s.optimize()})})}cells(e){return this.rows().map(t=>t.children.at(e))}deleteColumn(e){let[t]=this.descendant(yi);t==null||t.children.head==null||t.children.forEach(r=>{let n=r.children.at(e);n?.remove()})}insertColumn(e){let[t]=this.descendant(yi);t==null||t.children.head==null||t.children.forEach(r=>{let n=r.children.at(e),s=Pi.formats(r.children.head.domNode),a=this.scroll.create(Pi.blotName,s);r.insertBefore(a,n)})}insertRow(e){let[t]=this.descendant(yi);if(t==null||t.children.head==null)return;let r=cc(),n=this.scroll.create(Fi.blotName);t.children.head.children.forEach(()=>{let a=this.scroll.create(Pi.blotName,r);n.appendChild(a)});let s=t.children.at(e);t.insertBefore(n,s)}rows(){let e=this.children.head;return e==null?[]:e.children.map(t=>t)}};U(tn,"blotName","table-container"),U(tn,"tagName","TABLE");tn.allowedChildren=[yi];yi.requiredContainer=tn;yi.allowedChildren=[Fi];Fi.requiredContainer=yi;Fi.allowedChildren=[Pi];Pi.requiredContainer=Fi});var iv,tp,rv,nv=T(()=>{iv=pt(pi(),1);oi();Di();tv();tp=class extends Qe{static register(){B.register(Pi),B.register(Fi),B.register(yi),B.register(tn)}constructor(){super(...arguments),this.listenBalanceCells()}balanceTables(){this.quill.scroll.descendants(tn).forEach(e=>{e.balanceCells()})}deleteColumn(){let[e,,t]=this.getTable();t!=null&&(e.deleteColumn(t.cellOffset()),this.quill.update(B.sources.USER))}deleteRow(){let[,e]=this.getTable();e!=null&&(e.remove(),this.quill.update(B.sources.USER))}deleteTable(){let[e]=this.getTable();if(e==null)return;let t=e.offset();e.remove(),this.quill.update(B.sources.USER),this.quill.setSelection(t,B.sources.SILENT)}getTable(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.quill.getSelection();if(e==null)return[null,null,null,-1];let[t,r]=this.quill.getLine(e.index);if(t==null||t.statics.blotName!==Pi.blotName)return[null,null,null,-1];let n=t.parent;return[n.parent.parent,n,t,r]}insertColumn(e){let t=this.quill.getSelection();if(!t)return;let[r,n,s]=this.getTable(t);if(s==null)return;let a=s.cellOffset();r.insertColumn(a+e),this.quill.update(B.sources.USER);let o=n.rowOffset();e===0&&(o+=1),this.quill.setSelection(t.index+o,t.length,B.sources.SILENT)}insertColumnLeft(){this.insertColumn(0)}insertColumnRight(){this.insertColumn(1)}insertRow(e){let t=this.quill.getSelection();if(!t)return;let[r,n,s]=this.getTable(t);if(s==null)return;let a=n.rowOffset();r.insertRow(a+e),this.quill.update(B.sources.USER),e>0?this.quill.setSelection(t,B.sources.SILENT):this.quill.setSelection(t.index+n.children.length,t.length,B.sources.SILENT)}insertRowAbove(){this.insertRow(0)}insertRowBelow(){this.insertRow(1)}insertTable(e,t){let r=this.quill.getSelection();if(r==null)return;let n=new Array(e).fill(0).reduce(s=>{let a=new Array(t).fill(` -`).join("");return s.insert(a,{table:cc()})},new iv.default().retain(r.index));this.quill.updateContents(n,B.sources.USER),this.quill.setSelection(r.index,B.sources.SILENT),this.balanceTables()}listenBalanceCells(){this.quill.on(B.events.SCROLL_OPTIMIZE,e=>{e.some(t=>["TD","TR","TBODY","TABLE"].includes(t.target.tagName)?(this.quill.once(B.events.TEXT_CHANGE,(r,n,s)=>{s===B.sources.USER&&this.balanceTables()}),!0):!1)})}},rv=tp});function av(i,e,t){let r=document.createElement("button");r.setAttribute("type","button"),r.classList.add(`ql-${e}`),r.setAttribute("aria-pressed","false"),t!=null?(r.value=t,r.setAttribute("aria-label",`${e}: ${t}`)):r.setAttribute("aria-label",e),i.appendChild(r)}function WB(i,e){Array.isArray(e[0])||(e=[e]),e.forEach(t=>{let r=document.createElement("span");r.classList.add("ql-formats"),t.forEach(n=>{if(typeof n=="string")av(r,n);else{let s=Object.keys(n)[0],a=n[s];Array.isArray(a)?YB(r,s,a):av(r,s,a)}}),i.appendChild(r)})}function YB(i,e,t){let r=document.createElement("select");r.classList.add(`ql-${e}`),t.forEach(n=>{let s=document.createElement("option");n!==!1?s.setAttribute("value",String(n)):s.setAttribute("selected","selected"),r.appendChild(s)}),i.appendChild(r)}var ov,sv,no,lv=T(()=>{ov=pt(pi(),1);Re();oi();Cs();Di();sv=gi("quill:toolbar"),no=class extends Qe{constructor(e,t){if(super(e,t),Array.isArray(this.options.container)){let r=document.createElement("div");r.setAttribute("role","toolbar"),WB(r,this.options.container),e.container?.parentNode?.insertBefore(r,e.container),this.container=r}else typeof this.options.container=="string"?this.container=document.querySelector(this.options.container):this.container=this.options.container;if(!(this.container instanceof HTMLElement)){sv.error("Container required for toolbar",this.options);return}this.container.classList.add("ql-toolbar"),this.controls=[],this.handlers={},this.options.handlers&&Object.keys(this.options.handlers).forEach(r=>{let n=this.options.handlers?.[r];n&&this.addHandler(r,n)}),Array.from(this.container.querySelectorAll("button, select")).forEach(r=>{this.attach(r)}),this.quill.on(B.events.EDITOR_CHANGE,()=>{let[r]=this.quill.selection.getRange();this.update(r)})}addHandler(e,t){this.handlers[e]=t}attach(e){let t=Array.from(e.classList).find(n=>n.indexOf("ql-")===0);if(!t)return;if(t=t.slice(3),e.tagName==="BUTTON"&&e.setAttribute("type","button"),this.handlers[t]==null&&this.quill.scroll.query(t)==null){sv.warn("ignoring attaching to nonexistent format",t,e);return}let r=e.tagName==="SELECT"?"change":"click";e.addEventListener(r,n=>{let s;if(e.tagName==="SELECT"){if(e.selectedIndex<0)return;let o=e.options[e.selectedIndex];o.hasAttribute("selected")?s=!1:s=o.value||!1}else e.classList.contains("ql-active")?s=!1:s=e.value||!e.hasAttribute("value"),n.preventDefault();this.quill.focus();let[a]=this.quill.selection.getRange();if(this.handlers[t]!=null)this.handlers[t].call(this,s);else if(this.quill.scroll.query(t).prototype instanceof Ke){if(s=prompt(`Enter ${t}`),!s)return;this.quill.updateContents(new ov.default().retain(a.index).delete(a.length).insert({[t]:s}),B.sources.USER)}else this.quill.format(t,s,B.sources.USER);this.update(a)}),this.controls.push([t,e])}update(e){let t=e==null?{}:this.quill.getFormat(e);this.controls.forEach(r=>{let[n,s]=r;if(s.tagName==="SELECT"){let a=null;if(e==null)a=null;else if(t[n]==null)a=s.querySelector("option[selected]");else if(!Array.isArray(t[n])){let o=t[n];typeof o=="string"&&(o=o.replace(/"/g,'\\"')),a=s.querySelector(`option[value="${o}"]`)}a==null?(s.value="",s.selectedIndex=-1):a.selected=!0}else if(e==null)s.classList.remove("ql-active"),s.setAttribute("aria-pressed","false");else if(s.hasAttribute("value")){let a=t[n],o=a===s.getAttribute("value")||a!=null&&a.toString()===s.getAttribute("value")||a==null&&!s.getAttribute("value");s.classList.toggle("ql-active",o),s.setAttribute("aria-pressed",o.toString())}else{let a=t[n]!=null;s.classList.toggle("ql-active",a),s.setAttribute("aria-pressed",a.toString())}})}};no.DEFAULTS={};no.DEFAULTS={container:null,handlers:{clean(){let i=this.quill.getSelection();if(i!=null)if(i.length===0){let e=this.quill.getFormat();Object.keys(e).forEach(t=>{this.quill.scroll.query(t,X.INLINE)!=null&&this.quill.format(t,!1,B.sources.USER)})}else this.quill.removeFormat(i.index,i.length,B.sources.USER)},direction(i){let{align:e}=this.quill.getFormat();i==="rtl"&&e==null?this.quill.format("align","right",B.sources.USER):!i&&e==="right"&&this.quill.format("align",!1,B.sources.USER),this.quill.format("direction",i,B.sources.USER)},indent(i){let e=this.quill.getSelection(),t=this.quill.getFormat(e),r=parseInt(t.indent||0,10);if(i==="+1"||i==="-1"){let n=i==="+1"?1:-1;t.direction==="rtl"&&(n*=-1),this.quill.format("indent",r+n,B.sources.USER)}},link(i){i===!0&&(i=prompt("Enter link URL:")),this.quill.format("link",i,B.sources.USER)},list(i){let e=this.quill.getSelection(),t=this.quill.getFormat(e);i==="check"?t.list==="checked"||t.list==="unchecked"?this.quill.format("list",!1,B.sources.USER):this.quill.format("list","unchecked",B.sources.USER):this.quill.format("list",i,B.sources.USER)}}}});var XB,KB,ZB,QB,JB,eP,tP,iP,hv,rP,nP,sP,aP,oP,lP,hP,dP,cP,uP,fP,mP,pP,gP,xP,yP,vP,bP,wP,MP,TP,NP,EP,SP,Un,uc=T(()=>{XB='',KB='',ZB='',QB='',JB='',eP='',tP='',iP='',hv='',rP='',nP='',sP='',aP='',oP='',lP='',hP='',dP='',cP='',uP='',fP='',mP='',pP='',gP='',xP='',yP='',vP='',bP='',wP='',MP='',TP='',NP='',EP='',SP='',Un={align:{"":XB,center:KB,right:ZB,justify:QB},background:JB,blockquote:eP,bold:tP,clean:iP,code:hv,"code-block":hv,color:rP,direction:{"":nP,rtl:sP},formula:aP,header:{1:oP,2:lP,3:hP,4:dP,5:cP,6:uP},italic:fP,image:mP,indent:{"+1":pP,"-1":gP},link:xP,list:{bullet:yP,check:vP,ordered:bP},script:{sub:wP,super:MP},strike:TP,table:NP,underline:EP,video:SP}});function cv(i,e){i.setAttribute(e,`${i.getAttribute(e)!=="true"}`)}var AP,dv,ip,$n,n0=T(()=>{AP='',dv=0;ip=class{constructor(e){this.select=e,this.container=document.createElement("span"),this.buildPicker(),this.select.style.display="none",this.select.parentNode.insertBefore(this.container,this.select),this.label.addEventListener("mousedown",()=>{this.togglePicker()}),this.label.addEventListener("keydown",t=>{switch(t.key){case"Enter":this.togglePicker();break;case"Escape":this.escape(),t.preventDefault();break;default:}}),this.select.addEventListener("change",this.update.bind(this))}togglePicker(){this.container.classList.toggle("ql-expanded"),cv(this.label,"aria-expanded"),cv(this.options,"aria-hidden")}buildItem(e){let t=document.createElement("span");t.tabIndex="0",t.setAttribute("role","button"),t.classList.add("ql-picker-item");let r=e.getAttribute("value");return r&&t.setAttribute("data-value",r),e.textContent&&t.setAttribute("data-label",e.textContent),t.addEventListener("click",()=>{this.selectItem(t,!0)}),t.addEventListener("keydown",n=>{switch(n.key){case"Enter":this.selectItem(t,!0),n.preventDefault();break;case"Escape":this.escape(),n.preventDefault();break;default:}}),t}buildLabel(){let e=document.createElement("span");return e.classList.add("ql-picker-label"),e.innerHTML=AP,e.tabIndex="0",e.setAttribute("role","button"),e.setAttribute("aria-expanded","false"),this.container.appendChild(e),e}buildOptions(){let e=document.createElement("span");e.classList.add("ql-picker-options"),e.setAttribute("aria-hidden","true"),e.tabIndex="-1",e.id=`ql-picker-options-${dv}`,dv+=1,this.label.setAttribute("aria-controls",e.id),this.options=e,Array.from(this.select.options).forEach(t=>{let r=this.buildItem(t);e.appendChild(r),t.selected===!0&&this.selectItem(r)}),this.container.appendChild(e)}buildPicker(){Array.from(this.select.attributes).forEach(e=>{this.container.setAttribute(e.name,e.value)}),this.container.classList.add("ql-picker"),this.label=this.buildLabel(),this.buildOptions()}escape(){this.close(),setTimeout(()=>this.label.focus(),1)}close(){this.container.classList.remove("ql-expanded"),this.label.setAttribute("aria-expanded","false"),this.options.setAttribute("aria-hidden","true")}selectItem(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1,r=this.container.querySelector(".ql-selected");e!==r&&(r?.classList.remove("ql-selected"),e!=null&&(e.classList.add("ql-selected"),this.select.selectedIndex=Array.from(e.parentNode.children).indexOf(e),e.hasAttribute("data-value")?this.label.setAttribute("data-value",e.getAttribute("data-value")):this.label.removeAttribute("data-value"),e.hasAttribute("data-label")?this.label.setAttribute("data-label",e.getAttribute("data-label")):this.label.removeAttribute("data-label"),t&&(this.select.dispatchEvent(new Event("change")),this.close())))}update(){let e;if(this.select.selectedIndex>-1){let r=this.container.querySelector(".ql-picker-options").children[this.select.selectedIndex];e=this.select.options[this.select.selectedIndex],this.selectItem(r)}else this.selectItem(null);let t=e!=null&&e!==this.select.querySelector("option[selected]");this.label.classList.toggle("ql-active",t)}},$n=ip});var rp,fc,np=T(()=>{n0();rp=class extends $n{constructor(e,t){super(e),this.label.innerHTML=t,this.container.classList.add("ql-color-picker"),Array.from(this.container.querySelectorAll(".ql-picker-item")).slice(0,7).forEach(r=>{r.classList.add("ql-primary")})}buildItem(e){let t=super.buildItem(e);return t.style.backgroundColor=e.getAttribute("value")||"",t}selectItem(e,t){super.selectItem(e,t);let r=this.label.querySelector(".ql-color-label"),n=e&&e.getAttribute("data-value")||"";r&&(r.tagName==="line"?r.style.stroke=n:r.style.fill=n)}},fc=rp});var sp,mc,ap=T(()=>{n0();sp=class extends $n{constructor(e,t){super(e),this.container.classList.add("ql-icon-picker"),Array.from(this.container.querySelectorAll(".ql-picker-item")).forEach(r=>{r.innerHTML=t[r.getAttribute("data-value")||""]}),this.defaultItem=this.container.querySelector(".ql-selected"),this.selectItem(this.defaultItem)}selectItem(e,t){super.selectItem(e,t);let r=e||this.defaultItem;if(r!=null){if(this.label.innerHTML===r.innerHTML)return;this.label.innerHTML=r.innerHTML}}},mc=sp});var kP,op,pc,lp=T(()=>{kP=i=>{let{overflowY:e}=getComputedStyle(i,null);return e!=="visible"&&e!=="clip"},op=class{constructor(e,t){this.quill=e,this.boundsContainer=t||document.body,this.root=e.addContainer("ql-tooltip"),this.root.innerHTML=this.constructor.TEMPLATE,kP(this.quill.root)&&this.quill.root.addEventListener("scroll",()=>{this.root.style.marginTop=`${-1*this.quill.root.scrollTop}px`}),this.hide()}hide(){this.root.classList.add("ql-hidden")}position(e){let t=e.left+e.width/2-this.root.offsetWidth/2,r=e.bottom+this.quill.root.scrollTop;this.root.style.left=`${t}px`,this.root.style.top=`${r}px`,this.root.classList.remove("ql-flip");let n=this.boundsContainer.getBoundingClientRect(),s=this.root.getBoundingClientRect(),a=0;if(s.right>n.right&&(a=n.right-s.right,this.root.style.left=`${t+a}px`),s.leftn.bottom){let o=s.bottom-s.top,l=e.bottom-e.top+o;this.root.style.top=`${r-l}px`,this.root.classList.add("ql-flip")}return a}show(){this.root.classList.remove("ql-editing"),this.root.classList.remove("ql-hidden")}},pc=op});function RP(i){let e=i.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtube\.com\/watch.*v=([a-zA-Z0-9_-]+)/)||i.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtu\.be\/([a-zA-Z0-9_-]+)/);return e?`${e[1]||"https"}://www.youtube.com/embed/${e[2]}?showinfo=0`:(e=i.match(/^(?:(https?):\/\/)?(?:www\.)?vimeo\.com\/(\d+)/))?`${e[1]||"https"}://player.vimeo.com/video/${e[2]}/`:i}function s0(i,e){let t=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!1;e.forEach(r=>{let n=document.createElement("option");r===t?n.setAttribute("selected","selected"):n.setAttribute("value",String(r)),i.appendChild(n)})}var CP,_P,LP,IP,zP,rn,so,hp=T(()=>{kn();Qr();km();np();ap();n0();lp();CP=[!1,"center","right","justify"],_P=["#000000","#e60000","#ff9900","#ffff00","#008a00","#0066cc","#9933ff","#ffffff","#facccc","#ffebcc","#ffffcc","#cce8cc","#cce0f5","#ebd6ff","#bbbbbb","#f06666","#ffc266","#ffff66","#66b966","#66a3e0","#c285ff","#888888","#a10000","#b26b00","#b2b200","#006100","#0047b2","#6b24b2","#444444","#5c0000","#663d00","#666600","#003700","#002966","#3d1466"],LP=[!1,"serif","monospace"],IP=["1","2","3",!1],zP=["small",!1,"large","huge"],rn=class extends Za{constructor(e,t){super(e,t);let r=n=>{if(!document.body.contains(e.root)){document.body.removeEventListener("click",r);return}this.tooltip!=null&&!this.tooltip.root.contains(n.target)&&document.activeElement!==this.tooltip.textbox&&!this.quill.hasFocus()&&this.tooltip.hide(),this.pickers!=null&&this.pickers.forEach(s=>{s.container.contains(n.target)||s.close()})};e.emitter.listenDOM("click",document.body,r)}addModule(e){let t=super.addModule(e);return e==="toolbar"&&this.extendToolbar(t),t}buildButtons(e,t){Array.from(e).forEach(r=>{(r.getAttribute("class")||"").split(/\s+/).forEach(s=>{if(s.startsWith("ql-")&&(s=s.slice(3),t[s]!=null))if(s==="direction")r.innerHTML=t[s][""]+t[s].rtl;else if(typeof t[s]=="string")r.innerHTML=t[s];else{let a=r.value||"";a!=null&&t[s][a]&&(r.innerHTML=t[s][a])}})})}buildPickers(e,t){this.pickers=Array.from(e).map(n=>{if(n.classList.contains("ql-align")&&(n.querySelector("option")==null&&s0(n,CP),typeof t.align=="object"))return new mc(n,t.align);if(n.classList.contains("ql-background")||n.classList.contains("ql-color")){let s=n.classList.contains("ql-background")?"background":"color";return n.querySelector("option")==null&&s0(n,_P,s==="background"?"#ffffff":"#000000"),new fc(n,t[s])}return n.querySelector("option")==null&&(n.classList.contains("ql-font")?s0(n,LP):n.classList.contains("ql-header")?s0(n,IP):n.classList.contains("ql-size")&&s0(n,zP)),new $n(n)});let r=()=>{this.pickers.forEach(n=>{n.update()})};this.quill.on(J.events.EDITOR_CHANGE,r)}};rn.DEFAULTS=ri({},Za.DEFAULTS,{modules:{toolbar:{handlers:{formula(){this.quill.theme.tooltip.edit("formula")},image(){let i=this.container.querySelector("input.ql-image[type=file]");i==null&&(i=document.createElement("input"),i.setAttribute("type","file"),i.setAttribute("accept",this.quill.uploader.options.mimetypes.join(", ")),i.classList.add("ql-image"),i.addEventListener("change",()=>{let e=this.quill.getSelection(!0);this.quill.uploader.upload(e,i.files),i.value=""}),this.container.appendChild(i)),i.click()},video(){this.quill.theme.tooltip.edit("video")}}}}});so=class extends pc{constructor(e,t){super(e,t),this.textbox=this.root.querySelector('input[type="text"]'),this.listen()}listen(){this.textbox.addEventListener("keydown",e=>{e.key==="Enter"?(this.save(),e.preventDefault()):e.key==="Escape"&&(this.cancel(),e.preventDefault())})}cancel(){this.hide(),this.restoreFocus()}edit(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"link",t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:null;if(this.root.classList.remove("ql-hidden"),this.root.classList.add("ql-editing"),this.textbox==null)return;t!=null?this.textbox.value=t:e!==this.root.getAttribute("data-mode")&&(this.textbox.value="");let r=this.quill.getBounds(this.quill.selection.savedRange);r!=null&&this.position(r),this.textbox.select(),this.textbox.setAttribute("placeholder",this.textbox.getAttribute(`data-${e}`)||""),this.root.setAttribute("data-mode",e)}restoreFocus(){this.quill.focus({preventScroll:!0})}save(){let{value:e}=this.textbox;switch(this.root.getAttribute("data-mode")){case"link":{let{scrollTop:t}=this.quill.root;this.linkRange?(this.quill.formatText(this.linkRange,"link",e,J.sources.USER),delete this.linkRange):(this.restoreFocus(),this.quill.format("link",e,J.sources.USER)),this.quill.root.scrollTop=t;break}case"video":e=RP(e);case"formula":{if(!e)break;let t=this.quill.getSelection(!0);if(t!=null){let r=t.index+t.length;this.quill.insertEmbed(r,this.root.getAttribute("data-mode"),e,J.sources.USER),this.root.getAttribute("data-mode")==="formula"&&this.quill.insertText(r+1," ",J.sources.USER),this.quill.setSelection(r+2,J.sources.USER)}break}default:}this.textbox.value="",this.hide()}}});var DP,gc,a0,uv=T(()=>{kn();Qr();hp();Dl();uc();oi();DP=[["bold","italic","link"],[{header:1},{header:2},"blockquote"]],gc=class extends so{constructor(e,t){super(e,t),this.quill.on(J.events.EDITOR_CHANGE,(r,n,s,a)=>{if(r===J.events.SELECTION_CHANGE)if(n!=null&&n.length>0&&a===J.sources.USER){this.show(),this.root.style.left="0px",this.root.style.width="",this.root.style.width=`${this.root.offsetWidth}px`;let o=this.quill.getLines(n.index,n.length);if(o.length===1){let l=this.quill.getBounds(n);l!=null&&this.position(l)}else{let l=o[o.length-1],h=this.quill.getIndex(l),d=Math.min(l.length()-1,n.index+n.length-h),c=this.quill.getBounds(new $t(h,d));c!=null&&this.position(c)}}else document.activeElement!==this.textbox&&this.quill.hasFocus()&&this.hide()})}listen(){super.listen(),this.root.querySelector(".ql-close").addEventListener("click",()=>{this.root.classList.remove("ql-editing")}),this.quill.on(J.events.SCROLL_OPTIMIZE,()=>{setTimeout(()=>{if(this.root.classList.contains("ql-hidden"))return;let e=this.quill.getSelection();if(e!=null){let t=this.quill.getBounds(e);t!=null&&this.position(t)}},1)})}cancel(){this.show()}position(e){let t=super.position(e),r=this.root.querySelector(".ql-tooltip-arrow");return r.style.marginLeft="",t!==0&&(r.style.marginLeft=`${-1*t-r.offsetWidth/2}px`),t}};U(gc,"TEMPLATE",['','
    ','','',"
    "].join(""));a0=class extends rn{constructor(e,t){t.modules.toolbar!=null&&t.modules.toolbar.container==null&&(t.modules.toolbar.container=DP),super(e,t),this.quill.container.classList.add("ql-bubble")}extendToolbar(e){this.tooltip=new gc(this.quill,this.options.bounds),e.container!=null&&(this.tooltip.root.appendChild(e.container),this.buildButtons(e.container.querySelectorAll("button"),Un),this.buildPickers(e.container.querySelectorAll("select"),Un))}};a0.DEFAULTS=ri({},rn.DEFAULTS,{modules:{toolbar:{handlers:{link(i){i?this.quill.theme.tooltip.edit():this.quill.format("link",!1,B.sources.USER)}}}}})});var OP,xc,yc,fv,mv=T(()=>{kn();Qr();hp();Kl();Dl();uc();oi();OP=[[{header:["1","2","3",!1]}],["bold","italic","underline","link"],[{list:"ordered"},{list:"bullet"}],["clean"]],xc=class extends so{constructor(){super(...arguments);U(this,"preview",this.root.querySelector("a.ql-preview"))}listen(){super.listen(),this.root.querySelector("a.ql-action").addEventListener("click",t=>{this.root.classList.contains("ql-editing")?this.save():this.edit("link",this.preview.textContent),t.preventDefault()}),this.root.querySelector("a.ql-remove").addEventListener("click",t=>{if(this.linkRange!=null){let r=this.linkRange;this.restoreFocus(),this.quill.formatText(r,"link",!1,J.sources.USER),delete this.linkRange}t.preventDefault(),this.hide()}),this.quill.on(J.events.SELECTION_CHANGE,(t,r,n)=>{if(t!=null){if(t.length===0&&n===J.sources.USER){let[s,a]=this.quill.scroll.descendant(xi,t.index);if(s!=null){this.linkRange=new $t(t.index-a,s.length());let o=xi.formats(s.domNode);this.preview.textContent=o,this.preview.setAttribute("href",o),this.show();let l=this.quill.getBounds(this.linkRange);l!=null&&this.position(l);return}}else delete this.linkRange;this.hide()}})}show(){super.show(),this.root.removeAttribute("data-mode")}};U(xc,"TEMPLATE",['','','',''].join(""));yc=class extends rn{constructor(e,t){t.modules.toolbar!=null&&t.modules.toolbar.container==null&&(t.modules.toolbar.container=OP),super(e,t),this.quill.container.classList.add("ql-snow")}extendToolbar(e){e.container!=null&&(e.container.classList.add("ql-snow"),this.buildButtons(e.container.querySelectorAll("button"),Un),this.buildPickers(e.container.querySelectorAll("select"),Un),this.tooltip=new xc(this.quill,this.options.bounds),e.container.querySelector(".ql-link")&&this.quill.keyboard.addBinding({key:"k",shortKey:!0},(t,r)=>{e.handlers.link.call(e,!r.format.link)}))}};yc.DEFAULTS=ri({},rn.DEFAULTS,{modules:{toolbar:{handlers:{link(i){if(i){let e=this.quill.getSelection();if(e==null||e.length===0)return;let t=this.quill.getText(e);/^\S+@\S+\.\S+$/.test(t)&&t.indexOf("mailto:")!==0&&(t=`mailto:${t}`);let{tooltip:r}=this.quill.theme;r.edit("link",t)}else this.quill.format("link",!1,B.sources.USER)}}}}});fv=yc});var vc,pv=T(()=>{Qm();Rm();Pm();Iy();Ry();Oy();By();Dm();Zd();Hm();$m();hc();Fy();Kl();Hy();$y();Gy();Wy();Ky();Jy();Qd();ev();nv();lv();uc();n0();np();ap();lp();uv();mv();Qm();lc.register({"attributors/attribute/direction":Jd,"attributors/class/align":zm,"attributors/class/background":hy,"attributors/class/color":ly,"attributors/class/direction":Bm,"attributors/class/font":qm,"attributors/class/size":Um,"attributors/style/align":Kd,"attributors/style/background":ql,"attributors/style/color":Fl,"attributors/style/direction":ec,"attributors/style/font":tc,"attributors/style/size":ic},!0);lc.register({"formats/align":zm,"formats/direction":Bm,"formats/indent":Ly,"formats/background":ql,"formats/color":Fl,"formats/font":qm,"formats/size":Um,"formats/blockquote":zy,"formats/code-block":st,"formats/header":Dy,"formats/list":Hn,"formats/bold":to,"formats/code":eo,"formats/italic":Py,"formats/link":xi,"formats/script":qy,"formats/strike":Uy,"formats/underline":jy,"formats/formula":Vy,"formats/image":Xy,"formats/video":Qy,"modules/syntax":i0,"modules/table":rv,"modules/toolbar":no,"themes/bubble":a0,"themes/snow":fv,"ui/icons":Un,"ui/picker":$n,"ui/icon-picker":mc,"ui/color-picker":fc,"ui/tooltip":pc},!0);vc=lc});var gv,xv,yv=T(()=>{gv=i=>` +`,{[st.blotName]:t}),a.insert(o)),s)],new WeakMap)}};l0.DEFAULTS={hljs:window.hljs,interval:1e3,languages:[{key:"plain",label:"Plain"},{key:"bash",label:"Bash"},{key:"cpp",label:"C++"},{key:"cs",label:"C#"},{key:"css",label:"CSS"},{key:"diff",label:"Diff"},{key:"xml",label:"HTML/XML"},{key:"java",label:"Java"},{key:"javascript",label:"JavaScript"},{key:"markdown",label:"Markdown"},{key:"php",label:"PHP"},{key:"python",label:"Python"},{key:"ruby",label:"Ruby"},{key:"sql",label:"SQL"}]}});function vc(){return`row-${Math.random().toString(36).slice(2,6)}`}var h0,Fi,qi,bi,tn,pv=T(()=>{Ri();eo();h0=class h0 extends Oe{static create(e){let t=super.create();return e?t.setAttribute("data-row",e):t.setAttribute("data-row",vc()),t}static formats(e){if(e.hasAttribute("data-row"))return e.getAttribute("data-row")}cellOffset(){return this.parent?this.parent.children.indexOf(this):-1}format(e,t){e===h0.blotName&&t?this.domNode.setAttribute("data-row",t):super.format(e,t)}row(){return this.parent}rowOffset(){return this.row()?this.row().rowOffset():-1}table(){return this.row()&&this.row().table()}};U(h0,"blotName","table"),U(h0,"tagName","TD");Fi=h0,qi=class extends Bi{checkMerge(){if(super.checkMerge()&&this.next.children.head!=null){let e=this.children.head.formats(),t=this.children.tail.formats(),r=this.next.children.head.formats(),n=this.next.children.tail.formats();return e.table===t.table&&e.table===r.table&&e.table===n.table}return!1}optimize(e){super.optimize(e),this.children.forEach(t=>{if(t.next==null)return;let r=t.formats(),n=t.next.formats();if(r.table!==n.table){let s=this.splitAfter(t);s&&s.optimize(),this.prev&&this.prev.optimize()}})}rowOffset(){return this.parent?this.parent.children.indexOf(this):-1}table(){return this.parent&&this.parent.parent}};U(qi,"blotName","table-row"),U(qi,"tagName","TR");bi=class extends Bi{};U(bi,"blotName","table-body"),U(bi,"tagName","TBODY");tn=class extends Bi{balanceCells(){let e=this.descendants(qi),t=e.reduce((r,n)=>Math.max(n.children.length,r),0);e.forEach(r=>{new Array(t-r.children.length).fill(0).forEach(()=>{let n;r.children.head!=null&&(n=Fi.formats(r.children.head.domNode));let s=this.scroll.create(Fi.blotName,n);r.appendChild(s),s.optimize()})})}cells(e){return this.rows().map(t=>t.children.at(e))}deleteColumn(e){let[t]=this.descendant(bi);t==null||t.children.head==null||t.children.forEach(r=>{let n=r.children.at(e);n?.remove()})}insertColumn(e){let[t]=this.descendant(bi);t==null||t.children.head==null||t.children.forEach(r=>{let n=r.children.at(e),s=Fi.formats(r.children.head.domNode),a=this.scroll.create(Fi.blotName,s);r.insertBefore(a,n)})}insertRow(e){let[t]=this.descendant(bi);if(t==null||t.children.head==null)return;let r=vc(),n=this.scroll.create(qi.blotName);t.children.head.children.forEach(()=>{let a=this.scroll.create(Fi.blotName,r);n.appendChild(a)});let s=t.children.at(e);t.insertBefore(n,s)}rows(){let e=this.children.head;return e==null?[]:e.children.map(t=>t)}};U(tn,"blotName","table-container"),U(tn,"tagName","TABLE");tn.allowedChildren=[bi];bi.requiredContainer=tn;bi.allowedChildren=[qi];qi.requiredContainer=bi;qi.allowedChildren=[Fi];Fi.requiredContainer=qi});var gv,up,xv,yv=T(()=>{gv=yt(xi(),1);hi();Oi();pv();up=class extends Je{static register(){B.register(Fi),B.register(qi),B.register(bi),B.register(tn)}constructor(){super(...arguments),this.listenBalanceCells()}balanceTables(){this.quill.scroll.descendants(tn).forEach(e=>{e.balanceCells()})}deleteColumn(){let[e,,t]=this.getTable();t!=null&&(e.deleteColumn(t.cellOffset()),this.quill.update(B.sources.USER))}deleteRow(){let[,e]=this.getTable();e!=null&&(e.remove(),this.quill.update(B.sources.USER))}deleteTable(){let[e]=this.getTable();if(e==null)return;let t=e.offset();e.remove(),this.quill.update(B.sources.USER),this.quill.setSelection(t,B.sources.SILENT)}getTable(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.quill.getSelection();if(e==null)return[null,null,null,-1];let[t,r]=this.quill.getLine(e.index);if(t==null||t.statics.blotName!==Fi.blotName)return[null,null,null,-1];let n=t.parent;return[n.parent.parent,n,t,r]}insertColumn(e){let t=this.quill.getSelection();if(!t)return;let[r,n,s]=this.getTable(t);if(s==null)return;let a=s.cellOffset();r.insertColumn(a+e),this.quill.update(B.sources.USER);let o=n.rowOffset();e===0&&(o+=1),this.quill.setSelection(t.index+o,t.length,B.sources.SILENT)}insertColumnLeft(){this.insertColumn(0)}insertColumnRight(){this.insertColumn(1)}insertRow(e){let t=this.quill.getSelection();if(!t)return;let[r,n,s]=this.getTable(t);if(s==null)return;let a=n.rowOffset();r.insertRow(a+e),this.quill.update(B.sources.USER),e>0?this.quill.setSelection(t,B.sources.SILENT):this.quill.setSelection(t.index+n.children.length,t.length,B.sources.SILENT)}insertRowAbove(){this.insertRow(0)}insertRowBelow(){this.insertRow(1)}insertTable(e,t){let r=this.quill.getSelection();if(r==null)return;let n=new Array(e).fill(0).reduce(s=>{let a=new Array(t).fill(` +`).join("");return s.insert(a,{table:vc()})},new gv.default().retain(r.index));this.quill.updateContents(n,B.sources.USER),this.quill.setSelection(r.index,B.sources.SILENT),this.balanceTables()}listenBalanceCells(){this.quill.on(B.events.SCROLL_OPTIMIZE,e=>{e.some(t=>["TD","TR","TBODY","TABLE"].includes(t.target.tagName)?(this.quill.once(B.events.TEXT_CHANGE,(r,n,s)=>{s===B.sources.USER&&this.balanceTables()}),!0):!1)})}},xv=up});function bv(i,e,t){let r=document.createElement("button");r.setAttribute("type","button"),r.classList.add(`ql-${e}`),r.setAttribute("aria-pressed","false"),t!=null?(r.value=t,r.setAttribute("aria-label",`${e}: ${t}`)):r.setAttribute("aria-label",e),i.appendChild(r)}function xP(i,e){Array.isArray(e[0])||(e=[e]),e.forEach(t=>{let r=document.createElement("span");r.classList.add("ql-formats"),t.forEach(n=>{if(typeof n=="string")bv(r,n);else{let s=Object.keys(n)[0],a=n[s];Array.isArray(a)?yP(r,s,a):bv(r,s,a)}}),i.appendChild(r)})}function yP(i,e,t){let r=document.createElement("select");r.classList.add(`ql-${e}`),t.forEach(n=>{let s=document.createElement("option");n!==!1?s.setAttribute("value",String(n)):s.setAttribute("selected","selected"),r.appendChild(s)}),i.appendChild(r)}var wv,vv,so,Mv=T(()=>{wv=yt(xi(),1);De();hi();Cs();Oi();vv=yi("quill:toolbar"),so=class extends Je{constructor(e,t){if(super(e,t),Array.isArray(this.options.container)){let r=document.createElement("div");r.setAttribute("role","toolbar"),xP(r,this.options.container),e.container?.parentNode?.insertBefore(r,e.container),this.container=r}else typeof this.options.container=="string"?this.container=document.querySelector(this.options.container):this.container=this.options.container;if(!(this.container instanceof HTMLElement)){vv.error("Container required for toolbar",this.options);return}this.container.classList.add("ql-toolbar"),this.controls=[],this.handlers={},this.options.handlers&&Object.keys(this.options.handlers).forEach(r=>{let n=this.options.handlers?.[r];n&&this.addHandler(r,n)}),Array.from(this.container.querySelectorAll("button, select")).forEach(r=>{this.attach(r)}),this.quill.on(B.events.EDITOR_CHANGE,()=>{let[r]=this.quill.selection.getRange();this.update(r)})}addHandler(e,t){this.handlers[e]=t}attach(e){let t=Array.from(e.classList).find(n=>n.indexOf("ql-")===0);if(!t)return;if(t=t.slice(3),e.tagName==="BUTTON"&&e.setAttribute("type","button"),this.handlers[t]==null&&this.quill.scroll.query(t)==null){vv.warn("ignoring attaching to nonexistent format",t,e);return}let r=e.tagName==="SELECT"?"change":"click";e.addEventListener(r,n=>{let s;if(e.tagName==="SELECT"){if(e.selectedIndex<0)return;let o=e.options[e.selectedIndex];o.hasAttribute("selected")?s=!1:s=o.value||!1}else e.classList.contains("ql-active")?s=!1:s=e.value||!e.hasAttribute("value"),n.preventDefault();this.quill.focus();let[a]=this.quill.selection.getRange();if(this.handlers[t]!=null)this.handlers[t].call(this,s);else if(this.quill.scroll.query(t).prototype instanceof Ze){if(s=prompt(`Enter ${t}`),!s)return;this.quill.updateContents(new wv.default().retain(a.index).delete(a.length).insert({[t]:s}),B.sources.USER)}else this.quill.format(t,s,B.sources.USER);this.update(a)}),this.controls.push([t,e])}update(e){let t=e==null?{}:this.quill.getFormat(e);this.controls.forEach(r=>{let[n,s]=r;if(s.tagName==="SELECT"){let a=null;if(e==null)a=null;else if(t[n]==null)a=s.querySelector("option[selected]");else if(!Array.isArray(t[n])){let o=t[n];typeof o=="string"&&(o=o.replace(/"/g,'\\"')),a=s.querySelector(`option[value="${o}"]`)}a==null?(s.value="",s.selectedIndex=-1):a.selected=!0}else if(e==null)s.classList.remove("ql-active"),s.setAttribute("aria-pressed","false");else if(s.hasAttribute("value")){let a=t[n],o=a===s.getAttribute("value")||a!=null&&a.toString()===s.getAttribute("value")||a==null&&!s.getAttribute("value");s.classList.toggle("ql-active",o),s.setAttribute("aria-pressed",o.toString())}else{let a=t[n]!=null;s.classList.toggle("ql-active",a),s.setAttribute("aria-pressed",a.toString())}})}};so.DEFAULTS={};so.DEFAULTS={container:null,handlers:{clean(){let i=this.quill.getSelection();if(i!=null)if(i.length===0){let e=this.quill.getFormat();Object.keys(e).forEach(t=>{this.quill.scroll.query(t,X.INLINE)!=null&&this.quill.format(t,!1,B.sources.USER)})}else this.quill.removeFormat(i.index,i.length,B.sources.USER)},direction(i){let{align:e}=this.quill.getFormat();i==="rtl"&&e==null?this.quill.format("align","right",B.sources.USER):!i&&e==="right"&&this.quill.format("align",!1,B.sources.USER),this.quill.format("direction",i,B.sources.USER)},indent(i){let e=this.quill.getSelection(),t=this.quill.getFormat(e),r=parseInt(t.indent||0,10);if(i==="+1"||i==="-1"){let n=i==="+1"?1:-1;t.direction==="rtl"&&(n*=-1),this.quill.format("indent",r+n,B.sources.USER)}},link(i){i===!0&&(i=prompt("Enter link URL:")),this.quill.format("link",i,B.sources.USER)},list(i){let e=this.quill.getSelection(),t=this.quill.getFormat(e);i==="check"?t.list==="checked"||t.list==="unchecked"?this.quill.format("list",!1,B.sources.USER):this.quill.format("list","unchecked",B.sources.USER):this.quill.format("list",i,B.sources.USER)}}}});var vP,bP,wP,MP,TP,NP,EP,SP,Tv,AP,kP,CP,_P,LP,IP,zP,RP,DP,OP,BP,PP,FP,qP,HP,UP,$P,jP,GP,WP,VP,YP,XP,KP,Hn,bc=T(()=>{vP='',bP='',wP='',MP='',TP='',NP='',EP='',SP='',Tv='',AP='',kP='',CP='',_P='',LP='',IP='',zP='',RP='',DP='',OP='',BP='',PP='',FP='',qP='',HP='',UP='',$P='',jP='',GP='',WP='',VP='',YP='',XP='',KP='',Hn={align:{"":vP,center:bP,right:wP,justify:MP},background:TP,blockquote:NP,bold:EP,clean:SP,code:Tv,"code-block":Tv,color:AP,direction:{"":kP,rtl:CP},formula:_P,header:{1:LP,2:IP,3:zP,4:RP,5:DP,6:OP},italic:BP,image:PP,indent:{"+1":FP,"-1":qP},link:HP,list:{bullet:UP,check:$P,ordered:jP},script:{sub:GP,super:WP},strike:VP,table:YP,underline:XP,video:KP}});function Ev(i,e){i.setAttribute(e,`${i.getAttribute(e)!=="true"}`)}var ZP,Nv,fp,Un,d0=T(()=>{ZP='',Nv=0;fp=class{constructor(e){this.select=e,this.container=document.createElement("span"),this.buildPicker(),this.select.style.display="none",this.select.parentNode.insertBefore(this.container,this.select),this.label.addEventListener("mousedown",()=>{this.togglePicker()}),this.label.addEventListener("keydown",t=>{switch(t.key){case"Enter":this.togglePicker();break;case"Escape":this.escape(),t.preventDefault();break;default:}}),this.select.addEventListener("change",this.update.bind(this))}togglePicker(){this.container.classList.toggle("ql-expanded"),Ev(this.label,"aria-expanded"),Ev(this.options,"aria-hidden")}buildItem(e){let t=document.createElement("span");t.tabIndex="0",t.setAttribute("role","button"),t.classList.add("ql-picker-item");let r=e.getAttribute("value");return r&&t.setAttribute("data-value",r),e.textContent&&t.setAttribute("data-label",e.textContent),t.addEventListener("click",()=>{this.selectItem(t,!0)}),t.addEventListener("keydown",n=>{switch(n.key){case"Enter":this.selectItem(t,!0),n.preventDefault();break;case"Escape":this.escape(),n.preventDefault();break;default:}}),t}buildLabel(){let e=document.createElement("span");return e.classList.add("ql-picker-label"),e.innerHTML=ZP,e.tabIndex="0",e.setAttribute("role","button"),e.setAttribute("aria-expanded","false"),this.container.appendChild(e),e}buildOptions(){let e=document.createElement("span");e.classList.add("ql-picker-options"),e.setAttribute("aria-hidden","true"),e.tabIndex="-1",e.id=`ql-picker-options-${Nv}`,Nv+=1,this.label.setAttribute("aria-controls",e.id),this.options=e,Array.from(this.select.options).forEach(t=>{let r=this.buildItem(t);e.appendChild(r),t.selected===!0&&this.selectItem(r)}),this.container.appendChild(e)}buildPicker(){Array.from(this.select.attributes).forEach(e=>{this.container.setAttribute(e.name,e.value)}),this.container.classList.add("ql-picker"),this.label=this.buildLabel(),this.buildOptions()}escape(){this.close(),setTimeout(()=>this.label.focus(),1)}close(){this.container.classList.remove("ql-expanded"),this.label.setAttribute("aria-expanded","false"),this.options.setAttribute("aria-hidden","true")}selectItem(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1,r=this.container.querySelector(".ql-selected");e!==r&&(r?.classList.remove("ql-selected"),e!=null&&(e.classList.add("ql-selected"),this.select.selectedIndex=Array.from(e.parentNode.children).indexOf(e),e.hasAttribute("data-value")?this.label.setAttribute("data-value",e.getAttribute("data-value")):this.label.removeAttribute("data-value"),e.hasAttribute("data-label")?this.label.setAttribute("data-label",e.getAttribute("data-label")):this.label.removeAttribute("data-label"),t&&(this.select.dispatchEvent(new Event("change")),this.close())))}update(){let e;if(this.select.selectedIndex>-1){let r=this.container.querySelector(".ql-picker-options").children[this.select.selectedIndex];e=this.select.options[this.select.selectedIndex],this.selectItem(r)}else this.selectItem(null);let t=e!=null&&e!==this.select.querySelector("option[selected]");this.label.classList.toggle("ql-active",t)}},Un=fp});var mp,wc,pp=T(()=>{d0();mp=class extends Un{constructor(e,t){super(e),this.label.innerHTML=t,this.container.classList.add("ql-color-picker"),Array.from(this.container.querySelectorAll(".ql-picker-item")).slice(0,7).forEach(r=>{r.classList.add("ql-primary")})}buildItem(e){let t=super.buildItem(e);return t.style.backgroundColor=e.getAttribute("value")||"",t}selectItem(e,t){super.selectItem(e,t);let r=this.label.querySelector(".ql-color-label"),n=e&&e.getAttribute("data-value")||"";r&&(r.tagName==="line"?r.style.stroke=n:r.style.fill=n)}},wc=mp});var gp,Mc,xp=T(()=>{d0();gp=class extends Un{constructor(e,t){super(e),this.container.classList.add("ql-icon-picker"),Array.from(this.container.querySelectorAll(".ql-picker-item")).forEach(r=>{r.innerHTML=t[r.getAttribute("data-value")||""]}),this.defaultItem=this.container.querySelector(".ql-selected"),this.selectItem(this.defaultItem)}selectItem(e,t){super.selectItem(e,t);let r=e||this.defaultItem;if(r!=null){if(this.label.innerHTML===r.innerHTML)return;this.label.innerHTML=r.innerHTML}}},Mc=gp});var QP,yp,Tc,vp=T(()=>{QP=i=>{let{overflowY:e}=getComputedStyle(i,null);return e!=="visible"&&e!=="clip"},yp=class{constructor(e,t){this.quill=e,this.boundsContainer=t||document.body,this.root=e.addContainer("ql-tooltip"),this.root.innerHTML=this.constructor.TEMPLATE,QP(this.quill.root)&&this.quill.root.addEventListener("scroll",()=>{this.root.style.marginTop=`${-1*this.quill.root.scrollTop}px`}),this.hide()}hide(){this.root.classList.add("ql-hidden")}position(e){let t=e.left+e.width/2-this.root.offsetWidth/2,r=e.bottom+this.quill.root.scrollTop;this.root.style.left=`${t}px`,this.root.style.top=`${r}px`,this.root.classList.remove("ql-flip");let n=this.boundsContainer.getBoundingClientRect(),s=this.root.getBoundingClientRect(),a=0;if(s.right>n.right&&(a=n.right-s.right,this.root.style.left=`${t+a}px`),s.leftn.bottom){let o=s.bottom-s.top,l=e.bottom-e.top+o;this.root.style.top=`${r-l}px`,this.root.classList.add("ql-flip")}return a}show(){this.root.classList.remove("ql-editing"),this.root.classList.remove("ql-hidden")}},Tc=yp});function nF(i){let e=i.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtube\.com\/watch.*v=([a-zA-Z0-9_-]+)/)||i.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtu\.be\/([a-zA-Z0-9_-]+)/);return e?`${e[1]||"https"}://www.youtube.com/embed/${e[2]}?showinfo=0`:(e=i.match(/^(?:(https?):\/\/)?(?:www\.)?vimeo\.com\/(\d+)/))?`${e[1]||"https"}://player.vimeo.com/video/${e[2]}/`:i}function c0(i,e){let t=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!1;e.forEach(r=>{let n=document.createElement("option");r===t?n.setAttribute("selected","selected"):n.setAttribute("value",String(r)),i.appendChild(n)})}var JP,eF,tF,iF,rF,rn,ao,bp=T(()=>{An();Qr();Fm();pp();xp();d0();vp();JP=[!1,"center","right","justify"],eF=["#000000","#e60000","#ff9900","#ffff00","#008a00","#0066cc","#9933ff","#ffffff","#facccc","#ffebcc","#ffffcc","#cce8cc","#cce0f5","#ebd6ff","#bbbbbb","#f06666","#ffc266","#ffff66","#66b966","#66a3e0","#c285ff","#888888","#a10000","#b26b00","#b2b200","#006100","#0047b2","#6b24b2","#444444","#5c0000","#663d00","#666600","#003700","#002966","#3d1466"],tF=[!1,"serif","monospace"],iF=["1","2","3",!1],rF=["small",!1,"large","huge"],rn=class extends Qa{constructor(e,t){super(e,t);let r=n=>{if(!document.body.contains(e.root)){document.body.removeEventListener("click",r);return}this.tooltip!=null&&!this.tooltip.root.contains(n.target)&&document.activeElement!==this.tooltip.textbox&&!this.quill.hasFocus()&&this.tooltip.hide(),this.pickers!=null&&this.pickers.forEach(s=>{s.container.contains(n.target)||s.close()})};e.emitter.listenDOM("click",document.body,r)}addModule(e){let t=super.addModule(e);return e==="toolbar"&&this.extendToolbar(t),t}buildButtons(e,t){Array.from(e).forEach(r=>{(r.getAttribute("class")||"").split(/\s+/).forEach(s=>{if(s.startsWith("ql-")&&(s=s.slice(3),t[s]!=null))if(s==="direction")r.innerHTML=t[s][""]+t[s].rtl;else if(typeof t[s]=="string")r.innerHTML=t[s];else{let a=r.value||"";a!=null&&t[s][a]&&(r.innerHTML=t[s][a])}})})}buildPickers(e,t){this.pickers=Array.from(e).map(n=>{if(n.classList.contains("ql-align")&&(n.querySelector("option")==null&&c0(n,JP),typeof t.align=="object"))return new Mc(n,t.align);if(n.classList.contains("ql-background")||n.classList.contains("ql-color")){let s=n.classList.contains("ql-background")?"background":"color";return n.querySelector("option")==null&&c0(n,eF,s==="background"?"#ffffff":"#000000"),new wc(n,t[s])}return n.querySelector("option")==null&&(n.classList.contains("ql-font")?c0(n,tF):n.classList.contains("ql-header")?c0(n,iF):n.classList.contains("ql-size")&&c0(n,rF)),new Un(n)});let r=()=>{this.pickers.forEach(n=>{n.update()})};this.quill.on(ee.events.EDITOR_CHANGE,r)}};rn.DEFAULTS=si({},Qa.DEFAULTS,{modules:{toolbar:{handlers:{formula(){this.quill.theme.tooltip.edit("formula")},image(){let i=this.container.querySelector("input.ql-image[type=file]");i==null&&(i=document.createElement("input"),i.setAttribute("type","file"),i.setAttribute("accept",this.quill.uploader.options.mimetypes.join(", ")),i.classList.add("ql-image"),i.addEventListener("change",()=>{let e=this.quill.getSelection(!0);this.quill.uploader.upload(e,i.files),i.value=""}),this.container.appendChild(i)),i.click()},video(){this.quill.theme.tooltip.edit("video")}}}}});ao=class extends Tc{constructor(e,t){super(e,t),this.textbox=this.root.querySelector('input[type="text"]'),this.listen()}listen(){this.textbox.addEventListener("keydown",e=>{e.key==="Enter"?(this.save(),e.preventDefault()):e.key==="Escape"&&(this.cancel(),e.preventDefault())})}cancel(){this.hide(),this.restoreFocus()}edit(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"link",t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:null;if(this.root.classList.remove("ql-hidden"),this.root.classList.add("ql-editing"),this.textbox==null)return;t!=null?this.textbox.value=t:e!==this.root.getAttribute("data-mode")&&(this.textbox.value="");let r=this.quill.getBounds(this.quill.selection.savedRange);r!=null&&this.position(r),this.textbox.select(),this.textbox.setAttribute("placeholder",this.textbox.getAttribute(`data-${e}`)||""),this.root.setAttribute("data-mode",e)}restoreFocus(){this.quill.focus({preventScroll:!0})}save(){let{value:e}=this.textbox;switch(this.root.getAttribute("data-mode")){case"link":{let{scrollTop:t}=this.quill.root;this.linkRange?(this.quill.formatText(this.linkRange,"link",e,ee.sources.USER),delete this.linkRange):(this.restoreFocus(),this.quill.format("link",e,ee.sources.USER)),this.quill.root.scrollTop=t;break}case"video":e=nF(e);case"formula":{if(!e)break;let t=this.quill.getSelection(!0);if(t!=null){let r=t.index+t.length;this.quill.insertEmbed(r,this.root.getAttribute("data-mode"),e,ee.sources.USER),this.root.getAttribute("data-mode")==="formula"&&this.quill.insertText(r+1," ",ee.sources.USER),this.quill.setSelection(r+2,ee.sources.USER)}break}default:}this.textbox.value="",this.hide()}}});var sF,Nc,u0,Sv=T(()=>{An();Qr();bp();Hl();bc();hi();sF=[["bold","italic","link"],[{header:1},{header:2},"blockquote"]],Nc=class extends ao{constructor(e,t){super(e,t),this.quill.on(ee.events.EDITOR_CHANGE,(r,n,s,a)=>{if(r===ee.events.SELECTION_CHANGE)if(n!=null&&n.length>0&&a===ee.sources.USER){this.show(),this.root.style.left="0px",this.root.style.width="",this.root.style.width=`${this.root.offsetWidth}px`;let o=this.quill.getLines(n.index,n.length);if(o.length===1){let l=this.quill.getBounds(n);l!=null&&this.position(l)}else{let l=o[o.length-1],h=this.quill.getIndex(l),d=Math.min(l.length()-1,n.index+n.length-h),c=this.quill.getBounds(new $t(h,d));c!=null&&this.position(c)}}else document.activeElement!==this.textbox&&this.quill.hasFocus()&&this.hide()})}listen(){super.listen(),this.root.querySelector(".ql-close").addEventListener("click",()=>{this.root.classList.remove("ql-editing")}),this.quill.on(ee.events.SCROLL_OPTIMIZE,()=>{setTimeout(()=>{if(this.root.classList.contains("ql-hidden"))return;let e=this.quill.getSelection();if(e!=null){let t=this.quill.getBounds(e);t!=null&&this.position(t)}},1)})}cancel(){this.show()}position(e){let t=super.position(e),r=this.root.querySelector(".ql-tooltip-arrow");return r.style.marginLeft="",t!==0&&(r.style.marginLeft=`${-1*t-r.offsetWidth/2}px`),t}};U(Nc,"TEMPLATE",['','
    ','','',"
    "].join(""));u0=class extends rn{constructor(e,t){t.modules.toolbar!=null&&t.modules.toolbar.container==null&&(t.modules.toolbar.container=sF),super(e,t),this.quill.container.classList.add("ql-bubble")}extendToolbar(e){this.tooltip=new Nc(this.quill,this.options.bounds),e.container!=null&&(this.tooltip.root.appendChild(e.container),this.buildButtons(e.container.querySelectorAll("button"),Hn),this.buildPickers(e.container.querySelectorAll("select"),Hn))}};u0.DEFAULTS=si({},rn.DEFAULTS,{modules:{toolbar:{handlers:{link(i){i?this.quill.theme.tooltip.edit():this.quill.format("link",!1,B.sources.USER)}}}}})});var aF,Ec,Sc,Av,kv=T(()=>{An();Qr();bp();i0();Hl();bc();hi();aF=[[{header:["1","2","3",!1]}],["bold","italic","underline","link"],[{list:"ordered"},{list:"bullet"}],["clean"]],Ec=class extends ao{constructor(){super(...arguments);U(this,"preview",this.root.querySelector("a.ql-preview"))}listen(){super.listen(),this.root.querySelector("a.ql-action").addEventListener("click",t=>{this.root.classList.contains("ql-editing")?this.save():this.edit("link",this.preview.textContent),t.preventDefault()}),this.root.querySelector("a.ql-remove").addEventListener("click",t=>{if(this.linkRange!=null){let r=this.linkRange;this.restoreFocus(),this.quill.formatText(r,"link",!1,ee.sources.USER),delete this.linkRange}t.preventDefault(),this.hide()}),this.quill.on(ee.events.SELECTION_CHANGE,(t,r,n)=>{if(t!=null){if(t.length===0&&n===ee.sources.USER){let[s,a]=this.quill.scroll.descendant(vi,t.index);if(s!=null){this.linkRange=new $t(t.index-a,s.length());let o=vi.formats(s.domNode);this.preview.textContent=o,this.preview.setAttribute("href",o),this.show();let l=this.quill.getBounds(this.linkRange);l!=null&&this.position(l);return}}else delete this.linkRange;this.hide()}})}show(){super.show(),this.root.removeAttribute("data-mode")}};U(Ec,"TEMPLATE",['','','',''].join(""));Sc=class extends rn{constructor(e,t){t.modules.toolbar!=null&&t.modules.toolbar.container==null&&(t.modules.toolbar.container=aF),super(e,t),this.quill.container.classList.add("ql-snow")}extendToolbar(e){e.container!=null&&(e.container.classList.add("ql-snow"),this.buildButtons(e.container.querySelectorAll("button"),Hn),this.buildPickers(e.container.querySelectorAll("select"),Hn),this.tooltip=new Ec(this.quill,this.options.bounds),e.container.querySelector(".ql-link")&&this.quill.keyboard.addBinding({key:"k",shortKey:!0},(t,r)=>{e.handlers.link.call(e,!r.format.link)}))}};Sc.DEFAULTS=si({},rn.DEFAULTS,{modules:{toolbar:{handlers:{link(i){if(i){let e=this.quill.getSelection();if(e==null||e.length===0)return;let t=this.quill.getText(e);/^\S+@\S+\.\S+$/.test(t)&&t.indexOf("mailto:")!==0&&(t=`mailto:${t}`);let{tooltip:r}=this.quill.theme;r.edit("link",t)}else this.quill.format("link",!1,B.sources.USER)}}}}});Av=Sc});var Ac,Cv=T(()=>{hp();Gm();Xm();Wy();Yy();Ky();Zy();Wm();sc();Qm();ep();xc();Jy();i0();tv();rv();sv();ov();dv();fv();ac();mv();yv();Mv();bc();d0();pp();xp();vp();Sv();kv();hp();gc.register({"attributors/attribute/direction":oc,"attributors/class/align":jm,"attributors/class/background":Ty,"attributors/class/color":My,"attributors/class/direction":Ym,"attributors/class/font":Zm,"attributors/class/size":Jm,"attributors/style/align":nc,"attributors/style/background":Wl,"attributors/style/color":Gl,"attributors/style/direction":lc,"attributors/style/font":hc,"attributors/style/size":dc},!0);gc.register({"formats/align":jm,"formats/direction":Ym,"formats/indent":Gy,"formats/background":Wl,"formats/color":Gl,"formats/font":Zm,"formats/size":Jm,"formats/blockquote":Vy,"formats/code-block":st,"formats/header":Xy,"formats/list":qn,"formats/bold":io,"formats/code":to,"formats/italic":Qy,"formats/link":vi,"formats/script":ev,"formats/strike":iv,"formats/underline":nv,"formats/formula":av,"formats/image":hv,"formats/video":uv,"modules/syntax":l0,"modules/table":xv,"modules/toolbar":so,"themes/bubble":u0,"themes/snow":Av,"ui/icons":Hn,"ui/picker":Un,"ui/icon-picker":Mc,"ui/color-picker":wc,"ui/tooltip":Tc},!0);Ac=gc});var _v,Lv,Iv=T(()=>{_v=i=>` @font-face { font-family: 'KaTeX_AMS'; src: url(${i}fonts/KaTeX_AMS-Regular.woff2) format('woff2'), url(${i}fonts/KaTeX_AMS-Regular.woff) format('woff'), url(${i}fonts/KaTeX_AMS-Regular.ttf) format('truetype'); @@ -508,7 +508,7 @@ ${ks(this.code(e,t))} font-weight: normal; font-style: normal; } - `,xv=()=>` + `,Lv=()=>` .katex { font: normal 1.21em KaTeX_Main, Times New Roman, serif; line-height: 1.2; @@ -1472,8 +1472,8 @@ ewline is an empty block at top level, between .base elements */ body { counter-reset: katexEqnNo mmlEqnNo; } -`});var bv={};tt(bv,{default:()=>BP});var dp,vv,bc,BP,wv=T(()=>{og();pv();pe();yv();dp=!1,vv=vc.import("formats/formula"),bc=class{constructor(e){this.opt=e,this.mindMap=e.mindMap,window.katex=Oh,this.init(),this.config=this.getKatexConfig(),this.cssEl=null,this.addStyle(),this.extendQuill(),this.onDestroy=this.onDestroy.bind(this),this.mindMap.on("beforeDestroy",this.onDestroy)}onDestroy(){Object.getPrototypeOf(this.mindMap).constructor.instanceCount<=1&&(dp=!1,vc.register("formats/formula",vv,!0))}init(){this.mindMap.opt.enableEditFormulaInRichTextEdit&&(this.mindMap.opt.transformRichTextOnEnterEdit=this.latexRichToText.bind(this),this.mindMap.opt.beforeHideRichTextEdit=this.formatLatex.bind(this))}getKatexConfig(){let e={throwOnError:!1,errorColor:"#f00",output:"mathml"},{getKatexOutputType:t}=this.mindMap.opt;t=t||function(){let n=I2();if(n&&n<=100)return"html"};let r=t()||"mathml";return e.output=["mathml","html"].includes(r)?r:"mathml",e}extendQuill(){if(dp)return;dp=!0;let e=this;class t extends vv{static create(n){let s=super.create(n);return typeof n=="string"&&(Oh.render(n,s,e.config),s.setAttribute("data-value",mn(n))),s}}vc.register("formats/formula",t,!0)}getStyleText(){let{katexFontPath:e}=this.mindMap.opt,t="";return this.config.output==="html"&&(t=gv(e)),t+=xv(),t}addStyle(){this.cssEl=document.createElement("style"),this.cssEl.type="text/css",this.cssEl.innerHTML=this.getStyleText(),document.head.appendChild(this.cssEl)}removeStyle(){document.head.removeChild(this.cssEl)}insertFormulaToNode(e,t){let r=this.mindMap.richText;r.showEditText({node:e}),r.quill.insertEmbed(r.quill.getLength()-1,"formula",t),r.hideEditText([e])}latexRichToText(e){if(e.indexOf('class="ql-formula"')!==-1){let n=new DOMParser().parseFromString(e,"text/html").getElementsByClassName("ql-formula");for(let s of n)e=e.replace(s.outerHTML,`$${s.getAttribute("data-value")}$`);this.mindMap.opt.openRealtimeRenderOnNodeTextEdit&&setTimeout(()=>{this.mindMap.emit("node_text_edit_change",{node:this.mindMap.richText.node,text:this.mindMap.richText.getEditText(),richText:!0})},0)}return e}formatLatex(e){let t=e.quill.getContents(),r=t.ops,n=!1;for(let s=r.length-1;s>=0;s--){let o=r[s].insert;if(o&&typeof o!="object"&&o!==` -`&&/\$.+?\$/g.test(o)){let l=[...o.matchAll(/\$.+?\$/g)],h=o.split(/\$.+?\$/g);for(let d=l.length-1;d>=0;d--){let c=l[d]&&l[d][0]&&l[d][0].slice(1,-1)||null;c!==null&&c.trim().length>0&&this.checkFormulaIsLegal(c)?(h.splice(d+1,0,{insert:{formula:c}}),n=!0):h.splice(d+1,0,"")}for(;h.length>0;){let d=h.pop();if(typeof d=="string"){if(d.length<1)continue;d={insert:d}}d.attributes=r[s].attributes,r.splice(s+1,0,d)}r.splice(s,1)}}n&&e.quill.setContents(t)}checkFormulaIsLegal(e){try{return Oh.renderToString(e),!0}catch{return!1}}beforePluginRemove(){this.removeStyle(),this.mindMap.off("beforeDestroy",this.onDestroy)}beforePluginDestroy(){this.removeStyle(),this.mindMap.off("beforeDestroy",this.onDestroy)}};bc.instanceName="formula";BP=bc});var cp,Mv,Tv=T(()=>{$e();cp=class{constructor(e={}){this.opt=e,this.mindMap=this.opt.mindMap,this.scale=1,this.sx=0,this.sy=0,this.x=0,this.y=0,this.firstDrag=!0,this.setTransformData(this.mindMap.opt.viewData),this.bind()}bind(){this.mindMap.keyCommand.addShortcut("Control+=",()=>{this.enlarge()}),this.mindMap.keyCommand.addShortcut("Control+-",()=>{this.narrow()}),this.mindMap.keyCommand.addShortcut("Control+i",()=>{this.fit()}),this.mindMap.event.on("mousedown",e=>{let{isDisableDrag:t,mousedownEventPreventDefault:r}=this.mindMap.opt;t||(r&&e.preventDefault(),this.sx=this.x,this.sy=this.y)}),this.mindMap.event.on("drag",(e,t)=>{e.ctrlKey||e.metaKey||this.mindMap.opt.isDisableDrag||(this.firstDrag&&(this.firstDrag=!1,this.mindMap.renderer.activeNodeList.length>0&&this.mindMap.execCommand("CLEAR_ACTIVE_NODE")),this.x=this.sx+t.mousemoveOffset.x,this.y=this.sy+t.mousemoveOffset.y,this.transform())}),this.mindMap.event.on("mouseup",()=>{this.firstDrag=!0}),this.mindMap.event.on("mousewheel",(e,t,r,n)=>{let{customHandleMousewheel:s,mousewheelAction:a,mouseScaleCenterUseMousePosition:o,mousewheelMoveStep:l,mousewheelZoomActionReverse:h,disableMouseWheelZoom:d,translateRatio:c}=this.mindMap.opt;if(s&&typeof s=="function")return s(e);if(a===k.MOUSE_WHEEL_ACTION.ZOOM||e.ctrlKey||e.metaKey){if(d)return;let{x:f,y:m}=this.mindMap.toPos(e.clientX,e.clientY),g=o?f:void 0,x=o?m:void 0;switch(n&&(t.includes(k.DIR.LEFT)||t.includes(k.DIR.RIGHT))&&(t=t.filter(y=>![k.DIR.LEFT,k.DIR.RIGHT].includes(y))),!0){case t.includes(k.DIR.UP||k.DIR.LEFT):h?this.enlarge(g,x,n):this.narrow(g,x,n);break;case t.includes(k.DIR.DOWN||k.DIR.RIGHT):h?this.narrow(g,x,n):this.enlarge(g,x,n);break}}else{let f=0,m=0;n?(f=Math.abs(e.wheelDeltaX),m=Math.abs(e.wheelDeltaY)):f=m=l;let g=0,x=0;t.includes(k.DIR.DOWN)&&(x=-m),t.includes(k.DIR.UP)&&(x=m),t.includes(k.DIR.LEFT)&&(g=f),t.includes(k.DIR.RIGHT)&&(g=-f),this.translateXY(g*c,x*c)}}),this.mindMap.on("resize",()=>{this.checkNeedMindMapInCanvas()&&this.transform()})}getTransformData(){return{transform:this.mindMap.draw.transform(),state:{scale:this.scale,x:this.x,y:this.y,sx:this.sx,sy:this.sy}}}setTransformData(e){e&&(Object.keys(e.state).forEach(t=>{this[t]=e.state[t]}),this.mindMap.draw.transform({...e.transform}),this.mindMap.emit("view_data_change",this.getTransformData()),this.emitEvent("scale"),this.emitEvent("translate"))}translateXY(e,t){e===0&&t===0||(this.x+=e,this.y+=t,this.transform(),this.emitEvent("translate"))}translateX(e){e!==0&&(this.x+=e,this.transform(),this.emitEvent("translate"))}translateXTo(e){this.x=e,this.transform(),this.emitEvent("translate")}translateY(e){e!==0&&(this.y+=e,this.transform(),this.emitEvent("translate"))}translateYTo(e){this.y=e,this.transform(),this.emitEvent("translate")}transform(){try{this.limitMindMapInCanvas()}catch{}this.mindMap.draw.transform({origin:[0,0],scale:this.scale,translate:[this.x,this.y]}),this.mindMap.emit("view_data_change",this.getTransformData())}reset(){let e=this.scale!==1,t=this.x!==0||this.y!==0;this.scale=1,this.x=0,this.y=0,this.transform(),e&&this.emitEvent("scale"),t&&this.emitEvent("translate")}narrow(e,t,r){let{scaleRatio:n,minZoomRatio:s}=this.mindMap.opt;n=n/(r?5:1);let a=Math.max(this.scale-n,s/100);this.scaleInCenter(a,e,t),this.transform(),this.emitEvent("scale")}enlarge(e,t,r){let{scaleRatio:n,maxZoomRatio:s}=this.mindMap.opt;n=n/(r?5:1);let a=0;s===-1?a=this.scale+n:a=Math.min(this.scale+n,s/100),this.scaleInCenter(a,e,t),this.transform(),this.emitEvent("scale")}scaleInCenter(e,t,r){(t===void 0||r===void 0)&&(t=this.mindMap.width/2,r=this.mindMap.height/2);let n=this.scale,s=1-e/n,a=(t-this.x)*s,o=(r-this.y)*s;this.x+=a,this.y+=o,this.scale=e}setScale(e,t,r){t!==void 0&&r!==void 0?this.scaleInCenter(e,t,r):this.scale=e,this.transform(),this.emitEvent("scale")}fit(e=()=>{},t=!1,r){r=r===void 0?this.mindMap.opt.fitPadding:r;let n=this.mindMap.draw,s=n.transform(),a=e()||n.rbox(),o=a.width/s.scaleX,l=a.height/s.scaleY,h=o/l,{width:d,height:c}=this.mindMap.elRect;d=d-r*2,c=c-r*2;let f=d/c,m=0,g="";if(o<=d&&l<=c&&!t)m=1,g=1;else{let S=0,A=0;h>f?(S=d,A=d/h,g=2):(A=c,S=c*h,g=3),m=S/o}this.setScale(m);let x=e()||n.rbox();x.x-=this.mindMap.elRect.left,x.y-=this.mindMap.elRect.top;let y=0,b=0;g===1?(y=-x.x+r+(d-x.width)/2,b=-x.y+r+(c-x.height)/2):g===2?(y=-x.x+r,b=-x.y+r+(c-x.height)/2):g===3&&(y=-x.x+r+(d-x.width)/2,b=-x.y+r),this.translateXY(y,b)}checkNeedMindMapInCanvas(){if(this.mindMap.demonstrate&&this.mindMap.demonstrate.isInDemonstrate)return!1;let{isLimitMindMapInCanvasWhenHasScrollbar:e,isLimitMindMapInCanvas:t}=this.mindMap.opt;return this.mindMap.scrollbar?e:t}limitMindMapInCanvas(){if(!this.checkNeedMindMapInCanvas())return;let{scale:e,left:t,top:r,right:n,bottom:s}=this.getPositionLimit(),a=(this.mindMap.width-this.mindMap.initWidth)/2*e,o=(this.mindMap.height-this.mindMap.initHeight)/2*e,l=this.scale/e;t*=l,n*=l,r*=l,s*=l;let h=this.mindMap.width/2,d=this.mindMap.height/2,c=this.scale-1;t-=c*h-a,n-=c*h-a,r-=c*d-o,s-=c*d-o,this.x>t&&(this.x=t),this.xr&&(this.y=r),this.y{"use strict";var PP=Object.prototype.hasOwnProperty,jt="~";function o0(){}Object.create&&(o0.prototype=Object.create(null),new o0().__proto__||(jt=!1));function FP(i,e,t){this.fn=i,this.context=e,this.once=t||!1}function Nv(i,e,t,r,n){if(typeof t!="function")throw new TypeError("The listener must be a function");var s=new FP(t,r||i,n),a=jt?jt+e:e;return i._events[a]?i._events[a].fn?i._events[a]=[i._events[a],s]:i._events[a].push(s):(i._events[a]=s,i._eventsCount++),i}function wc(i,e){--i._eventsCount===0?i._events=new o0:delete i._events[e]}function Ct(){this._events=new o0,this._eventsCount=0}Ct.prototype.eventNames=function(){var e=[],t,r;if(this._eventsCount===0)return e;for(r in t=this._events)PP.call(t,r)&&e.push(jt?r.slice(1):r);return Object.getOwnPropertySymbols?e.concat(Object.getOwnPropertySymbols(t)):e};Ct.prototype.listeners=function(e){var t=jt?jt+e:e,r=this._events[t];if(!r)return[];if(r.fn)return[r.fn];for(var n=0,s=r.length,a=new Array(s);n{Sv=pt(Ev());$e();fp=class extends Sv.default{constructor(e={}){super(),this.opt=e,this.mindMap=e.mindMap,this.isLeftMousedown=!1,this.isRightMousedown=!1,this.isMiddleMousedown=!1,this.mousedownPos={x:0,y:0},this.mousemovePos={x:0,y:0},this.mousemoveOffset={x:0,y:0},this.bindFn(),this.bind()}bindFn(){this.onBodyMousedown=this.onBodyMousedown.bind(this),this.onBodyClick=this.onBodyClick.bind(this),this.onDrawClick=this.onDrawClick.bind(this),this.onMousedown=this.onMousedown.bind(this),this.onMousemove=this.onMousemove.bind(this),this.onMouseup=this.onMouseup.bind(this),this.onNodeMouseup=this.onNodeMouseup.bind(this),this.onMousewheel=this.onMousewheel.bind(this),this.onContextmenu=this.onContextmenu.bind(this),this.onSvgMousedown=this.onSvgMousedown.bind(this),this.onKeyup=this.onKeyup.bind(this),this.onMouseenter=this.onMouseenter.bind(this),this.onMouseleave=this.onMouseleave.bind(this)}bind(){document.body.addEventListener("mousedown",this.onBodyMousedown),document.body.addEventListener("click",this.onBodyClick),this.mindMap.svg.on("click",this.onDrawClick),this.mindMap.el.addEventListener("mousedown",this.onMousedown),this.mindMap.svg.on("mousedown",this.onSvgMousedown),window.addEventListener("mousemove",this.onMousemove),window.addEventListener("mouseup",this.onMouseup),this.on("node_mouseup",this.onNodeMouseup),this.mindMap.el.addEventListener("wheel",this.onMousewheel),this.mindMap.svg.on("contextmenu",this.onContextmenu),this.mindMap.svg.on("mouseenter",this.onMouseenter),this.mindMap.svg.on("mouseleave",this.onMouseleave),window.addEventListener("keyup",this.onKeyup)}unbind(){document.body.removeEventListener("mousedown",this.onBodyMousedown),document.body.removeEventListener("click",this.onBodyClick),this.mindMap.svg.off("click",this.onDrawClick),this.mindMap.el.removeEventListener("mousedown",this.onMousedown),window.removeEventListener("mousemove",this.onMousemove),window.removeEventListener("mouseup",this.onMouseup),this.off("node_mouseup",this.onNodeMouseup),this.mindMap.el.removeEventListener("wheel",this.onMousewheel),this.mindMap.svg.off("contextmenu",this.onContextmenu),this.mindMap.svg.off("mouseenter",this.onMouseenter),this.mindMap.svg.off("mouseleave",this.onMouseleave),window.removeEventListener("keyup",this.onKeyup)}onDrawClick(e){this.emit("draw_click",e)}onBodyMousedown(e){this.emit("body_mousedown",e)}onBodyClick(e){this.emit("body_click",e)}onSvgMousedown(e){this.emit("svg_mousedown",e)}onMousedown(e){e.which===1?this.isLeftMousedown=!0:e.which===3?this.isRightMousedown=!0:e.which===2&&(this.isMiddleMousedown=!0),this.mousedownPos.x=e.clientX,this.mousedownPos.y=e.clientY,this.emit("mousedown",e,this)}onMousemove(e){let{useLeftKeySelectionRightKeyDrag:t}=this.mindMap.opt;this.mousemovePos.x=e.clientX,this.mousemovePos.y=e.clientY,this.mousemoveOffset.x=e.clientX-this.mousedownPos.x,this.mousemoveOffset.y=e.clientY-this.mousedownPos.y,this.emit("mousemove",e,this),(this.isMiddleMousedown||(t?this.isRightMousedown:this.isLeftMousedown))&&(e.preventDefault(),this.emit("drag",e,this))}onMouseup(e){this.onNodeMouseup(),this.emit("mouseup",e,this)}onNodeMouseup(){this.isLeftMousedown=!1,this.isRightMousedown=!1,this.isMiddleMousedown=!1}onMousewheel(e){e.stopPropagation(),e.preventDefault();let t=[];e.deltaY<0&&t.push(k.DIR.UP),e.deltaY>0&&t.push(k.DIR.DOWN),e.deltaX<0&&t.push(k.DIR.LEFT),e.deltaX>0&&t.push(k.DIR.RIGHT);let r=!1,{customCheckIsTouchPad:n}=this.mindMap.opt;typeof n=="function"?r=n(e):r=Math.abs(e.deltaY)<=10,this.emit("mousewheel",e,t,this,r)}onContextmenu(e){e.preventDefault(),!e.ctrlKey&&this.emit("contextmenu",e)}onKeyup(e){this.emit("keyup",e)}onMouseenter(e){this.emit("svg_mouseenter",e)}onMouseleave(e){this.emit("svg_mouseleave",e)}},Av=fp});var mp,pp,Cv=T(()=>{Ar();pe();$e();mp=class extends Mt{constructor(e={},t){super(e),this.isUseLeft=t===k.LAYOUT.LOGICAL_STRUCTURE_LEFT}doLayout(e){Zt([()=>{this.computedBaseValue()},()=>{this.computedTopValue()},()=>{this.adjustTopValue()},()=>{e(this.root)}])}computedBaseValue(){let e=0;de(this.renderer.renderTree,null,(t,r,n,s,a,o)=>{let l=this.createNode(t,r,n,s,a,o);if(l.sortIndex=e,e++,n?this.setNodeCenter(l):this.isUseLeft?l.left=r._node.left-l.width-this.getMarginX(s):l.left=r._node.left+r._node.width+this.getMarginX(s),!t.data.expand)return!0},(t,r,n,s)=>{let a=t.data.expand===!1?0:t._node.children.length;t._node.childrenAreaHeight=a?t._node.children.reduce((l,h)=>l+h.height,0)+(a+1)*this.getMarginY(s+1):0;let o=t._node.checkHasGeneralization()?t._node._generalizationNodeHeight+this.getMarginY(s+1):0;t._node.childrenAreaHeight2=Math.max(t._node.childrenAreaHeight,o)},!0,0)}computedTopValue(){de(this.root,null,(e,t,r,n)=>{if(e.getData("expand")&&e.children&&e.children.length){let s=this.getMarginY(n+1),o=e.top+e.height/2-e.childrenAreaHeight/2+s;e.children.forEach(l=>{l.top=o,o+=l.height+s})}},null,!0)}adjustTopValue(){de(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;let s=e.childrenAreaHeight2-this.getMarginY(n+1)*2-e.height;s>0&&this.updateBrothers(e,s/2)},null,!0)}updateBrothers(e,t){if(e.parent){let r=e.parent.children,n=Ie(e,r);r.forEach((s,a)=>{if(s.uid===e.uid||s.hasCustomPosition())return;let o=0;an&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothers(e.parent,t)}}renderLine(e,t,r,n){n==="curve"?this.renderLineCurve(e,t,r):n==="direct"?this.renderLineDirect(e,t,r):this.renderLineStraight(e,t,r)}renderLineStraight(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let f=(this.getMarginX(e.layerIndex+1)-l)*.6;this.isUseLeft&&(f*=-1);let m=this.mindMap.themeConfig.nodeUseLineStyle;e.children.forEach((g,x)=>{let y;this.isUseLeft?y=e.layerIndex===0?n:n-l:y=e.layerIndex===0?n+a:n+a+l;let b=s+o/2,S=this.isUseLeft?g.left+g.width:g.left,A=g.top+g.height/2,C=m?g.width*(this.isUseLeft?-1:1):0;b=m&&!e.isRoot?b+o/2:b,A=m?A+g.height/2:A;let I=this.createFoldLine([[y,b],[y+f,b],[y+f,A],[S+C,A]]);this.setLineStyle(r,t[x],I,g)})}renderLineDirect(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c}=this.mindMap.themeConfig;e.children.forEach((f,m)=>{e.layerIndex===0&&(l=0);let g=this.isUseLeft?n-l:n+a+l,x=s+o/2,y=this.isUseLeft?f.left+f.width:f.left,b=f.top+f.height/2;x=c&&!e.isRoot?x+o/2:x,b=c?b+f.height/2:b;let S=c?` L ${this.isUseLeft?f.left:f.left+f.width},${b}`:"",A=`M ${g},${x} L ${y},${b}`+S;this.setLineStyle(r,t[m],A,f)})}renderLineCurve(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c,rootLineStartPositionKeepSameInCurve:f,rootLineKeepSameInCurve:m}=this.mindMap.themeConfig;e.children.forEach((g,x)=>{e.layerIndex===0&&(l=0);let y;this.isUseLeft?y=e.layerIndex===0&&!f?n+a/2:n-l:y=e.layerIndex===0&&!f?n+a/2:n+a+l;let b=s+o/2,S=this.isUseLeft?g.left+g.width:g.left,A=g.top+g.height/2,C="";b=c&&!e.isRoot?b+o/2:b,A=c?A+g.height/2:A;let I;this.isUseLeft?I=c?` L ${g.left},${A}`:"":I=c?` L ${g.left+g.width},${A}`:"",e.isRoot&&!m?C=this.quadraticCurvePath(y,b,S,A)+I:C=this.cubicBezierPath(y,b,S,A)+I,this.setLineStyle(r,t[x],C,g)})}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s,layerIndex:a}=e;a===0&&(s=0);let{translateX:o,translateY:l}=t.transform(),h=this.mindMap.themeConfig.nodeUseLineStyle?n/2:0,d=this.isUseLeft?0-s:r,c=n/2+h;d===o&&c===l||t.translate(d-o,c-l)}renderGeneralization(e){e.forEach(t=>{let{left:r,top:n,bottom:s,right:a,generalizationLineMargin:o,generalizationNodeMargin:l}=this.getNodeGeneralizationRenderBoundaries(t,"h"),h=this.isUseLeft?r-o:a+o,d=h,c=n,f=h,m=s,g=d+(this.isUseLeft?-20:20),x=c+(m-c)/2,y=`M ${d},${c} Q ${g},${x} ${f},${m}`;t.generalizationLine.plot(y),t.generalizationNode.left=h+(this.isUseLeft?-l:l)-(this.isUseLeft?t.generalizationNode.width:0),t.generalizationNode.top=n+(s-n-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n){this.isUseLeft?e.size(t,n).x(-t).y(0):e.size(t,n).x(r).y(0)}},pp=mp});var gp,_v,Lv=T(()=>{Ar();pe();$e();gp=class extends Mt{constructor(e={}){super(e)}doLayout(e){Zt([()=>{this.computedBaseValue()},()=>{this.computedTopValue()},()=>{this.adjustTopValue()},()=>{e(this.root)}])}computedBaseValue(){de(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):(t._node.dir?o.dir=t._node.dir:o.dir=o.getData("dir")||(s%2===0?k.LAYOUT_GROW_DIR.RIGHT:k.LAYOUT_GROW_DIR.LEFT),o.left=o.dir===k.LAYOUT_GROW_DIR.RIGHT?t._node.left+t._node.width+this.getMarginX(n):t._node.left-this.getMarginX(n)-o.width),!e.data.expand)return!0},(e,t,r,n)=>{if(!e.data.expand){e._node.leftChildrenAreaHeight=0,e._node.rightChildrenAreaHeight=0;return}let s=0,a=0,o=0,l=0;e._node.children.forEach(d=>{d.dir===k.LAYOUT_GROW_DIR.LEFT?(s++,o+=d.height):(a++,l+=d.height)}),e._node.leftChildrenAreaHeight=o+(s+1)*this.getMarginY(n+1),e._node.rightChildrenAreaHeight=l+(a+1)*this.getMarginY(n+1);let h=e._node.checkHasGeneralization()?e._node._generalizationNodeHeight+this.getMarginY(n+1):0;e._node.leftChildrenAreaHeight2=Math.max(e._node.leftChildrenAreaHeight,h),e._node.rightChildrenAreaHeight2=Math.max(e._node.rightChildrenAreaHeight,h)},!0,0)}computedTopValue(){de(this.root,null,(e,t,r,n)=>{if(e.getData("expand")&&e.children&&e.children.length){let s=this.getMarginY(n+1),a=e.top+e.height/2+s,o=a-e.leftChildrenAreaHeight/2,l=a-e.rightChildrenAreaHeight/2;e.children.forEach(h=>{h.dir===k.LAYOUT_GROW_DIR.LEFT?(h.top=o,o+=h.height+s):(h.top=l,l+=h.height+s)})}},null,!0)}adjustTopValue(){de(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;let s=this.getMarginY(n+1)*2+e.height,a=e.leftChildrenAreaHeight2-s,o=e.rightChildrenAreaHeight2-s;(a>0||o>0)&&this.updateBrothers(e,a/2,o/2)},null,!0)}updateBrothers(e,t,r){if(e.parent){let n=e.parent.children.filter(a=>a.dir===e.dir),s=Ie(e,n);n.forEach((a,o)=>{if(a.hasCustomPosition())return;let l=0,h=a.dir===k.LAYOUT_GROW_DIR.LEFT?t:r;os&&(l=h),a.top+=l,a.children&&a.children.length&&this.updateChildren(a.children,"top",l)}),this.updateBrothers(e.parent,t,r)}}renderLine(e,t,r,n){n==="curve"?this.renderLineCurve(e,t,r):n==="direct"?this.renderLineDirect(e,t,r):this.renderLineStraight(e,t,r)}renderLineStraight(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let f=(this.getMarginX(e.layerIndex+1)-l)*.6,m=this.mindMap.themeConfig.nodeUseLineStyle;e.children.forEach((g,x)=>{let y=0,b=0,S=m?g.width:0;g.dir===k.LAYOUT_GROW_DIR.LEFT?(b=-f,y=e.layerIndex===0?n:n-l,S=-S):(b=f,y=e.layerIndex===0?n+a:n+a+l);let A=s+o/2,C=g.dir===k.LAYOUT_GROW_DIR.LEFT?g.left+g.width:g.left,I=g.top+g.height/2;A=m&&!e.isRoot?A+o/2:A,I=m?I+g.height/2:I;let O=this.createFoldLine([[y,A],[y+b,A],[y+b,I],[C+S,I]]);this.setLineStyle(r,t[x],O,g)})}renderLineDirect(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c}=this.mindMap.themeConfig;e.children.forEach((f,m)=>{e.layerIndex===0&&(l=0);let g=f.dir===k.LAYOUT_GROW_DIR.LEFT?n-l:n+a+l,x=s+o/2,y=f.dir===k.LAYOUT_GROW_DIR.LEFT?f.left+f.width:f.left,b=f.top+f.height/2;x=c&&!e.isRoot?x+o/2:x,b=c?b+f.height/2:b;let S="";c&&(f.dir===k.LAYOUT_GROW_DIR.LEFT?S=` L ${f.left},${b}`:S=` L ${f.left+f.width},${b}`);let A=`M ${g},${x} L ${y},${b}`+S;this.setLineStyle(r,t[m],A,f)})}renderLineCurve(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c,rootLineKeepSameInCurve:f,rootLineStartPositionKeepSameInCurve:m}=this.mindMap.themeConfig;e.children.forEach((g,x)=>{e.layerIndex===0&&(l=0);let y=e.layerIndex===0&&!m?n+a/2:g.dir===k.LAYOUT_GROW_DIR.LEFT?n-l:n+a+l,b=s+o/2,S=g.dir===k.LAYOUT_GROW_DIR.LEFT?g.left+g.width:g.left,A=g.top+g.height/2,C="";b=c&&!e.isRoot?b+o/2:b,A=c?A+g.height/2:A;let I="";c&&(g.dir===k.LAYOUT_GROW_DIR.LEFT?I=` L ${g.left},${A}`:I=` L ${g.left+g.width},${A}`),e.isRoot&&!f?C=this.quadraticCurvePath(y,b,S,A)+I:C=this.cubicBezierPath(y,b,S,A)+I,this.setLineStyle(r,t[x],C,g)})}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s}=e,{translateX:a,translateY:o}=t.transform(),l=this.mindMap.themeConfig.nodeUseLineStyle?n/2:0,h=e.dir===k.LAYOUT_GROW_DIR.LEFT?0-s:r,d=n/2+l;if(h===a&&d===o)return;let c=h-a,f=d-o;t.translate(c,f)}renderGeneralization(e){e.forEach(t=>{let r=t.node.dir===k.LAYOUT_GROW_DIR.LEFT,{top:n,bottom:s,left:a,right:o,generalizationLineMargin:l,generalizationNodeMargin:h}=this.getNodeGeneralizationRenderBoundaries(t,"h"),d=r?a-l:o+l,c=d,f=n,m=d,g=s,x=c+(r?-20:20),y=f+(g-f)/2,b=`M ${c},${f} Q ${x},${y} ${m},${g}`;t.generalizationLine.plot(b),t.generalizationNode.left=d+(r?-h:h)-(r?t.generalizationNode.width:0),t.generalizationNode.top=n+(s-n-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){s.dir===k.LAYOUT_GROW_DIR.LEFT?e.size(t,n).x(-t).y(0):e.size(t,n).x(r).y(0)}},_v=gp});var xp,Iv,zv=T(()=>{Ar();pe();xp=class extends Mt{constructor(e={}){super(e)}doLayout(e){Zt([()=>{this.computedBaseValue()},()=>{this.computedLeftTopValue()},()=>{this.adjustLeftTopValue()},()=>{e(this.root)}])}computedBaseValue(){de(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):t._node.isRoot&&(o.top=t._node.top+t._node.height+this.getMarginX(n)),!e.data.expand)return!0},(e,t,r,n)=>{if(r){let s=e.data.expand===!1?0:e._node.children.length;e._node.childrenAreaWidth=s?e._node.children.reduce((a,o)=>a+o.width,0)+(s+1)*this.getMarginX(n+1):0}},!0,0)}computedLeftTopValue(){de(this.root,null,(e,t,r,n)=>{if(e.getData("expand")&&e.children&&e.children.length){let s=this.getMarginX(n+1),a=this.getMarginY(n+1);if(r){let l=e.left+e.width/2-e.childrenAreaWidth/2+s;e.children.forEach(h=>{h.left=l,l+=h.width+s})}else{let o=e.top+this.getNodeHeightWithGeneralization(e)+a+(this.getNodeActChildrenLength(e)>0?e.expandBtnSize:0);e.children.forEach(l=>{l.left=e.left+e.width*.5,l.top=o,o+=this.getNodeHeightWithGeneralization(l)+a+(this.getNodeActChildrenLength(l)>0?l.expandBtnSize:0)})}}},null,!0)}adjustLeftTopValue(){de(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;if(t&&t.isRoot){let o=this.getNodeAreaWidth(e,!0)-e.width;o>0&&this.updateBrothersLeft(e,o)}let s=e.children.length;if(t&&!t.isRoot&&s>0){let a=this.getMarginY(n+1),o=e.children.reduce((l,h)=>l+this.getNodeHeightWithGeneralization(h)+(this.getNodeActChildrenLength(h)>0?h.expandBtnSize:0),0)+s*a;this.updateBrothersTop(e,o)}},(e,t,r)=>{if(r){let{right:n,left:s}=this.getNodeBoundaries(e,"h"),a=n-s,o=e.left-s-(a-e.width)/2;this.updateChildren(e.children,"left",o)}},!0)}updateBrothersLeft(e,t){if(e.parent){let r=e.parent.children,n=Ie(e,r);r.forEach((s,a)=>{s.hasCustomPosition()||a<=n||(s.left+=t,s.children&&s.children.length&&this.updateChildren(s.children,"left",t))}),this.updateBrothersLeft(e.parent,t)}}updateBrothersTop(e,t){if(e.parent&&!e.parent.isRoot){let r=e.parent.children,n=Ie(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;a>n&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothersTop(e.parent,t)}}renderLine(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let c=e.children.length,f=this.getMarginX(e.layerIndex+1);if(e.isRoot){let m=n+a/2,g=s+o,x=f*.7,y=1/0,b=-1/0;e.children.forEach((A,C)=>{let I=A.left+A.width/2,O=A.top;Ib&&(b=I);let q=this.mindMap.themeConfig.nodeUseLineStyle?` L ${A.left},${O} L ${A.left+A.width},${O}`:"",P=`M ${I},${g+x} L ${I},${g+x>O?O+A.height:O}`+q;this.setLineStyle(r,t[C],P,A)}),y=Math.min(y,m),b=Math.max(b,m);let S=this.lineDraw.path();if(e.style.line(S),S.plot(this.transformPath(`M ${m},${g} L ${m},${g+x}`)),e._lines.push(S),r&&r(S,e),c>0){let A=this.lineDraw.path();e.style.line(A),A.plot(this.transformPath(`M ${y},${g+x} L ${b},${g+x}`)),e._lines.push(A),r&&r(A,e)}}else{let m=s+o,g=-1/0,x=e.left+e.width*.3;if(e.children.forEach((y,b)=>{let S=y.top+y.height/2;S>g&&(g=S);let A="",C=y.left,I=y.left+y.widthx&&(O=!0,S=y.top,g=S),S>s&&S0){let y=this.lineDraw.path();l=c>0?l:0,e.style.line(y),g{let{top:r,bottom:n,right:s,generalizationLineMargin:a,generalizationNodeMargin:o}=this.getNodeGeneralizationRenderBoundaries(t,"h"),l=s+a,h=r,d=s+a,c=n,f=l+20,m=h+(c-h)/2,g=`M ${l},${h} Q ${f},${m} ${d},${c}`;t.generalizationLine.plot(this.transformPath(g)),t.generalizationNode.left=s+o,t.generalizationNode.top=r+(n-r-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){e.size(r,t).x(0).y(n)}},Iv=xp});var yp,Rv,Dv=T(()=>{Ar();pe();yp=class extends Mt{constructor(e={}){super(e)}doLayout(e){Zt([()=>{this.computedBaseValue()},()=>{this.computedLeftValue()},()=>{this.adjustLeftValue()},()=>{e(this.root)}])}computedBaseValue(){de(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):o.top=t._node.top+t._node.height+this.getMarginX(n),!e.data.expand)return!0},(e,t,r,n)=>{let s=e.data.expand===!1?0:e._node.children.length;e._node.childrenAreaWidth=s?e._node.children.reduce((o,l)=>o+l.width,0)+(s+1)*this.getMarginY(n+1):0;let a=e._node.checkHasGeneralization()?e._node._generalizationNodeWidth+this.getMarginY(n+1):0;e._node.childrenAreaWidth2=Math.max(e._node.childrenAreaWidth,a)},!0,0)}computedLeftValue(){de(this.root,null,(e,t,r,n)=>{if(e.getData("expand")&&e.children&&e.children.length){let s=this.getMarginY(n+1),o=e.left+e.width/2-e.childrenAreaWidth/2+s;e.children.forEach(l=>{l.left=o,o+=l.width+s})}},null,!0)}adjustLeftValue(){de(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;let s=e.childrenAreaWidth2-this.getMarginY(n+1)*2-e.width;s>0&&this.updateBrothers(e,s/2)},null,!0)}updateBrothers(e,t){if(e.parent){let r=e.parent.children,n=Ie(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;an&&(o=t),s.left+=o,s.children&&s.children.length&&this.updateChildren(s.children,"left",o)}),this.updateBrothers(e.parent,t)}}renderLine(e,t,r,n){n==="curve"?this.renderLineCurve(e,t,r):n==="direct"?this.renderLineDirect(e,t,r):this.renderLineStraight(e,t,r)}renderLineCurve(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c,rootLineStartPositionKeepSameInCurve:f,rootLineKeepSameInCurve:m}=this.mindMap.themeConfig;e.children.forEach((g,x)=>{e.layerIndex===0&&(l=0);let y=n+a/2,b=e.layerIndex===0&&!f?s+o/2:s+o+l,S=g.left+g.width/2,A=g.top,C="",I=c?` L ${g.left},${A} L ${g.left+g.width},${A}`:"";e.isRoot&&!m?C=this.quadraticCurvePath(y,b,S,A,!0)+I:C=this.cubicBezierPath(y,b,S,A,!0)+I,this.setLineStyle(r,t[x],C,g)})}renderLineDirect(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o}=e,{nodeUseLineStyle:l}=this.mindMap.themeConfig,h=n+a/2,d=s+o;e.children.forEach((c,f)=>{let m=c.left+c.width/2,g=c.top,x=l?` L ${c.left},${g} L ${c.left+c.width},${g}`:"",y=`M ${h},${d} L ${m},${g}`+x;this.setLineStyle(r,t[f],y,c)})}renderLineStraight(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l,isRoot:h}=e,{alwaysShowExpandBtn:d,notShowExpandBtn:c}=this.mindMap.opt;(!d||c)&&(l=0);let f=n+a/2,m=s+o,x=this.getMarginX(e.layerIndex+1)*.7,y=1/0,b=-1/0,S=e.children.length;e.children.forEach((C,I)=>{let O=C.left+C.width/2,q=m+x>C.top?C.top+C.height:C.top;Ob&&(b=O);let P=this.mindMap.themeConfig.nodeUseLineStyle?` L ${C.left},${q} L ${C.left+C.width},${q}`:"",W=`M ${O},${m+x} L ${O},${q}`+P;this.setLineStyle(r,t[I],W,C)}),y=Math.min(f,y),b=Math.max(f,b);let A=this.lineDraw.path();if(e.style.line(A),l=S>0&&!h?l:0,A.plot(this.transformPath(`M ${f},${m+l} L ${f},${m+x}`)),e._lines.push(A),r&&r(A,e),S>0){let C=this.lineDraw.path();e.style.line(C),C.plot(this.transformPath(`M ${y},${m+x} L ${b},${m+x}`)),e._lines.push(C),r&&r(C,e)}}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s}=e,{translateX:a,translateY:o}=t.transform();t.translate(r/2-s/2-a,n+s/2-o)}renderGeneralization(e){e.forEach(t=>{let{bottom:r,left:n,right:s,generalizationLineMargin:a,generalizationNodeMargin:o}=this.getNodeGeneralizationRenderBoundaries(t,"v"),l=n,h=r+a,d=s,c=r+a,f=l+(d-l)/2,m=h+20,g=`M ${l},${h} Q ${f},${m} ${d},${c}`;t.generalizationLine.plot(this.transformPath(g)),t.generalizationNode.top=r+o,t.generalizationNode.left=n+(s-n-t.generalizationNode.width)/2})}renderExpandBtnRect(e,t,r,n,s){e.size(r,t).x(0).y(n)}},Rv=yp});var vp,bp,Ov=T(()=>{Ar();pe();$e();vp=class extends Mt{constructor(e={},t){super(e),this.layout=t}doLayout(e){Zt([()=>{this.computedBaseValue()},()=>{this.computedLeftTopValue()},()=>{this.adjustLeftTopValue()},()=>{e(this.root)}])}computedBaseValue(){de(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):(this.layout===k.LAYOUT.TIMELINE2?t._node.dir?o.dir=t._node.dir:o.dir=s%2===0?k.LAYOUT_GROW_DIR.BOTTOM:k.LAYOUT_GROW_DIR.TOP:o.dir="",t._node.isRoot&&(o.top=t._node.top+(e._node.height>t._node.height?-(e._node.height-t._node.height)/2:(t._node.height-e._node.height)/2))),!e.data.expand)return!0},null,!0,0)}computedLeftTopValue(){de(this.root,null,(e,t,r,n,s)=>{if(e.getData("expand")&&e.children&&e.children.length){let a=this.getMarginX(n+1),o=this.getMarginY(n+1);if(r){let h=e.left+e.width+a;e.children.forEach(d=>{d.left=h,h+=d.width+a})}else{let l=e.top+e.height+o+(this.getNodeActChildrenLength(e)>0?e.expandBtnSize:0);e.children.forEach(h=>{h.left=e.left+e.width*.5,h.top=l,l+=h.height+o+(this.getNodeActChildrenLength(h)>0?h.expandBtnSize:0)})}}},null,!0)}adjustLeftTopValue(){de(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;e.isRoot&&this.updateBrothersLeft(e);let s=e.children.length;if(t&&!t.isRoot&&s>0){let a=this.getMarginY(n+1),o=e.children.reduce((l,h)=>l+h.height+(this.getNodeActChildrenLength(h)>0?h.expandBtnSize:0),0)+s*a;this.updateBrothersTop(e,o)}},(e,t,r,n)=>{t&&t.isRoot&&e.dir===k.LAYOUT_GROW_DIR.TOP&&e.children.forEach(s=>{let a=this.getNodeAreaHeight(s),o=s.top;s.top=e.top-(s.top-e.top)-a+e.height,this.updateChildren(s.children,"top",s.top-o)})},!0)}getNodeAreaHeight(e){let t=0,r=n=>{t+=n.height+(this.getNodeActChildrenLength(n)>0?n.expandBtnSize:0)+this.getMarginY(n.layerIndex),n.children.length&&n.children.forEach(s=>{r(s)})};return r(e),t}updateBrothersLeft(e){let t=e.children,r=0;t.forEach(n=>{n.left+=r,n.children&&n.children.length&&this.updateChildren(n.children,"left",r);let{left:s,right:a}=this.getNodeBoundaries(n,"h"),l=a-s-n.width;l>0&&(r+=l)})}updateBrothersTop(e,t){if(e.parent&&!e.parent.isRoot){let r=e.parent.children,n=Ie(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;a>n&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothersTop(e.parent,t)}}renderLine(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let c=e.children.length;if(e.isRoot){let f=e;e.children.forEach((m,g)=>{let x=f.left+f.width,y=m.left,b=e.top+e.height/2,S=`M ${x},${b} L ${y},${b}`;this.setLineStyle(r,t[g],S,m),f=m})}else{let f=-1/0,m=1/0,g=e.left+e.width*.3;if(e.children.forEach((x,y)=>{let b=x.top+x.height/2;b>f&&(f=b),b0){let x=this.lineDraw.path();l=c>0?l:0,e.parent&&e.parent.isRoot&&e.dir===k.LAYOUT_GROW_DIR.TOP?x.plot(this.transformPath(`M ${g},${s} L ${g},${m}`)):x.plot(this.transformPath(`M ${g},${s+o+l} L ${g},${f}`)),e.style.line(x),e._lines.push(x),r&&r(x,e)}}}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s,isRoot:a}=e;if(!a){let{translateX:o,translateY:l}=t.transform();e.parent&&e.parent.isRoot&&e.dir===k.LAYOUT_GROW_DIR.TOP?t.translate(r*.3-s/2-o,-s/2-l):t.translate(r*.3-s/2-o,n+s/2-l)}}renderGeneralization(e){e.forEach(t=>{let{top:r,bottom:n,right:s,generalizationLineMargin:a,generalizationNodeMargin:o}=this.getNodeGeneralizationRenderBoundaries(t,"h"),l=s+a,h=r,d=s+a,c=n,f=l+20,m=h+(c-h)/2,g=`M ${l},${h} Q ${f},${m} ${d},${c}`;t.generalizationLine.plot(this.transformPath(g)),t.generalizationNode.left=s+o,t.generalizationNode.top=r+(n-r-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){if(this.layout===k.LAYOUT.TIMELINE)e.size(r,t).x(0).y(n);else{let a="";s.dir===k.LAYOUT_GROW_DIR.TOP?a=s.layerIndex===1?k.LAYOUT_GROW_DIR.TOP:k.LAYOUT_GROW_DIR.BOTTOM:a=k.LAYOUT_GROW_DIR.BOTTOM,a===k.LAYOUT_GROW_DIR.TOP?e.size(r,t).x(0).y(-t):e.size(r,t).x(0).y(n)}}},bp=vp});var wp,Mc,Bv=T(()=>{Ar();pe();$e();wp=class extends Mt{constructor(e={},t){super(e),this.layout=t}doLayout(e){Zt([()=>{this.computedBaseValue()},()=>{this.computedTopValue()},()=>{this.adjustLeftTopValue()},()=>{e(this.root)}])}computedBaseValue(){de(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):(t._node.dir?o.dir=t._node.dir:this.layout===k.LAYOUT.VERTICAL_TIMELINE2?o.dir=k.LAYOUT_GROW_DIR.LEFT:this.layout===k.LAYOUT.VERTICAL_TIMELINE3?o.dir=k.LAYOUT_GROW_DIR.RIGHT:o.dir=s%2===0?k.LAYOUT_GROW_DIR.RIGHT:k.LAYOUT_GROW_DIR.LEFT,t._node.isRoot?o.left=t._node.left+(e._node.width>t._node.width?-(e._node.width-t._node.width)/2:(t._node.width-e._node.width)/2):o.left=o.dir===k.LAYOUT_GROW_DIR.RIGHT?t._node.left+t._node.width+this.getMarginX(n):t._node.left-this.getMarginX(n)-o.width),!e.data.expand)return!0},(e,t,r,n)=>{if(r)return;let s=e.data.expand===!1?0:e._node.children.length;e._node.childrenAreaHeight=s?e._node.children.reduce((a,o)=>a+o.height,0)+(s+1)*this.getMarginY(n+1):0},!0,0)}computedTopValue(){de(this.root,null,(e,t,r,n,s)=>{if(e.getData("expand")&&e.children&&e.children.length){let a=this.getMarginY(n+1);if(r){let l=e.top+e.height+a;e.children.forEach(h=>{h.top=l,l+=h.height+a})}else{let o=this.getMarginY(n+1),h=e.top+e.height/2+o-e.childrenAreaHeight/2;e.children.forEach(d=>{d.top=h,h+=d.height+o})}}},null,!0)}adjustLeftTopValue(){de(this.root,null,(e,t,r,n)=>{if(!e.getData("expand")||r)return;let s=this.getMarginY(n+1)*2+e.height,a=e.childrenAreaHeight-s;a>0&&this.updateBrothers(e,a/2)},null,!0)}updateBrothers(e,t){if(e.parent){let r=e.parent.children,n=Ie(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition()||!e.parent.isRoot&&s.uid===e.uid)return;let o=0;e.parent.isRoot?an?o=t*2:o=t:an&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothers(e.parent,t)}}updateBrothersTop(e,t){if(e.parent&&!e.parent.isRoot){let r=e.parent.children,n=Ie(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;a>n&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothersTop(e.parent,t)}}renderLine(e,t,r,n){n==="curve"?this.renderLineCurve(e,t,r):n==="direct"?this.renderLineDirect(e,t,r):this.renderLineStraight(e,t,r)}renderLineStraight(e,t,r){if(e.children.length<=0)return[];let{expandBtnSize:n}=e,{alwaysShowExpandBtn:s,notShowExpandBtn:a}=this.mindMap.opt;if((!s||a)&&(n=0),e.isRoot){let o=e;e.children.forEach((l,h)=>{let d=o.top+o.height,c=l.top,f=e.left+e.width/2,m=`M ${f},${d} L ${f},${c}`;this.setLineStyle(r,t[h],m,l),o=l})}else if(e.dir===k.LAYOUT_GROW_DIR.RIGHT){let o=e.left+e.width,l=e.top+e.height/2,d=(this.getMarginX(e.layerIndex+1)-n)*.6;e.children.forEach((c,f)=>{let m=c.left,g=c.top+c.height/2,x=this.createFoldLine([[o,l],[o+d,l],[o+d,g],[m,g]]);this.setLineStyle(r,t[f],x,c)})}else{let o=e.left,l=e.top+e.height/2,d=(this.getMarginX(e.layerIndex+1)-n)*.6;e.children.forEach((c,f)=>{let m=c.left+c.width,g=c.top+c.height/2,x=this.createFoldLine([[o,l],[o-d,l],[o-d,g],[m,g]]);this.setLineStyle(r,t[f],x,c)})}}renderLineDirect(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0),e.children.forEach((c,f)=>{if(e.isRoot){let m=e;e.children.forEach((g,x)=>{let y=m.top+m.height,b=g.top,S=e.left+e.width/2,A=`M ${S},${y} L ${S},${b}`;this.setLineStyle(r,t[x],A,g),m=g})}else{let m=c.dir===k.LAYOUT_GROW_DIR.LEFT?n-l:n+a+l,g=s+o/2,x=c.dir===k.LAYOUT_GROW_DIR.LEFT?c.left+c.width:c.left,y=c.top+c.height/2,b=`M ${m},${g} L ${x},${y}`;this.setLineStyle(r,t[f],b,c)}})}renderLineCurve(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0),e.children.forEach((c,f)=>{if(e.isRoot){let m=e;e.children.forEach((g,x)=>{let y=m.top+m.height,b=g.top,S=e.left+e.width/2,A=`M ${S},${y} L ${S},${b}`;this.setLineStyle(r,t[x],A,g),m=g})}else{let m=c.dir===k.LAYOUT_GROW_DIR.LEFT?n-l:n+a+l,g=s+o/2,x=c.dir===k.LAYOUT_GROW_DIR.LEFT?c.left+c.width:c.left,y=c.top+c.height/2,b=this.cubicBezierPath(m,g,x,y);this.setLineStyle(r,t[f],b,c)}})}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s,isRoot:a}=e;if(!a){let{translateX:o,translateY:l}=t.transform();e.dir===k.LAYOUT_GROW_DIR.RIGHT?t.translate(r-o,n/2-l):t.translate(-s-o,n/2-l)}}renderGeneralization(e){e.forEach(t=>{let r=t.node.dir===k.LAYOUT_GROW_DIR.LEFT,{top:n,bottom:s,left:a,right:o,generalizationLineMargin:l,generalizationNodeMargin:h}=this.getNodeGeneralizationRenderBoundaries(t,"h"),d=r?a-l:o+l,c=d,f=n,m=d,g=s,x=c+(r?-20:20),y=f+(g-f)/2,b=`M ${c},${f} Q ${x},${y} ${m},${g}`;t.generalizationLine.plot(this.transformPath(b)),t.generalizationNode.left=d+(r?-h:h)-(r?t.generalizationNode.width:0),t.generalizationNode.top=n+(s-n-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){s.dir===k.LAYOUT_GROW_DIR.LEFT?e.size(t,n).x(-t).y(0):e.size(t,n).x(r).y(0)}},Mc=wp});var er,Pv=T(()=>{pe();er={top:{renderExpandBtn({node:i,btn:e,expandBtnSize:t,translateX:r,translateY:n,width:s,height:a}){i.parent&&i.parent.isRoot?e.translate(s*.3-t/2-r,-t/2-n):e.translate(s*.3-t/2-r,a+t/2-n)},renderLine({node:i,line:e,top:t,x:r,lineLength:n,height:s,expandBtnSize:a,maxy:o,ctx:l}){i.parent&&i.parent.isRoot?e.plot(l.transformPath(`M ${r},${t} L ${r+n},${t-Math.tan(fn(l.mindMap.opt.fishboneDeg))*n}`)):e.plot(l.transformPath(`M ${r},${t+s+a} L ${r},${o}`))},computedLeftTopValue({layerIndex:i,node:e,ctx:t}){if(i>=1&&e.children){let r=t.getMarginY(i+1),n=e.left+e.width*t.childIndent,s=e.top+e.height+(t.getNodeActChildrenLength(e)>0?e.expandBtnSize:0)+r;e.children.forEach(a=>{a.left=n,a.top+=s,s+=a.height+(t.getNodeActChildrenLength(a)>0?a.expandBtnSize:0)+r})}},adjustLeftTopValueBefore({node:i,parent:e,ctx:t,layerIndex:r}){let n=i.children.length,s=t.getMarginY(r+1);if(e&&!e.isRoot&&n>0){let a=i.children.reduce((o,l)=>o+l.height+(t.getNodeActChildrenLength(l)>0?l.expandBtnSize:0)+s,0);t.updateBrothersTop(i,a)}},adjustLeftTopValueAfter({parent:i,node:e,ctx:t}){if(i&&i.isRoot){let r=t.getMarginY(e.layerIndex+1),n=e.expandBtnSize+r;e.children.forEach(s=>{let a=t.getNodeAreaHeight(s),o=s.top,l=s.left;s.top=e.top-(s.top-e.top)-a+e.height,s.left=e.left+e.width*t.indent+(a+n)/Math.tan(fn(t.mindMap.opt.fishboneDeg)),n+=a,t.updateChildrenPro(s.children,{top:s.top-o,left:s.left-l})})}}},bottom:{renderExpandBtn({node:i,btn:e,expandBtnSize:t,translateX:r,translateY:n,width:s,height:a}){i.parent&&i.parent.isRoot?e.translate(s*.3-t/2-r,a+t/2-n):e.translate(s*.3-t/2-r,-t/2-n)},renderLine({node:i,line:e,top:t,x:r,lineLength:n,height:s,miny:a,ctx:o}){i.parent&&i.parent.isRoot?e.plot(o.transformPath(`M ${r},${t+s} L ${r+n},${t+s+Math.tan(fn(o.mindMap.opt.fishboneDeg))*n}`)):e.plot(o.transformPath(`M ${r},${t} L ${r},${a}`))},computedLeftTopValue({layerIndex:i,node:e,ctx:t}){let r=t.getMarginY(i+1);if(i===1&&e.children){let n=e.left+e.width*t.childIndent,s=e.top+e.height+(t.getNodeActChildrenLength(e)>0?e.expandBtnSize:0)+r;e.children.forEach(a=>{a.left=n,a.top=s+(t.getNodeActChildrenLength(a)>0?a.expandBtnSize:0),s+=a.height+(t.getNodeActChildrenLength(a)>0?a.expandBtnSize:0)+r})}if(i>1&&e.children){let n=e.left+e.width*t.childIndent,s=e.top-(t.getNodeActChildrenLength(e)>0?e.expandBtnSize:0)-r;e.children.forEach(a=>{a.left=n,a.top=s-a.height,s-=a.height+(t.getNodeActChildrenLength(a)>0?a.expandBtnSize:0)+r})}},adjustLeftTopValueBefore({node:i,ctx:e,layerIndex:t}){let r=e.getMarginY(t+1),n=i.children.length;if(t>2&&n>0){let s=i.children.reduce((a,o)=>a+o.height+(e.getNodeActChildrenLength(o)>0?o.expandBtnSize:0)+r,0);e.updateBrothersTop(i,-s)}},adjustLeftTopValueAfter({parent:i,node:e,ctx:t}){if(i&&i.isRoot){let r=t.getMarginY(e.layerIndex+1),n=0,s=e.expandBtnSize;e.children.forEach(a=>{let o=t.getNodeActChildrenLength(a)>0,l=t.getNodeAreaHeight(a),h=o?l-a.height-(o?a.expandBtnSize:0):0;h-=o?r:0;let d=n+h,c=a.left;a.top+=d,a.left=e.left+e.width*t.indent+(l+s)/Math.tan(fn(t.mindMap.opt.fishboneDeg)),n+=h,s+=l,t.updateChildrenPro(a.children,{top:d,left:a.left-c})})}}}}});var Mp,Tp,Fv=T(()=>{Ar();pe();$e();Pv();yt();Y0();Mp=class extends Mt{constructor(e={},t){super(e),this.layout=t,this.indent=.3,this.childIndent=.5,this.fishTail=null,this.maxx=0,this.headRatio=1,this.tailRatio=.6,this.paddingXRatio=.3,this.fishHeadPathStr="M4,181 C4,181, 0,177, 4,173 Q 96.09523809523809,0, 288.2857142857143,0 L 288.2857142857143,354 Q 48.047619047619044,354, 8,218.18367346938777 C8,218.18367346938777, 6,214.18367346938777, 8,214.18367346938777 L 41.183673469387756,214.18367346938777 Z",this.fishTailPathStr="M 606.9342905223708 0 Q 713.1342905223709 -177 819.3342905223708 -177 L 766.2342905223709 0 L 819.3342905223708 177 Q 713.1342905223709 177 606.9342905223708 0 z",this.bindEvent(),this.extendShape(),this.beforeChange=this.beforeChange.bind(this)}nodeIsRemoveAllLines(e){return e.isRoot||e.layerIndex===1}isFishbone2(){return this.layout===k.LAYOUT.FISHBONE2}bindEvent(){this.isFishbone2()&&(this.onCheckUpdateFishTail=this.onCheckUpdateFishTail.bind(this),this.mindMap.on("afterExecCommand",this.onCheckUpdateFishTail))}unBindEvent(){this.mindMap.off("afterExecCommand",this.onCheckUpdateFishTail)}extendShape(){this.isFishbone2()&&this.mindMap.addShape({name:"fishHead",createShape:e=>{let t=Ae(``),{width:r,height:n}=e.shapeInstance.getNodeSize();return t.size(r,n),t},getPadding:({width:e,height:t,paddingX:r,paddingY:n})=>{e+=r*2,t+=n*2;let s=this.paddingXRatio*e,a=0;return e+=s*2,a=(e/this.headRatio-t)/2,{paddingX:s,paddingY:a}}})}doLayout(e){Zt([()=>{this.computedBaseValue(),this.addFishTail()},()=>{this.computedLeftTopValue()},()=>{this.adjustLeftTopValue(),this.updateFishTailPosition()},()=>{e(this.root)}])}addFishTail(){if(!this.isFishbone2())return;let e=this.mindMap.lineDraw.findOne(".smm-layout-fishbone-tail");e?this.fishTail=e:(this.fishTail=Ae(``),this.fishTail.addClass("smm-layout-fishbone-tail"));let t=this.root.height,r=t*this.tailRatio;this.fishTail.size(r,t),this.styleFishTail(),this.mindMap.lineDraw.add(this.fishTail)}onCheckUpdateFishTail(e,t,r){if(e==="SET_NODE_DATA"){let n=!1;Object.keys(r).forEach(s=>{Mu.includes(s)&&(n=!0)}),n&&this.styleFishTail()}}styleFishTail(){this.root.style.shape(this.fishTail)}removeFishTail(){let e=this.mindMap.lineDraw.findOne(".smm-layout-fishbone-tail");e&&e.remove()}updateFishTailPosition(){this.isFishbone2()&&this.fishTail.x(this.maxx).cy(this.root.top+this.root.height/2)}computedBaseValue(){de(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{r&&this.isFishbone2()&&(e.data.shape="fishHead");let o=this.createNode(e,t,r,n,s,a);if(r)this.setNodeCenter(o);else if(t._node.dir?o.dir=t._node.dir:o.dir=s%2===0?k.LAYOUT_GROW_DIR.TOP:k.LAYOUT_GROW_DIR.BOTTOM,t._node.isRoot){let l=this.getMarginY(n),h=this.isFishbone2()?t._node.height/4:0;this.checkIsTop(o)?o.top=t._node.top-o.height-l+h:o.top=t._node.top+t._node.height+l-h}if(!e.data.expand)return!0},null,!0,0)}computedLeftTopValue(){de(this.root,null,(e,t,r,n)=>{if(e.isRoot){let a=this.getMarginX(n+1),o=this.isFishbone2()?2:1,l=e.left+e.width+e.height/o+a,h=e.left+e.width+e.height/o+a;e.children.forEach(d=>{this.checkIsTop(d)?(d.left=l,l+=d.width+a):(d.left=h+20,h+=d.width+a)})}let s={layerIndex:n,node:e,ctx:this};this.checkIsTop(e)?er.top.computedLeftTopValue(s):er.bottom.computedLeftTopValue(s)},null,!0)}adjustLeftTopValue(){de(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;let s={node:e,parent:t,layerIndex:n,ctx:this};this.checkIsTop(e)?er.top.adjustLeftTopValueBefore(s):er.bottom.adjustLeftTopValueBefore(s)},(e,t)=>{let r={parent:t,node:e,ctx:this};if(this.checkIsTop(e)?er.top.adjustLeftTopValueAfter(r):er.bottom.adjustLeftTopValueAfter(r),e.isRoot){let n=0,s=0,a=-1/0;e.children.forEach(o=>{if(this.checkIsTop(o)){o.left+=n,this.updateChildren(o.children,"left",n);let{left:l,right:h}=this.getNodeBoundaries(o,"h");h>a&&(a=h),n+=h-l}else{o.left+=s,this.updateChildren(o.children,"left",s);let{left:l,right:h}=this.getNodeBoundaries(o,"h");h>a&&(a=h),s+=h-l}}),this.maxx=a}},!0)}getNodeAreaHeight(e){let t=0,r=n=>{let s=this.getMarginY(n.layerIndex);t+=n.height+(this.getNodeActChildrenLength(n)>0?n.expandBtnSize:0)+s,n.children.length&&n.children.forEach(a=>{r(a)})};return r(e),t}updateBrothersLeft(e){let t=e.children,r=0;t.forEach(n=>{n.left+=r,n.children&&n.children.length&&this.updateChildren(n.children,"left",r);let{left:s,right:a}=this.getNodeBoundaries(n,"h"),l=a-s-n.width;l>0&&(r+=l)})}updateBrothersTop(e,t){if(e.parent&&!e.parent.isRoot){let r=e.parent.children,n=Ie(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;a>n&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.checkIsTop(e)?this.updateBrothersTop(e.parent,t):this.updateBrothersTop(e.parent,e.layerIndex===3?0:t)}}checkIsTop(e){return e.dir===k.LAYOUT_GROW_DIR.TOP}renderLine(e,t,r){if(e.layerIndex!==1&&e.children.length<=0)return[];let{top:n,height:s,expandBtnSize:a}=e,{alwaysShowExpandBtn:o,notShowExpandBtn:l}=this.mindMap.opt;(!o||l)&&(a=0);let h=e.children.length;if(e.isRoot){let d=-1/0;e.children.forEach(x=>{x.left>d&&(d=x.left);let y=this.getMarginY(x.layerIndex),b=x.left,S=e.height/2+y-(this.isFishbone2()?e.height/4:0),A=S/Math.tan(fn(this.mindMap.opt.fishboneDeg)),C=this.lineDraw.path();this.checkIsTop(x)?C.plot(this.transformPath(`M ${b-A},${x.top+x.height+S} L ${x.left},${x.top+x.height}`)):C.plot(this.transformPath(`M ${b-A},${x.top-S} L ${b},${x.top}`)),e.style.line(C),e._lines.push(C),r&&r(C,e)});let c=e.top+e.height/2,f=e.height/2+this.getMarginY(e.layerIndex+1),m=this.lineDraw.path(),g=this.isFishbone2()?this.maxx:d-f/Math.tan(fn(this.mindMap.opt.fishboneDeg));m.plot(this.transformPath(`M ${e.left+e.width},${c} L ${g},${c}`)),e.style.line(m),e._lines.push(m),r&&r(m,e)}else{let d=-1/0,c=1/0,f=-1/0,m=e.left+e.width*this.indent;if(e.children.forEach((g,x)=>{g.left>f&&(f=g.left);let y=g.top+g.height/2;if(y>d&&(d=y),y1){let b=`M ${m},${y} L ${g.left},${y}`;this.setLineStyle(r,t[x],b,g)}}),h>=0){let g=this.lineDraw.path();a=h>0?a:0;let x=f-e.left-e.width*this.indent;x=Math.max(x,0);let y={node:e,line:g,top:n,x:m,lineLength:x,height:s,expandBtnSize:a,maxy:d,miny:c,ctx:this};this.checkIsTop(e)?er.top.renderLine(y):er.bottom.renderLine(y),e.style.line(g),e._lines.push(g),r&&r(g,e)}}}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s,isRoot:a}=e;if(!a){let{translateX:o,translateY:l}=t.transform(),h={node:e,btn:t,expandBtnSize:s,translateX:o,translateY:l,width:r,height:n};this.checkIsTop(e)?er.top.renderExpandBtn(h):er.bottom.renderExpandBtn(h)}}renderGeneralization(e){e.forEach(t=>{let{top:r,bottom:n,right:s,generalizationLineMargin:a,generalizationNodeMargin:o}=this.getNodeGeneralizationRenderBoundaries(t,"h"),l=s+a,h=r,d=s+a,c=n,f=l+20,m=h+(c-h)/2,g=`M ${l},${h} Q ${f},${m} ${d},${c}`;t.generalizationLine.plot(this.transformPath(g)),t.generalizationNode.left=s+o,t.generalizationNode.top=r+(n-r-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){let a="";s.dir===k.LAYOUT_GROW_DIR.TOP?a=s.layerIndex===1?k.LAYOUT_GROW_DIR.TOP:k.LAYOUT_GROW_DIR.BOTTOM:a=s.layerIndex===1?k.LAYOUT_GROW_DIR.BOTTOM:k.LAYOUT_GROW_DIR.TOP,a===k.LAYOUT_GROW_DIR.TOP?e.size(r,t).x(0).y(-t):e.size(r,t).x(0).y(n)}beforeChange(){this.isFishbone2()&&(this.root.nodeData.data.shape=k.SHAPE.RECTANGLE,this.removeFishTail(),this.unBindEvent(),this.mindMap.removeShape("fishHead"))}},Tp=Mp});var qv,l0,Hv=T(()=>{pe();$e();qv="smm-node-edit-wrap",l0=class{constructor(e){this.renderer=e,this.mindMap=e.mindMap,this.currentNode=null,this.textEditNode=null,this.showTextEdit=!1,this.cacheEditingText="",this.hasBodyMousedown=!1,this.textNodePaddingX=5,this.textNodePaddingY=3,this.isNeedUpdateTextEditNode=!1,this.mindMap.addEditNodeClass(qv),this.bindEvent()}bindEvent(){this.show=this.show.bind(this),this.onScale=this.onScale.bind(this),this.onKeydown=this.onKeydown.bind(this),this.mindMap.on("node_dblclick",(e,t,r)=>{this.show({node:e,e:t,isInserting:r})}),this.mindMap.on("draw_click",()=>{this.hideEditTextBox()}),this.mindMap.on("body_mousedown",()=>{this.hasBodyMousedown=!0}),this.mindMap.on("body_click",()=>{this.hasBodyMousedown&&(this.hasBodyMousedown=!1,this.mindMap.opt.isEndNodeTextEditOnClickOuter&&this.hideEditTextBox())}),this.mindMap.on("svg_mousedown",()=>{this.hideEditTextBox()}),this.mindMap.on("expand_btn_click",()=>{this.hideEditTextBox()}),this.mindMap.on("before_node_active",()=>{this.hideEditTextBox()}),this.mindMap.on("mousewheel",()=>{this.mindMap.opt.mousewheelAction===k.MOUSE_WHEEL_ACTION.MOVE&&this.hideEditTextBox()}),this.mindMap.keyCommand.addShortcut("F2",()=>{this.renderer.activeNodeList.length<=0||this.show({node:this.renderer.activeNodeList[0]})}),this.mindMap.on("scale",this.onScale),this.mindMap.opt.enableAutoEnterTextEditWhenKeydown&&window.addEventListener("keydown",this.onKeydown),this.mindMap.on("beforeDestroy",()=>{this.unBindEvent()}),this.mindMap.on("after_update_config",(e,t)=>{e.openRealtimeRenderOnNodeTextEdit!==t.openRealtimeRenderOnNodeTextEdit&&(this.mindMap.richText?this.mindMap.richText.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(e.openRealtimeRenderOnNodeTextEdit):this.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(e.openRealtimeRenderOnNodeTextEdit)),e.enableAutoEnterTextEditWhenKeydown!==t.enableAutoEnterTextEditWhenKeydown&&window[e.enableAutoEnterTextEditWhenKeydown?"addEventListener":"removeEventListener"]("keydown",this.onKeydown)}),this.mindMap.on("afterExecCommand",()=>{this.isShowTextEdit()&&(this.isNeedUpdateTextEditNode=!0)}),this.mindMap.on("node_tree_render_end",()=>{this.isShowTextEdit()&&this.isNeedUpdateTextEditNode&&(this.isNeedUpdateTextEditNode=!1,this.updateTextEditNode())})}unBindEvent(){window.removeEventListener("keydown",this.onKeydown)}onKeydown(e){if(e.target!==document.body)return;let t=this.mindMap.renderer.activeNodeList;if(t.length<=0||t.length>1)return;let r=t[0];r&&this.checkIsAutoEnterTextEditKey(e)&&(e.preventDefault(),this.show({node:r,e,isInserting:!1,isFromKeyDown:!0}))}checkIsAutoEnterTextEditKey(e){let t=e.keyCode;return(t===229||t>=65&&t<=90||t>=48&&t<=57)&&!this.mindMap.keyCommand.hasCombinationKey(e)}registerTmpShortcut(){this.mindMap.keyCommand.addShortcut("Enter",()=>{this.hideEditTextBox()}),this.mindMap.keyCommand.addShortcut("Tab",()=>{this.hideEditTextBox()})}isShowTextEdit(){return this.mindMap.richText?this.mindMap.richText.showTextEdit:this.showTextEdit}setIsShowTextEdit(e){this.showTextEdit=e,e?this.mindMap.keyCommand.stopCheckInSvg():this.mindMap.keyCommand.recoveryCheckInSvg()}async show({node:e,isInserting:t=!1,isFromKeyDown:r=!1,isFromScale:n=!1}){if(e.isUseCustomNodeContent())return;this.getCurrentEditNode()&&this.hideEditTextBox();let{beforeTextEdit:a,openRealtimeRenderOnNodeTextEdit:o}=this.mindMap.opt;if(typeof a=="function"){let m=!1;try{m=await a(e,t)}catch(g){m=!1,this.mindMap.opt.errorHandler(Mi.BEFORE_TEXT_EDIT_ERROR,g)}if(!m)return}let{offsetLeft:l,offsetTop:h}=y2(this.mindMap,e);this.mindMap.view.translateXY(l,h);let d=e._textData.node;o&&d.show();let c=d.node.getBoundingClientRect();o&&d.hide();let f={node:e,rect:c,isInserting:t,isFromKeyDown:r,isFromScale:n};if(this.mindMap.richText){this.mindMap.richText.showEditText(f);return}this.currentNode=e,this.showEditTextBox(f)}onOpenRealtimeRenderOnNodeTextEditConfigUpdate(e){this.textEditNode&&(this.textEditNode.style.background=e?"transparent":this.currentNode?this.getBackground(this.currentNode):"",this.textEditNode.style.boxShadow=e?"none":"0 0 20px rgba(0,0,0,.5)")}onScale(){let e=this.getCurrentEditNode();e&&(this.mindMap.richText?(this.mindMap.richText.cacheEditingText=this.mindMap.richText.getEditText(),this.mindMap.richText.showTextEdit=!1):(this.cacheEditingText=this.getEditText(),this.setIsShowTextEdit(!1)),this.show({node:e,isFromScale:!0}))}showEditTextBox({node:e,rect:t,isInserting:r,isFromKeyDown:n,isFromScale:s}){if(this.showTextEdit)return;let{nodeTextEditZIndex:a,textAutoWrapWidth:o,selectTextOnEnterEditText:l,openRealtimeRenderOnNodeTextEdit:h,autoEmptyTextWhenKeydownEnterEdit:d}=this.mindMap.opt;s||this.mindMap.emit("before_show_text_edit"),this.registerTmpShortcut(),this.textEditNode||(this.textEditNode=document.createElement("div"),this.textEditNode.classList.add(qv),this.textEditNode.style.cssText=` +`});var Rv={};tt(Rv,{default:()=>oF});var wp,zv,kc,oF,Dv=T(()=>{wg();Cv();pe();Iv();wp=!1,zv=Ac.import("formats/formula"),kc=class{constructor(e){this.opt=e,this.mindMap=e.mindMap,window.katex=jh,this.init(),this.config=this.getKatexConfig(),this.cssEl=null,this.addStyle(),this.extendQuill(),this.onDestroy=this.onDestroy.bind(this),this.mindMap.on("beforeDestroy",this.onDestroy)}onDestroy(){Object.getPrototypeOf(this.mindMap).constructor.instanceCount<=1&&(wp=!1,Ac.register("formats/formula",zv,!0))}init(){this.mindMap.opt.enableEditFormulaInRichTextEdit&&(this.mindMap.opt.transformRichTextOnEnterEdit=this.latexRichToText.bind(this),this.mindMap.opt.beforeHideRichTextEdit=this.formatLatex.bind(this))}getKatexConfig(){let e={throwOnError:!1,errorColor:"#f00",output:"mathml"},{getKatexOutputType:t}=this.mindMap.opt;t=t||function(){let n=W2();if(n&&n<=100)return"html"};let r=t()||"mathml";return e.output=["mathml","html"].includes(r)?r:"mathml",e}extendQuill(){if(wp)return;wp=!0;let e=this;class t extends zv{static create(n){let s=super.create(n);return typeof n=="string"&&(jh.render(n,s,e.config),s.setAttribute("data-value",fn(n))),s}}Ac.register("formats/formula",t,!0)}getStyleText(){let{katexFontPath:e}=this.mindMap.opt,t="";return this.config.output==="html"&&(t=_v(e)),t+=Lv(),t}addStyle(){this.cssEl=document.createElement("style"),this.cssEl.type="text/css",this.cssEl.innerHTML=this.getStyleText(),document.head.appendChild(this.cssEl)}removeStyle(){document.head.removeChild(this.cssEl)}insertFormulaToNode(e,t){let r=this.mindMap.richText;r.showEditText({node:e}),r.quill.insertEmbed(r.quill.getLength()-1,"formula",t),r.hideEditText([e])}latexRichToText(e){if(e.indexOf('class="ql-formula"')!==-1){let n=new DOMParser().parseFromString(e,"text/html").getElementsByClassName("ql-formula");for(let s of n)e=e.replace(s.outerHTML,`$${s.getAttribute("data-value")}$`);this.mindMap.opt.openRealtimeRenderOnNodeTextEdit&&setTimeout(()=>{this.mindMap.emit("node_text_edit_change",{node:this.mindMap.richText.node,text:this.mindMap.richText.getEditText(),richText:!0})},0)}return e}formatLatex(e){let t=e.quill.getContents(),r=t.ops,n=!1;for(let s=r.length-1;s>=0;s--){let o=r[s].insert;if(o&&typeof o!="object"&&o!==` +`&&/\$.+?\$/g.test(o)){let l=[...o.matchAll(/\$.+?\$/g)],h=o.split(/\$.+?\$/g);for(let d=l.length-1;d>=0;d--){let c=l[d]&&l[d][0]&&l[d][0].slice(1,-1)||null;c!==null&&c.trim().length>0&&this.checkFormulaIsLegal(c)?(h.splice(d+1,0,{insert:{formula:c}}),n=!0):h.splice(d+1,0,"")}for(;h.length>0;){let d=h.pop();if(typeof d=="string"){if(d.length<1)continue;d={insert:d}}d.attributes=r[s].attributes,r.splice(s+1,0,d)}r.splice(s,1)}}n&&e.quill.setContents(t)}checkFormulaIsLegal(e){try{return jh.renderToString(e),!0}catch{return!1}}beforePluginRemove(){this.removeStyle(),this.mindMap.off("beforeDestroy",this.onDestroy)}beforePluginDestroy(){this.removeStyle(),this.mindMap.off("beforeDestroy",this.onDestroy)}};kc.instanceName="formula";oF=kc});var Mp,Ov,Bv=T(()=>{Ge();Mp=class{constructor(e={}){this.opt=e,this.mindMap=this.opt.mindMap,this.scale=1,this.sx=0,this.sy=0,this.x=0,this.y=0,this.firstDrag=!0,this.setTransformData(this.mindMap.opt.viewData),this.bind()}bind(){this.mindMap.keyCommand.addShortcut("Control+=",()=>{this.enlarge()}),this.mindMap.keyCommand.addShortcut("Control+-",()=>{this.narrow()}),this.mindMap.keyCommand.addShortcut("Control+i",()=>{this.fit()}),this.mindMap.event.on("mousedown",e=>{let{isDisableDrag:t,mousedownEventPreventDefault:r}=this.mindMap.opt;t||(r&&e.preventDefault(),this.sx=this.x,this.sy=this.y)}),this.mindMap.event.on("drag",(e,t)=>{e.ctrlKey||e.metaKey||this.mindMap.opt.isDisableDrag||(this.firstDrag&&(this.firstDrag=!1,this.mindMap.renderer.activeNodeList.length>0&&this.mindMap.execCommand("CLEAR_ACTIVE_NODE")),this.x=this.sx+t.mousemoveOffset.x,this.y=this.sy+t.mousemoveOffset.y,this.transform())}),this.mindMap.event.on("mouseup",()=>{this.firstDrag=!0}),this.mindMap.event.on("mousewheel",(e,t,r,n)=>{let{customHandleMousewheel:s,mousewheelAction:a,mouseScaleCenterUseMousePosition:o,mousewheelMoveStep:l,mousewheelZoomActionReverse:h,disableMouseWheelZoom:d,translateRatio:c}=this.mindMap.opt;if(s&&typeof s=="function")return s(e);if(a===C.MOUSE_WHEEL_ACTION.ZOOM||e.ctrlKey||e.metaKey){if(d)return;let{x:f,y:m}=this.mindMap.toPos(e.clientX,e.clientY),g=o?f:void 0,x=o?m:void 0;switch(n&&(t.includes(C.DIR.LEFT)||t.includes(C.DIR.RIGHT))&&(t=t.filter(y=>![C.DIR.LEFT,C.DIR.RIGHT].includes(y))),!0){case t.includes(C.DIR.UP||C.DIR.LEFT):h?this.enlarge(g,x,n):this.narrow(g,x,n);break;case t.includes(C.DIR.DOWN||C.DIR.RIGHT):h?this.narrow(g,x,n):this.enlarge(g,x,n);break}}else{let f=0,m=0;n?(f=Math.abs(e.wheelDeltaX),m=Math.abs(e.wheelDeltaY)):f=m=l;let g=0,x=0;t.includes(C.DIR.DOWN)&&(x=-m),t.includes(C.DIR.UP)&&(x=m),t.includes(C.DIR.LEFT)&&(g=f),t.includes(C.DIR.RIGHT)&&(g=-f),this.translateXY(g*c,x*c)}}),this.mindMap.on("resize",()=>{this.checkNeedMindMapInCanvas()&&this.transform()})}getTransformData(){return{transform:this.mindMap.draw.transform(),state:{scale:this.scale,x:this.x,y:this.y,sx:this.sx,sy:this.sy}}}setTransformData(e){e&&(Object.keys(e.state).forEach(t=>{this[t]=e.state[t]}),this.mindMap.draw.transform({...e.transform}),this.mindMap.emit("view_data_change",this.getTransformData()),this.emitEvent("scale"),this.emitEvent("translate"))}translateXY(e,t){e===0&&t===0||(this.x+=e,this.y+=t,this.transform(),this.emitEvent("translate"))}translateX(e){e!==0&&(this.x+=e,this.transform(),this.emitEvent("translate"))}translateXTo(e){this.x=e,this.transform(),this.emitEvent("translate")}translateY(e){e!==0&&(this.y+=e,this.transform(),this.emitEvent("translate"))}translateYTo(e){this.y=e,this.transform(),this.emitEvent("translate")}transform(){try{this.limitMindMapInCanvas()}catch{}this.mindMap.draw.transform({origin:[0,0],scale:this.scale,translate:[this.x,this.y]}),this.mindMap.emit("view_data_change",this.getTransformData())}reset(){let e=this.scale!==1,t=this.x!==0||this.y!==0;this.scale=1,this.x=0,this.y=0,this.transform(),e&&this.emitEvent("scale"),t&&this.emitEvent("translate")}narrow(e,t,r){let{scaleRatio:n,minZoomRatio:s}=this.mindMap.opt;n=n/(r?5:1);let a=Math.max(this.scale-n,s/100);this.scaleInCenter(a,e,t),this.transform(),this.emitEvent("scale")}enlarge(e,t,r){let{scaleRatio:n,maxZoomRatio:s}=this.mindMap.opt;n=n/(r?5:1);let a=0;s===-1?a=this.scale+n:a=Math.min(this.scale+n,s/100),this.scaleInCenter(a,e,t),this.transform(),this.emitEvent("scale")}scaleInCenter(e,t,r){(t===void 0||r===void 0)&&(t=this.mindMap.width/2,r=this.mindMap.height/2);let n=this.scale,s=1-e/n,a=(t-this.x)*s,o=(r-this.y)*s;this.x+=a,this.y+=o,this.scale=e}setScale(e,t,r){t!==void 0&&r!==void 0?this.scaleInCenter(e,t,r):this.scale=e,this.transform(),this.emitEvent("scale")}fit(e=()=>{},t=!1,r){r=r===void 0?this.mindMap.opt.fitPadding:r;let n=this.mindMap.draw,s=n.transform(),a=e()||n.rbox(),o=a.width/s.scaleX,l=a.height/s.scaleY,h=o/l,{width:d,height:c}=this.mindMap.elRect;d=d-r*2,c=c-r*2;let f=d/c,m=0,g="";if(o<=d&&l<=c&&!t)m=1,g=1;else{let E=0,k=0;h>f?(E=d,k=d/h,g=2):(k=c,E=c*h,g=3),m=E/o}this.setScale(m);let x=e()||n.rbox();x.x-=this.mindMap.elRect.left,x.y-=this.mindMap.elRect.top;let y=0,b=0;g===1?(y=-x.x+r+(d-x.width)/2,b=-x.y+r+(c-x.height)/2):g===2?(y=-x.x+r,b=-x.y+r+(c-x.height)/2):g===3&&(y=-x.x+r+(d-x.width)/2,b=-x.y+r),this.translateXY(y,b)}checkNeedMindMapInCanvas(){if(this.mindMap.demonstrate&&this.mindMap.demonstrate.isInDemonstrate)return!1;let{isLimitMindMapInCanvasWhenHasScrollbar:e,isLimitMindMapInCanvas:t}=this.mindMap.opt;return this.mindMap.scrollbar?e:t}limitMindMapInCanvas(){if(!this.checkNeedMindMapInCanvas())return;let{scale:e,left:t,top:r,right:n,bottom:s}=this.getPositionLimit(),a=(this.mindMap.width-this.mindMap.initWidth)/2*e,o=(this.mindMap.height-this.mindMap.initHeight)/2*e,l=this.scale/e;t*=l,n*=l,r*=l,s*=l;let h=this.mindMap.width/2,d=this.mindMap.height/2,c=this.scale-1;t-=c*h-a,n-=c*h-a,r-=c*d-o,s-=c*d-o,this.x>t&&(this.x=t),this.xr&&(this.y=r),this.y{"use strict";var lF=Object.prototype.hasOwnProperty,jt="~";function f0(){}Object.create&&(f0.prototype=Object.create(null),new f0().__proto__||(jt=!1));function hF(i,e,t){this.fn=i,this.context=e,this.once=t||!1}function Pv(i,e,t,r,n){if(typeof t!="function")throw new TypeError("The listener must be a function");var s=new hF(t,r||i,n),a=jt?jt+e:e;return i._events[a]?i._events[a].fn?i._events[a]=[i._events[a],s]:i._events[a].push(s):(i._events[a]=s,i._eventsCount++),i}function Cc(i,e){--i._eventsCount===0?i._events=new f0:delete i._events[e]}function Lt(){this._events=new f0,this._eventsCount=0}Lt.prototype.eventNames=function(){var e=[],t,r;if(this._eventsCount===0)return e;for(r in t=this._events)lF.call(t,r)&&e.push(jt?r.slice(1):r);return Object.getOwnPropertySymbols?e.concat(Object.getOwnPropertySymbols(t)):e};Lt.prototype.listeners=function(e){var t=jt?jt+e:e,r=this._events[t];if(!r)return[];if(r.fn)return[r.fn];for(var n=0,s=r.length,a=new Array(s);n{qv=yt(Fv());Ge();Np=class extends qv.default{constructor(e={}){super(),this.opt=e,this.mindMap=e.mindMap,this.isLeftMousedown=!1,this.isRightMousedown=!1,this.isMiddleMousedown=!1,this.mousedownPos={x:0,y:0},this.mousemovePos={x:0,y:0},this.mousemoveOffset={x:0,y:0},this.bindFn(),this.bind()}bindFn(){this.onBodyMousedown=this.onBodyMousedown.bind(this),this.onBodyClick=this.onBodyClick.bind(this),this.onDrawClick=this.onDrawClick.bind(this),this.onMousedown=this.onMousedown.bind(this),this.onMousemove=this.onMousemove.bind(this),this.onMouseup=this.onMouseup.bind(this),this.onNodeMouseup=this.onNodeMouseup.bind(this),this.onMousewheel=this.onMousewheel.bind(this),this.onContextmenu=this.onContextmenu.bind(this),this.onSvgMousedown=this.onSvgMousedown.bind(this),this.onKeyup=this.onKeyup.bind(this),this.onMouseenter=this.onMouseenter.bind(this),this.onMouseleave=this.onMouseleave.bind(this)}bind(){document.body.addEventListener("mousedown",this.onBodyMousedown),document.body.addEventListener("click",this.onBodyClick),this.mindMap.svg.on("click",this.onDrawClick),this.mindMap.el.addEventListener("mousedown",this.onMousedown),this.mindMap.svg.on("mousedown",this.onSvgMousedown),window.addEventListener("mousemove",this.onMousemove),window.addEventListener("mouseup",this.onMouseup),this.on("node_mouseup",this.onNodeMouseup),this.mindMap.el.addEventListener("wheel",this.onMousewheel),this.mindMap.svg.on("contextmenu",this.onContextmenu),this.mindMap.svg.on("mouseenter",this.onMouseenter),this.mindMap.svg.on("mouseleave",this.onMouseleave),window.addEventListener("keyup",this.onKeyup)}unbind(){document.body.removeEventListener("mousedown",this.onBodyMousedown),document.body.removeEventListener("click",this.onBodyClick),this.mindMap.svg.off("click",this.onDrawClick),this.mindMap.el.removeEventListener("mousedown",this.onMousedown),window.removeEventListener("mousemove",this.onMousemove),window.removeEventListener("mouseup",this.onMouseup),this.off("node_mouseup",this.onNodeMouseup),this.mindMap.el.removeEventListener("wheel",this.onMousewheel),this.mindMap.svg.off("contextmenu",this.onContextmenu),this.mindMap.svg.off("mouseenter",this.onMouseenter),this.mindMap.svg.off("mouseleave",this.onMouseleave),window.removeEventListener("keyup",this.onKeyup)}onDrawClick(e){this.emit("draw_click",e)}onBodyMousedown(e){this.emit("body_mousedown",e)}onBodyClick(e){this.emit("body_click",e)}onSvgMousedown(e){this.emit("svg_mousedown",e)}onMousedown(e){e.which===1?this.isLeftMousedown=!0:e.which===3?this.isRightMousedown=!0:e.which===2&&(this.isMiddleMousedown=!0),this.mousedownPos.x=e.clientX,this.mousedownPos.y=e.clientY,this.emit("mousedown",e,this)}onMousemove(e){let{useLeftKeySelectionRightKeyDrag:t}=this.mindMap.opt;this.mousemovePos.x=e.clientX,this.mousemovePos.y=e.clientY,this.mousemoveOffset.x=e.clientX-this.mousedownPos.x,this.mousemoveOffset.y=e.clientY-this.mousedownPos.y,this.emit("mousemove",e,this),(this.isMiddleMousedown||(t?this.isRightMousedown:this.isLeftMousedown))&&(e.preventDefault(),this.emit("drag",e,this))}onMouseup(e){this.onNodeMouseup(),this.emit("mouseup",e,this)}onNodeMouseup(){this.isLeftMousedown=!1,this.isRightMousedown=!1,this.isMiddleMousedown=!1}onMousewheel(e){e.stopPropagation(),e.preventDefault();let t=[];e.deltaY<0&&t.push(C.DIR.UP),e.deltaY>0&&t.push(C.DIR.DOWN),e.deltaX<0&&t.push(C.DIR.LEFT),e.deltaX>0&&t.push(C.DIR.RIGHT);let r=!1,{customCheckIsTouchPad:n}=this.mindMap.opt;typeof n=="function"?r=n(e):r=Math.abs(e.deltaY)<=10,this.emit("mousewheel",e,t,this,r)}onContextmenu(e){e.preventDefault(),!e.ctrlKey&&this.emit("contextmenu",e)}onKeyup(e){this.emit("keyup",e)}onMouseenter(e){this.emit("svg_mouseenter",e)}onMouseleave(e){this.emit("svg_mouseleave",e)}},Hv=Np});var Ep,Sp,$v=T(()=>{Ar();pe();Ge();Ep=class extends Nt{constructor(e={},t){super(e),this.isUseLeft=t===C.LAYOUT.LOGICAL_STRUCTURE_LEFT}doLayout(e){Jt([()=>{this.computedBaseValue()},()=>{this.computedTopValue()},()=>{this.adjustTopValue()},()=>{e(this.root)}])}computedBaseValue(){let e=0;ue(this.renderer.renderTree,null,(t,r,n,s,a,o)=>{let l=this.createNode(t,r,n,s,a,o);if(l.sortIndex=e,e++,n?this.setNodeCenter(l):this.isUseLeft?l.left=r._node.left-l.width-this.getMarginX(s):l.left=r._node.left+r._node.width+this.getMarginX(s),!t.data.expand)return!0},(t,r,n,s)=>{let a=t.data.expand===!1?0:t._node.children.length;t._node.childrenAreaHeight=a?t._node.children.reduce((l,h)=>l+h.height,0)+(a+1)*this.getMarginY(s+1):0;let o=t._node.checkHasGeneralization()?t._node._generalizationNodeHeight+this.getMarginY(s+1):0;t._node.childrenAreaHeight2=Math.max(t._node.childrenAreaHeight,o)},!0,0)}computedTopValue(){ue(this.root,null,(e,t,r,n)=>{if(e.getData("expand")&&e.children&&e.children.length){let s=this.getMarginY(n+1),o=e.top+e.height/2-e.childrenAreaHeight/2+s;e.children.forEach(l=>{l.top=o,o+=l.height+s})}},null,!0)}adjustTopValue(){ue(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;let s=e.childrenAreaHeight2-this.getMarginY(n+1)*2-e.height;s>0&&this.updateBrothers(e,s/2)},null,!0)}updateBrothers(e,t){if(e.parent){let r=e.parent.children,n=ze(e,r);r.forEach((s,a)=>{if(s.uid===e.uid||s.hasCustomPosition())return;let o=0;an&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothers(e.parent,t)}}renderLine(e,t,r,n){n==="curve"?this.renderLineCurve(e,t,r):n==="direct"?this.renderLineDirect(e,t,r):this.renderLineStraight(e,t,r)}renderLineStraight(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let f=(this.getMarginX(e.layerIndex+1)-l)*.6;this.isUseLeft&&(f*=-1);let m=this.mindMap.themeConfig.nodeUseLineStyle;e.children.forEach((g,x)=>{let y;this.isUseLeft?y=e.layerIndex===0?n:n-l:y=e.layerIndex===0?n+a:n+a+l;let b=s+o/2,E=this.isUseLeft?g.left+g.width:g.left,k=g.top+g.height/2,L=m?g.width*(this.isUseLeft?-1:1):0;b=m&&!e.isRoot?b+o/2:b,k=m?k+g.height/2:k;let I=this.createFoldLine([[y,b],[y+f,b],[y+f,k],[E+L,k]]);this.setLineStyle(r,t[x],I,g)})}renderLineDirect(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c}=this.mindMap.themeConfig;e.children.forEach((f,m)=>{e.layerIndex===0&&(l=0);let g=this.isUseLeft?n-l:n+a+l,x=s+o/2,y=this.isUseLeft?f.left+f.width:f.left,b=f.top+f.height/2;x=c&&!e.isRoot?x+o/2:x,b=c?b+f.height/2:b;let E=c?` L ${this.isUseLeft?f.left:f.left+f.width},${b}`:"",k=`M ${g},${x} L ${y},${b}`+E;this.setLineStyle(r,t[m],k,f)})}renderLineCurve(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c,rootLineStartPositionKeepSameInCurve:f,rootLineKeepSameInCurve:m}=this.mindMap.themeConfig;e.children.forEach((g,x)=>{e.layerIndex===0&&(l=0);let y;this.isUseLeft?y=e.layerIndex===0&&!f?n+a/2:n-l:y=e.layerIndex===0&&!f?n+a/2:n+a+l;let b=s+o/2,E=this.isUseLeft?g.left+g.width:g.left,k=g.top+g.height/2,L="";b=c&&!e.isRoot?b+o/2:b,k=c?k+g.height/2:k;let I;this.isUseLeft?I=c?` L ${g.left},${k}`:"":I=c?` L ${g.left+g.width},${k}`:"",e.isRoot&&!m?L=this.quadraticCurvePath(y,b,E,k)+I:L=this.cubicBezierPath(y,b,E,k)+I,this.setLineStyle(r,t[x],L,g)})}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s,layerIndex:a}=e;a===0&&(s=0);let{translateX:o,translateY:l}=t.transform(),h=this.mindMap.themeConfig.nodeUseLineStyle?n/2:0,d=this.isUseLeft?0-s:r,c=n/2+h;d===o&&c===l||t.translate(d-o,c-l)}renderGeneralization(e){e.forEach(t=>{let{left:r,top:n,bottom:s,right:a,generalizationLineMargin:o,generalizationNodeMargin:l}=this.getNodeGeneralizationRenderBoundaries(t,"h"),h=this.isUseLeft?r-o:a+o,d=h,c=n,f=h,m=s,g=d+(this.isUseLeft?-20:20),x=c+(m-c)/2,y=`M ${d},${c} Q ${g},${x} ${f},${m}`;t.generalizationLine.plot(y),t.generalizationNode.left=h+(this.isUseLeft?-l:l)-(this.isUseLeft?t.generalizationNode.width:0),t.generalizationNode.top=n+(s-n-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n){this.isUseLeft?e.size(t,n).x(-t).y(0):e.size(t,n).x(r).y(0)}},Sp=Ep});var Ap,jv,Gv=T(()=>{Ar();pe();Ge();Ap=class extends Nt{constructor(e={}){super(e)}doLayout(e){Jt([()=>{this.computedBaseValue()},()=>{this.computedTopValue()},()=>{this.adjustTopValue()},()=>{e(this.root)}])}computedBaseValue(){ue(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):(t._node.dir?o.dir=t._node.dir:o.dir=o.getData("dir")||(s%2===0?C.LAYOUT_GROW_DIR.RIGHT:C.LAYOUT_GROW_DIR.LEFT),o.left=o.dir===C.LAYOUT_GROW_DIR.RIGHT?t._node.left+t._node.width+this.getMarginX(n):t._node.left-this.getMarginX(n)-o.width),!e.data.expand)return!0},(e,t,r,n)=>{if(!e.data.expand){e._node.leftChildrenAreaHeight=0,e._node.rightChildrenAreaHeight=0;return}let s=0,a=0,o=0,l=0;e._node.children.forEach(d=>{d.dir===C.LAYOUT_GROW_DIR.LEFT?(s++,o+=d.height):(a++,l+=d.height)}),e._node.leftChildrenAreaHeight=o+(s+1)*this.getMarginY(n+1),e._node.rightChildrenAreaHeight=l+(a+1)*this.getMarginY(n+1);let h=e._node.checkHasGeneralization()?e._node._generalizationNodeHeight+this.getMarginY(n+1):0;e._node.leftChildrenAreaHeight2=Math.max(e._node.leftChildrenAreaHeight,h),e._node.rightChildrenAreaHeight2=Math.max(e._node.rightChildrenAreaHeight,h)},!0,0)}computedTopValue(){ue(this.root,null,(e,t,r,n)=>{if(e.getData("expand")&&e.children&&e.children.length){let s=this.getMarginY(n+1),a=e.top+e.height/2+s,o=a-e.leftChildrenAreaHeight/2,l=a-e.rightChildrenAreaHeight/2;e.children.forEach(h=>{h.dir===C.LAYOUT_GROW_DIR.LEFT?(h.top=o,o+=h.height+s):(h.top=l,l+=h.height+s)})}},null,!0)}adjustTopValue(){ue(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;let s=this.getMarginY(n+1)*2+e.height,a=e.leftChildrenAreaHeight2-s,o=e.rightChildrenAreaHeight2-s;(a>0||o>0)&&this.updateBrothers(e,a/2,o/2)},null,!0)}updateBrothers(e,t,r){if(e.parent){let n=e.parent.children.filter(a=>a.dir===e.dir),s=ze(e,n);n.forEach((a,o)=>{if(a.hasCustomPosition())return;let l=0,h=a.dir===C.LAYOUT_GROW_DIR.LEFT?t:r;os&&(l=h),a.top+=l,a.children&&a.children.length&&this.updateChildren(a.children,"top",l)}),this.updateBrothers(e.parent,t,r)}}renderLine(e,t,r,n){n==="curve"?this.renderLineCurve(e,t,r):n==="direct"?this.renderLineDirect(e,t,r):this.renderLineStraight(e,t,r)}renderLineStraight(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let f=(this.getMarginX(e.layerIndex+1)-l)*.6,m=this.mindMap.themeConfig.nodeUseLineStyle;e.children.forEach((g,x)=>{let y=0,b=0,E=m?g.width:0;g.dir===C.LAYOUT_GROW_DIR.LEFT?(b=-f,y=e.layerIndex===0?n:n-l,E=-E):(b=f,y=e.layerIndex===0?n+a:n+a+l);let k=s+o/2,L=g.dir===C.LAYOUT_GROW_DIR.LEFT?g.left+g.width:g.left,I=g.top+g.height/2;k=m&&!e.isRoot?k+o/2:k,I=m?I+g.height/2:I;let O=this.createFoldLine([[y,k],[y+b,k],[y+b,I],[L+E,I]]);this.setLineStyle(r,t[x],O,g)})}renderLineDirect(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c}=this.mindMap.themeConfig;e.children.forEach((f,m)=>{e.layerIndex===0&&(l=0);let g=f.dir===C.LAYOUT_GROW_DIR.LEFT?n-l:n+a+l,x=s+o/2,y=f.dir===C.LAYOUT_GROW_DIR.LEFT?f.left+f.width:f.left,b=f.top+f.height/2;x=c&&!e.isRoot?x+o/2:x,b=c?b+f.height/2:b;let E="";c&&(f.dir===C.LAYOUT_GROW_DIR.LEFT?E=` L ${f.left},${b}`:E=` L ${f.left+f.width},${b}`);let k=`M ${g},${x} L ${y},${b}`+E;this.setLineStyle(r,t[m],k,f)})}renderLineCurve(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c,rootLineKeepSameInCurve:f,rootLineStartPositionKeepSameInCurve:m}=this.mindMap.themeConfig;e.children.forEach((g,x)=>{e.layerIndex===0&&(l=0);let y=e.layerIndex===0&&!m?n+a/2:g.dir===C.LAYOUT_GROW_DIR.LEFT?n-l:n+a+l,b=s+o/2,E=g.dir===C.LAYOUT_GROW_DIR.LEFT?g.left+g.width:g.left,k=g.top+g.height/2,L="";b=c&&!e.isRoot?b+o/2:b,k=c?k+g.height/2:k;let I="";c&&(g.dir===C.LAYOUT_GROW_DIR.LEFT?I=` L ${g.left},${k}`:I=` L ${g.left+g.width},${k}`),e.isRoot&&!f?L=this.quadraticCurvePath(y,b,E,k)+I:L=this.cubicBezierPath(y,b,E,k)+I,this.setLineStyle(r,t[x],L,g)})}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s}=e,{translateX:a,translateY:o}=t.transform(),l=this.mindMap.themeConfig.nodeUseLineStyle?n/2:0,h=e.dir===C.LAYOUT_GROW_DIR.LEFT?0-s:r,d=n/2+l;if(h===a&&d===o)return;let c=h-a,f=d-o;t.translate(c,f)}renderGeneralization(e){e.forEach(t=>{let r=t.node.dir===C.LAYOUT_GROW_DIR.LEFT,{top:n,bottom:s,left:a,right:o,generalizationLineMargin:l,generalizationNodeMargin:h}=this.getNodeGeneralizationRenderBoundaries(t,"h"),d=r?a-l:o+l,c=d,f=n,m=d,g=s,x=c+(r?-20:20),y=f+(g-f)/2,b=`M ${c},${f} Q ${x},${y} ${m},${g}`;t.generalizationLine.plot(b),t.generalizationNode.left=d+(r?-h:h)-(r?t.generalizationNode.width:0),t.generalizationNode.top=n+(s-n-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){s.dir===C.LAYOUT_GROW_DIR.LEFT?e.size(t,n).x(-t).y(0):e.size(t,n).x(r).y(0)}},jv=Ap});var kp,Wv,Vv=T(()=>{Ar();pe();kp=class extends Nt{constructor(e={}){super(e)}doLayout(e){Jt([()=>{this.computedBaseValue()},()=>{this.computedLeftTopValue()},()=>{this.adjustLeftTopValue()},()=>{e(this.root)}])}computedBaseValue(){ue(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):t._node.isRoot&&(o.top=t._node.top+t._node.height+this.getMarginX(n)),!e.data.expand)return!0},(e,t,r,n)=>{if(r){let s=e.data.expand===!1?0:e._node.children.length;e._node.childrenAreaWidth=s?e._node.children.reduce((a,o)=>a+o.width,0)+(s+1)*this.getMarginX(n+1):0}},!0,0)}computedLeftTopValue(){ue(this.root,null,(e,t,r,n)=>{if(e.getData("expand")&&e.children&&e.children.length){let s=this.getMarginX(n+1),a=this.getMarginY(n+1);if(r){let l=e.left+e.width/2-e.childrenAreaWidth/2+s;e.children.forEach(h=>{h.left=l,l+=h.width+s})}else{let o=e.top+this.getNodeHeightWithGeneralization(e)+a+(this.getNodeActChildrenLength(e)>0?e.expandBtnSize:0);e.children.forEach(l=>{l.left=e.left+e.width*.5,l.top=o,o+=this.getNodeHeightWithGeneralization(l)+a+(this.getNodeActChildrenLength(l)>0?l.expandBtnSize:0)})}}},null,!0)}adjustLeftTopValue(){ue(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;if(t&&t.isRoot){let o=this.getNodeAreaWidth(e,!0)-e.width;o>0&&this.updateBrothersLeft(e,o)}let s=e.children.length;if(t&&!t.isRoot&&s>0){let a=this.getMarginY(n+1),o=e.children.reduce((l,h)=>l+this.getNodeHeightWithGeneralization(h)+(this.getNodeActChildrenLength(h)>0?h.expandBtnSize:0),0)+s*a;this.updateBrothersTop(e,o)}},(e,t,r)=>{if(r){let{right:n,left:s}=this.getNodeBoundaries(e,"h"),a=n-s,o=e.left-s-(a-e.width)/2;this.updateChildren(e.children,"left",o)}},!0)}updateBrothersLeft(e,t){if(e.parent){let r=e.parent.children,n=ze(e,r);r.forEach((s,a)=>{s.hasCustomPosition()||a<=n||(s.left+=t,s.children&&s.children.length&&this.updateChildren(s.children,"left",t))}),this.updateBrothersLeft(e.parent,t)}}updateBrothersTop(e,t){if(e.parent&&!e.parent.isRoot){let r=e.parent.children,n=ze(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;a>n&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothersTop(e.parent,t)}}renderLine(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let c=e.children.length,f=this.getMarginX(e.layerIndex+1);if(e.isRoot){let m=n+a/2,g=s+o,x=f*.7,y=1/0,b=-1/0;e.children.forEach((k,L)=>{let I=k.left+k.width/2,O=k.top;Ib&&(b=I);let q=this.mindMap.themeConfig.nodeUseLineStyle?` L ${k.left},${O} L ${k.left+k.width},${O}`:"",F=`M ${I},${g+x} L ${I},${g+x>O?O+k.height:O}`+q;this.setLineStyle(r,t[L],F,k)}),y=Math.min(y,m),b=Math.max(b,m);let E=this.lineDraw.path();if(e.style.line(E),E.plot(this.transformPath(`M ${m},${g} L ${m},${g+x}`)),e._lines.push(E),r&&r(E,e),c>0){let k=this.lineDraw.path();e.style.line(k),k.plot(this.transformPath(`M ${y},${g+x} L ${b},${g+x}`)),e._lines.push(k),r&&r(k,e)}}else{let m=s+o,g=-1/0,x=e.left+e.width*.3;if(e.children.forEach((y,b)=>{let E=y.top+y.height/2;E>g&&(g=E);let k="",L=y.left,I=y.left+y.widthx&&(O=!0,E=y.top,g=E),E>s&&E0){let y=this.lineDraw.path();l=c>0?l:0,e.style.line(y),g{let{top:r,bottom:n,right:s,generalizationLineMargin:a,generalizationNodeMargin:o}=this.getNodeGeneralizationRenderBoundaries(t,"h"),l=s+a,h=r,d=s+a,c=n,f=l+20,m=h+(c-h)/2,g=`M ${l},${h} Q ${f},${m} ${d},${c}`;t.generalizationLine.plot(this.transformPath(g)),t.generalizationNode.left=s+o,t.generalizationNode.top=r+(n-r-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){e.size(r,t).x(0).y(n)}},Wv=kp});var Cp,Yv,Xv=T(()=>{Ar();pe();Cp=class extends Nt{constructor(e={}){super(e)}doLayout(e){Jt([()=>{this.computedBaseValue()},()=>{this.computedLeftValue()},()=>{this.adjustLeftValue()},()=>{e(this.root)}])}computedBaseValue(){ue(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):o.top=t._node.top+t._node.height+this.getMarginX(n),!e.data.expand)return!0},(e,t,r,n)=>{let s=e.data.expand===!1?0:e._node.children.length;e._node.childrenAreaWidth=s?e._node.children.reduce((o,l)=>o+l.width,0)+(s+1)*this.getMarginY(n+1):0;let a=e._node.checkHasGeneralization()?e._node._generalizationNodeWidth+this.getMarginY(n+1):0;e._node.childrenAreaWidth2=Math.max(e._node.childrenAreaWidth,a)},!0,0)}computedLeftValue(){ue(this.root,null,(e,t,r,n)=>{if(e.getData("expand")&&e.children&&e.children.length){let s=this.getMarginY(n+1),o=e.left+e.width/2-e.childrenAreaWidth/2+s;e.children.forEach(l=>{l.left=o,o+=l.width+s})}},null,!0)}adjustLeftValue(){ue(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;let s=e.childrenAreaWidth2-this.getMarginY(n+1)*2-e.width;s>0&&this.updateBrothers(e,s/2)},null,!0)}updateBrothers(e,t){if(e.parent){let r=e.parent.children,n=ze(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;an&&(o=t),s.left+=o,s.children&&s.children.length&&this.updateChildren(s.children,"left",o)}),this.updateBrothers(e.parent,t)}}renderLine(e,t,r,n){n==="curve"?this.renderLineCurve(e,t,r):n==="direct"?this.renderLineDirect(e,t,r):this.renderLineStraight(e,t,r)}renderLineCurve(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let{nodeUseLineStyle:c,rootLineStartPositionKeepSameInCurve:f,rootLineKeepSameInCurve:m}=this.mindMap.themeConfig;e.children.forEach((g,x)=>{e.layerIndex===0&&(l=0);let y=n+a/2,b=e.layerIndex===0&&!f?s+o/2:s+o+l,E=g.left+g.width/2,k=g.top,L="",I=c?` L ${g.left},${k} L ${g.left+g.width},${k}`:"";e.isRoot&&!m?L=this.quadraticCurvePath(y,b,E,k,!0)+I:L=this.cubicBezierPath(y,b,E,k,!0)+I,this.setLineStyle(r,t[x],L,g)})}renderLineDirect(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o}=e,{nodeUseLineStyle:l}=this.mindMap.themeConfig,h=n+a/2,d=s+o;e.children.forEach((c,f)=>{let m=c.left+c.width/2,g=c.top,x=l?` L ${c.left},${g} L ${c.left+c.width},${g}`:"",y=`M ${h},${d} L ${m},${g}`+x;this.setLineStyle(r,t[f],y,c)})}renderLineStraight(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l,isRoot:h}=e,{alwaysShowExpandBtn:d,notShowExpandBtn:c}=this.mindMap.opt;(!d||c)&&(l=0);let f=n+a/2,m=s+o,x=this.getMarginX(e.layerIndex+1)*.7,y=1/0,b=-1/0,E=e.children.length;e.children.forEach((L,I)=>{let O=L.left+L.width/2,q=m+x>L.top?L.top+L.height:L.top;Ob&&(b=O);let F=this.mindMap.themeConfig.nodeUseLineStyle?` L ${L.left},${q} L ${L.left+L.width},${q}`:"",W=`M ${O},${m+x} L ${O},${q}`+F;this.setLineStyle(r,t[I],W,L)}),y=Math.min(f,y),b=Math.max(f,b);let k=this.lineDraw.path();if(e.style.line(k),l=E>0&&!h?l:0,k.plot(this.transformPath(`M ${f},${m+l} L ${f},${m+x}`)),e._lines.push(k),r&&r(k,e),E>0){let L=this.lineDraw.path();e.style.line(L),L.plot(this.transformPath(`M ${y},${m+x} L ${b},${m+x}`)),e._lines.push(L),r&&r(L,e)}}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s}=e,{translateX:a,translateY:o}=t.transform();t.translate(r/2-s/2-a,n+s/2-o)}renderGeneralization(e){e.forEach(t=>{let{bottom:r,left:n,right:s,generalizationLineMargin:a,generalizationNodeMargin:o}=this.getNodeGeneralizationRenderBoundaries(t,"v"),l=n,h=r+a,d=s,c=r+a,f=l+(d-l)/2,m=h+20,g=`M ${l},${h} Q ${f},${m} ${d},${c}`;t.generalizationLine.plot(this.transformPath(g)),t.generalizationNode.top=r+o,t.generalizationNode.left=n+(s-n-t.generalizationNode.width)/2})}renderExpandBtnRect(e,t,r,n,s){e.size(r,t).x(0).y(n)}},Yv=Cp});var _p,Lp,Kv=T(()=>{Ar();pe();Ge();_p=class extends Nt{constructor(e={},t){super(e),this.layout=t}doLayout(e){Jt([()=>{this.computedBaseValue()},()=>{this.computedLeftTopValue()},()=>{this.adjustLeftTopValue()},()=>{e(this.root)}])}computedBaseValue(){ue(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):(this.layout===C.LAYOUT.TIMELINE2?t._node.dir?o.dir=t._node.dir:o.dir=s%2===0?C.LAYOUT_GROW_DIR.BOTTOM:C.LAYOUT_GROW_DIR.TOP:o.dir="",t._node.isRoot&&(o.top=t._node.top+(e._node.height>t._node.height?-(e._node.height-t._node.height)/2:(t._node.height-e._node.height)/2))),!e.data.expand)return!0},null,!0,0)}computedLeftTopValue(){ue(this.root,null,(e,t,r,n,s)=>{if(e.getData("expand")&&e.children&&e.children.length){let a=this.getMarginX(n+1),o=this.getMarginY(n+1);if(r){let h=e.left+e.width+a;e.children.forEach(d=>{d.left=h,h+=d.width+a})}else{let l=e.top+e.height+o+(this.getNodeActChildrenLength(e)>0?e.expandBtnSize:0);e.children.forEach(h=>{h.left=e.left+e.width*.5,h.top=l,l+=h.height+o+(this.getNodeActChildrenLength(h)>0?h.expandBtnSize:0)})}}},null,!0)}adjustLeftTopValue(){ue(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;e.isRoot&&this.updateBrothersLeft(e);let s=e.children.length;if(t&&!t.isRoot&&s>0){let a=this.getMarginY(n+1),o=e.children.reduce((l,h)=>l+h.height+(this.getNodeActChildrenLength(h)>0?h.expandBtnSize:0),0)+s*a;this.updateBrothersTop(e,o)}},(e,t,r,n)=>{t&&t.isRoot&&e.dir===C.LAYOUT_GROW_DIR.TOP&&e.children.forEach(s=>{let a=this.getNodeAreaHeight(s),o=s.top;s.top=e.top-(s.top-e.top)-a+e.height,this.updateChildren(s.children,"top",s.top-o)})},!0)}getNodeAreaHeight(e){let t=0,r=n=>{t+=n.height+(this.getNodeActChildrenLength(n)>0?n.expandBtnSize:0)+this.getMarginY(n.layerIndex),n.children.length&&n.children.forEach(s=>{r(s)})};return r(e),t}updateBrothersLeft(e){let t=e.children,r=0;t.forEach(n=>{n.left+=r,n.children&&n.children.length&&this.updateChildren(n.children,"left",r);let{left:s,right:a}=this.getNodeBoundaries(n,"h"),l=a-s-n.width;l>0&&(r+=l)})}updateBrothersTop(e,t){if(e.parent&&!e.parent.isRoot){let r=e.parent.children,n=ze(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;a>n&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothersTop(e.parent,t)}}renderLine(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0);let c=e.children.length;if(e.isRoot){let f=e;e.children.forEach((m,g)=>{let x=f.left+f.width,y=m.left,b=e.top+e.height/2,E=`M ${x},${b} L ${y},${b}`;this.setLineStyle(r,t[g],E,m),f=m})}else{let f=-1/0,m=1/0,g=e.left+e.width*.3;if(e.children.forEach((x,y)=>{let b=x.top+x.height/2;b>f&&(f=b),b0){let x=this.lineDraw.path();l=c>0?l:0,e.parent&&e.parent.isRoot&&e.dir===C.LAYOUT_GROW_DIR.TOP?x.plot(this.transformPath(`M ${g},${s} L ${g},${m}`)):x.plot(this.transformPath(`M ${g},${s+o+l} L ${g},${f}`)),e.style.line(x),e._lines.push(x),r&&r(x,e)}}}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s,isRoot:a}=e;if(!a){let{translateX:o,translateY:l}=t.transform();e.parent&&e.parent.isRoot&&e.dir===C.LAYOUT_GROW_DIR.TOP?t.translate(r*.3-s/2-o,-s/2-l):t.translate(r*.3-s/2-o,n+s/2-l)}}renderGeneralization(e){e.forEach(t=>{let{top:r,bottom:n,right:s,generalizationLineMargin:a,generalizationNodeMargin:o}=this.getNodeGeneralizationRenderBoundaries(t,"h"),l=s+a,h=r,d=s+a,c=n,f=l+20,m=h+(c-h)/2,g=`M ${l},${h} Q ${f},${m} ${d},${c}`;t.generalizationLine.plot(this.transformPath(g)),t.generalizationNode.left=s+o,t.generalizationNode.top=r+(n-r-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){if(this.layout===C.LAYOUT.TIMELINE)e.size(r,t).x(0).y(n);else{let a="";s.dir===C.LAYOUT_GROW_DIR.TOP?a=s.layerIndex===1?C.LAYOUT_GROW_DIR.TOP:C.LAYOUT_GROW_DIR.BOTTOM:a=C.LAYOUT_GROW_DIR.BOTTOM,a===C.LAYOUT_GROW_DIR.TOP?e.size(r,t).x(0).y(-t):e.size(r,t).x(0).y(n)}}},Lp=_p});var Ip,_c,Zv=T(()=>{Ar();pe();Ge();Ip=class extends Nt{constructor(e={},t){super(e),this.layout=t}doLayout(e){Jt([()=>{this.computedBaseValue()},()=>{this.computedTopValue()},()=>{this.adjustLeftTopValue()},()=>{e(this.root)}])}computedBaseValue(){ue(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{let o=this.createNode(e,t,r,n,s,a);if(r?this.setNodeCenter(o):(t._node.dir?o.dir=t._node.dir:this.layout===C.LAYOUT.VERTICAL_TIMELINE2?o.dir=C.LAYOUT_GROW_DIR.LEFT:this.layout===C.LAYOUT.VERTICAL_TIMELINE3?o.dir=C.LAYOUT_GROW_DIR.RIGHT:o.dir=s%2===0?C.LAYOUT_GROW_DIR.RIGHT:C.LAYOUT_GROW_DIR.LEFT,t._node.isRoot?o.left=t._node.left+(e._node.width>t._node.width?-(e._node.width-t._node.width)/2:(t._node.width-e._node.width)/2):o.left=o.dir===C.LAYOUT_GROW_DIR.RIGHT?t._node.left+t._node.width+this.getMarginX(n):t._node.left-this.getMarginX(n)-o.width),!e.data.expand)return!0},(e,t,r,n)=>{if(r)return;let s=e.data.expand===!1?0:e._node.children.length;e._node.childrenAreaHeight=s?e._node.children.reduce((a,o)=>a+o.height,0)+(s+1)*this.getMarginY(n+1):0},!0,0)}computedTopValue(){ue(this.root,null,(e,t,r,n,s)=>{if(e.getData("expand")&&e.children&&e.children.length){let a=this.getMarginY(n+1);if(r){let l=e.top+e.height+a;e.children.forEach(h=>{h.top=l,l+=h.height+a})}else{let o=this.getMarginY(n+1),h=e.top+e.height/2+o-e.childrenAreaHeight/2;e.children.forEach(d=>{d.top=h,h+=d.height+o})}}},null,!0)}adjustLeftTopValue(){ue(this.root,null,(e,t,r,n)=>{if(!e.getData("expand")||r)return;let s=this.getMarginY(n+1)*2+e.height,a=e.childrenAreaHeight-s;a>0&&this.updateBrothers(e,a/2)},null,!0)}updateBrothers(e,t){if(e.parent){let r=e.parent.children,n=ze(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition()||!e.parent.isRoot&&s.uid===e.uid)return;let o=0;e.parent.isRoot?an?o=t*2:o=t:an&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothers(e.parent,t)}}updateBrothersTop(e,t){if(e.parent&&!e.parent.isRoot){let r=e.parent.children,n=ze(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;a>n&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.updateBrothersTop(e.parent,t)}}renderLine(e,t,r,n){n==="curve"?this.renderLineCurve(e,t,r):n==="direct"?this.renderLineDirect(e,t,r):this.renderLineStraight(e,t,r)}renderLineStraight(e,t,r){if(e.children.length<=0)return[];let{expandBtnSize:n}=e,{alwaysShowExpandBtn:s,notShowExpandBtn:a}=this.mindMap.opt;if((!s||a)&&(n=0),e.isRoot){let o=e;e.children.forEach((l,h)=>{let d=o.top+o.height,c=l.top,f=e.left+e.width/2,m=`M ${f},${d} L ${f},${c}`;this.setLineStyle(r,t[h],m,l),o=l})}else if(e.dir===C.LAYOUT_GROW_DIR.RIGHT){let o=e.left+e.width,l=e.top+e.height/2,d=(this.getMarginX(e.layerIndex+1)-n)*.6;e.children.forEach((c,f)=>{let m=c.left,g=c.top+c.height/2,x=this.createFoldLine([[o,l],[o+d,l],[o+d,g],[m,g]]);this.setLineStyle(r,t[f],x,c)})}else{let o=e.left,l=e.top+e.height/2,d=(this.getMarginX(e.layerIndex+1)-n)*.6;e.children.forEach((c,f)=>{let m=c.left+c.width,g=c.top+c.height/2,x=this.createFoldLine([[o,l],[o-d,l],[o-d,g],[m,g]]);this.setLineStyle(r,t[f],x,c)})}}renderLineDirect(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0),e.children.forEach((c,f)=>{if(e.isRoot){let m=e;e.children.forEach((g,x)=>{let y=m.top+m.height,b=g.top,E=e.left+e.width/2,k=`M ${E},${y} L ${E},${b}`;this.setLineStyle(r,t[x],k,g),m=g})}else{let m=c.dir===C.LAYOUT_GROW_DIR.LEFT?n-l:n+a+l,g=s+o/2,x=c.dir===C.LAYOUT_GROW_DIR.LEFT?c.left+c.width:c.left,y=c.top+c.height/2,b=`M ${m},${g} L ${x},${y}`;this.setLineStyle(r,t[f],b,c)}})}renderLineCurve(e,t,r){if(e.children.length<=0)return[];let{left:n,top:s,width:a,height:o,expandBtnSize:l}=e,{alwaysShowExpandBtn:h,notShowExpandBtn:d}=this.mindMap.opt;(!h||d)&&(l=0),e.children.forEach((c,f)=>{if(e.isRoot){let m=e;e.children.forEach((g,x)=>{let y=m.top+m.height,b=g.top,E=e.left+e.width/2,k=`M ${E},${y} L ${E},${b}`;this.setLineStyle(r,t[x],k,g),m=g})}else{let m=c.dir===C.LAYOUT_GROW_DIR.LEFT?n-l:n+a+l,g=s+o/2,x=c.dir===C.LAYOUT_GROW_DIR.LEFT?c.left+c.width:c.left,y=c.top+c.height/2,b=this.cubicBezierPath(m,g,x,y);this.setLineStyle(r,t[f],b,c)}})}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s,isRoot:a}=e;if(!a){let{translateX:o,translateY:l}=t.transform();e.dir===C.LAYOUT_GROW_DIR.RIGHT?t.translate(r-o,n/2-l):t.translate(-s-o,n/2-l)}}renderGeneralization(e){e.forEach(t=>{let r=t.node.dir===C.LAYOUT_GROW_DIR.LEFT,{top:n,bottom:s,left:a,right:o,generalizationLineMargin:l,generalizationNodeMargin:h}=this.getNodeGeneralizationRenderBoundaries(t,"h"),d=r?a-l:o+l,c=d,f=n,m=d,g=s,x=c+(r?-20:20),y=f+(g-f)/2,b=`M ${c},${f} Q ${x},${y} ${m},${g}`;t.generalizationLine.plot(this.transformPath(b)),t.generalizationNode.left=d+(r?-h:h)-(r?t.generalizationNode.width:0),t.generalizationNode.top=n+(s-n-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){s.dir===C.LAYOUT_GROW_DIR.LEFT?e.size(t,n).x(-t).y(0):e.size(t,n).x(r).y(0)}},_c=Ip});var Ji,Qv=T(()=>{pe();Ji={top:{renderExpandBtn({node:i,btn:e,expandBtnSize:t,translateX:r,translateY:n,width:s,height:a}){i.parent&&i.parent.isRoot?e.translate(s*.3-t/2-r,-t/2-n):e.translate(s*.3-t/2-r,a+t/2-n)},renderLine({node:i,line:e,top:t,x:r,lineLength:n,height:s,expandBtnSize:a,maxy:o,ctx:l}){i.parent&&i.parent.isRoot?e.plot(l.transformPath(`M ${r},${t} L ${r+n},${t-Math.tan(un(l.mindMap.opt.fishboneDeg))*n}`)):e.plot(l.transformPath(`M ${r},${t+s+a} L ${r},${o}`))},computedLeftTopValue({layerIndex:i,node:e,ctx:t}){if(i>=1&&e.children){let r=t.getMarginY(i+1),n=e.left+e.width*t.childIndent,s=e.top+e.height+(t.getNodeActChildrenLength(e)>0?e.expandBtnSize:0)+r;e.children.forEach(a=>{a.left=n,a.top+=s,s+=a.height+(t.getNodeActChildrenLength(a)>0?a.expandBtnSize:0)+r})}},adjustLeftTopValueBefore({node:i,parent:e,ctx:t,layerIndex:r}){let n=i.children.length,s=t.getMarginY(r+1);if(e&&!e.isRoot&&n>0){let a=i.children.reduce((o,l)=>o+l.height+(t.getNodeActChildrenLength(l)>0?l.expandBtnSize:0)+s,0);t.updateBrothersTop(i,a)}},adjustLeftTopValueAfter({parent:i,node:e,ctx:t}){if(i&&i.isRoot){let r=t.getMarginY(e.layerIndex+1),n=e.expandBtnSize+r;e.children.forEach(s=>{let a=t.getNodeAreaHeight(s),o=s.top,l=s.left;s.top=e.top-(s.top-e.top)-a+e.height,s.left=e.left+e.width*t.indent+(a+n)/Math.tan(un(t.mindMap.opt.fishboneDeg)),n+=a,t.updateChildrenPro(s.children,{top:s.top-o,left:s.left-l})})}}},bottom:{renderExpandBtn({node:i,btn:e,expandBtnSize:t,translateX:r,translateY:n,width:s,height:a}){i.parent&&i.parent.isRoot?e.translate(s*.3-t/2-r,a+t/2-n):e.translate(s*.3-t/2-r,-t/2-n)},renderLine({node:i,line:e,top:t,x:r,lineLength:n,height:s,miny:a,ctx:o}){i.parent&&i.parent.isRoot?e.plot(o.transformPath(`M ${r},${t+s} L ${r+n},${t+s+Math.tan(un(o.mindMap.opt.fishboneDeg))*n}`)):e.plot(o.transformPath(`M ${r},${t} L ${r},${a}`))},computedLeftTopValue({layerIndex:i,node:e,ctx:t}){let r=t.getMarginY(i+1);if(i===1&&e.children){let n=e.left+e.width*t.childIndent,s=e.top+e.height+(t.getNodeActChildrenLength(e)>0?e.expandBtnSize:0)+r;e.children.forEach(a=>{a.left=n,a.top=s+(t.getNodeActChildrenLength(a)>0?a.expandBtnSize:0),s+=a.height+(t.getNodeActChildrenLength(a)>0?a.expandBtnSize:0)+r})}if(i>1&&e.children){let n=e.left+e.width*t.childIndent,s=e.top-(t.getNodeActChildrenLength(e)>0?e.expandBtnSize:0)-r;e.children.forEach(a=>{a.left=n,a.top=s-a.height,s-=a.height+(t.getNodeActChildrenLength(a)>0?a.expandBtnSize:0)+r})}},adjustLeftTopValueBefore({node:i,ctx:e,layerIndex:t}){let r=e.getMarginY(t+1),n=i.children.length;if(t>2&&n>0){let s=i.children.reduce((a,o)=>a+o.height+(e.getNodeActChildrenLength(o)>0?o.expandBtnSize:0)+r,0);e.updateBrothersTop(i,-s)}},adjustLeftTopValueAfter({parent:i,node:e,ctx:t}){if(i&&i.isRoot){let r=t.getMarginY(e.layerIndex+1),n=0,s=e.expandBtnSize;e.children.forEach(a=>{let o=t.getNodeActChildrenLength(a)>0,l=t.getNodeAreaHeight(a),h=o?l-a.height-(o?a.expandBtnSize:0):0;h-=o?r:0;let d=n+h,c=a.left;a.top+=d,a.left=e.left+e.width*t.indent+(l+s)/Math.tan(un(t.mindMap.opt.fishboneDeg)),n+=h,s+=l,t.updateChildrenPro(a.children,{top:d,left:a.left-c})})}}}}});var zp,Rp,Jv=T(()=>{Ar();pe();Ge();Qv();wt();ih();zp=class extends Nt{constructor(e={},t){super(e),this.layout=t,this.indent=.3,this.childIndent=.5,this.fishTail=null,this.maxx=0,this.headRatio=1,this.tailRatio=.6,this.paddingXRatio=.3,this.fishHeadPathStr="M4,181 C4,181, 0,177, 4,173 Q 96.09523809523809,0, 288.2857142857143,0 L 288.2857142857143,354 Q 48.047619047619044,354, 8,218.18367346938777 C8,218.18367346938777, 6,214.18367346938777, 8,214.18367346938777 L 41.183673469387756,214.18367346938777 Z",this.fishTailPathStr="M 606.9342905223708 0 Q 713.1342905223709 -177 819.3342905223708 -177 L 766.2342905223709 0 L 819.3342905223708 177 Q 713.1342905223709 177 606.9342905223708 0 z",this.bindEvent(),this.extendShape(),this.beforeChange=this.beforeChange.bind(this)}nodeIsRemoveAllLines(e){return e.isRoot||e.layerIndex===1}isFishbone2(){return this.layout===C.LAYOUT.FISHBONE2}bindEvent(){this.isFishbone2()&&(this.onCheckUpdateFishTail=this.onCheckUpdateFishTail.bind(this),this.mindMap.on("afterExecCommand",this.onCheckUpdateFishTail))}unBindEvent(){this.mindMap.off("afterExecCommand",this.onCheckUpdateFishTail)}extendShape(){this.isFishbone2()&&this.mindMap.addShape({name:"fishHead",createShape:e=>{let t=ke(``),{width:r,height:n}=e.shapeInstance.getNodeSize();return t.size(r,n),t},getPadding:({width:e,height:t,paddingX:r,paddingY:n})=>{e+=r*2,t+=n*2;let s=this.paddingXRatio*e,a=0;return e+=s*2,a=(e/this.headRatio-t)/2,{paddingX:s,paddingY:a}}})}doLayout(e){Jt([()=>{this.computedBaseValue(),this.addFishTail()},()=>{this.computedLeftTopValue()},()=>{this.adjustLeftTopValue(),this.updateFishTailPosition()},()=>{e(this.root)}])}addFishTail(){if(!this.isFishbone2())return;let e=this.mindMap.lineDraw.findOne(".smm-layout-fishbone-tail");e?this.fishTail=e:(this.fishTail=ke(``),this.fishTail.addClass("smm-layout-fishbone-tail"));let t=this.root.height,r=t*this.tailRatio;this.fishTail.size(r,t),this.styleFishTail(),this.mindMap.lineDraw.add(this.fishTail)}onCheckUpdateFishTail(e,t,r){if(e==="SET_NODE_DATA"){let n=!1;Object.keys(r).forEach(s=>{zu.includes(s)&&(n=!0)}),n&&this.styleFishTail()}}styleFishTail(){this.root.style.shape(this.fishTail)}removeFishTail(){let e=this.mindMap.lineDraw.findOne(".smm-layout-fishbone-tail");e&&e.remove()}updateFishTailPosition(){this.isFishbone2()&&this.fishTail.x(this.maxx).cy(this.root.top+this.root.height/2)}computedBaseValue(){ue(this.renderer.renderTree,null,(e,t,r,n,s,a)=>{r&&this.isFishbone2()&&(e.data.shape="fishHead");let o=this.createNode(e,t,r,n,s,a);if(r)this.setNodeCenter(o);else if(t._node.dir?o.dir=t._node.dir:o.dir=s%2===0?C.LAYOUT_GROW_DIR.TOP:C.LAYOUT_GROW_DIR.BOTTOM,t._node.isRoot){let l=this.getMarginY(n),h=this.isFishbone2()?t._node.height/4:0;this.checkIsTop(o)?o.top=t._node.top-o.height-l+h:o.top=t._node.top+t._node.height+l-h}if(!e.data.expand)return!0},null,!0,0)}computedLeftTopValue(){ue(this.root,null,(e,t,r,n)=>{if(e.isRoot){let a=this.getMarginX(n+1),o=this.isFishbone2()?2:1,l=e.left+e.width+e.height/o+a,h=e.left+e.width+e.height/o+a;e.children.forEach(d=>{this.checkIsTop(d)?(d.left=l,l+=d.width+a):(d.left=h+20,h+=d.width+a)})}let s={layerIndex:n,node:e,ctx:this};this.checkIsTop(e)?Ji.top.computedLeftTopValue(s):Ji.bottom.computedLeftTopValue(s)},null,!0)}adjustLeftTopValue(){ue(this.root,null,(e,t,r,n)=>{if(!e.getData("expand"))return;let s={node:e,parent:t,layerIndex:n,ctx:this};this.checkIsTop(e)?Ji.top.adjustLeftTopValueBefore(s):Ji.bottom.adjustLeftTopValueBefore(s)},(e,t)=>{let r={parent:t,node:e,ctx:this};if(this.checkIsTop(e)?Ji.top.adjustLeftTopValueAfter(r):Ji.bottom.adjustLeftTopValueAfter(r),e.isRoot){let n=0,s=0,a=-1/0;e.children.forEach(o=>{if(this.checkIsTop(o)){o.left+=n,this.updateChildren(o.children,"left",n);let{left:l,right:h}=this.getNodeBoundaries(o,"h");h>a&&(a=h),n+=h-l}else{o.left+=s,this.updateChildren(o.children,"left",s);let{left:l,right:h}=this.getNodeBoundaries(o,"h");h>a&&(a=h),s+=h-l}}),this.maxx=a}},!0)}getNodeAreaHeight(e){let t=0,r=n=>{let s=this.getMarginY(n.layerIndex);t+=n.height+(this.getNodeActChildrenLength(n)>0?n.expandBtnSize:0)+s,n.children.length&&n.children.forEach(a=>{r(a)})};return r(e),t}updateBrothersLeft(e){let t=e.children,r=0;t.forEach(n=>{n.left+=r,n.children&&n.children.length&&this.updateChildren(n.children,"left",r);let{left:s,right:a}=this.getNodeBoundaries(n,"h"),l=a-s-n.width;l>0&&(r+=l)})}updateBrothersTop(e,t){if(e.parent&&!e.parent.isRoot){let r=e.parent.children,n=ze(e,r);r.forEach((s,a)=>{if(s.hasCustomPosition())return;let o=0;a>n&&(o=t),s.top+=o,s.children&&s.children.length&&this.updateChildren(s.children,"top",o)}),this.checkIsTop(e)?this.updateBrothersTop(e.parent,t):this.updateBrothersTop(e.parent,e.layerIndex===3?0:t)}}checkIsTop(e){return e.dir===C.LAYOUT_GROW_DIR.TOP}renderLine(e,t,r){if(e.layerIndex!==1&&e.children.length<=0)return[];let{top:n,height:s,expandBtnSize:a}=e,{alwaysShowExpandBtn:o,notShowExpandBtn:l}=this.mindMap.opt;(!o||l)&&(a=0);let h=e.children.length;if(e.isRoot){let d=-1/0;e.children.forEach(x=>{x.left>d&&(d=x.left);let y=this.getMarginY(x.layerIndex),b=x.left,E=e.height/2+y-(this.isFishbone2()?e.height/4:0),k=E/Math.tan(un(this.mindMap.opt.fishboneDeg)),L=this.lineDraw.path();this.checkIsTop(x)?L.plot(this.transformPath(`M ${b-k},${x.top+x.height+E} L ${x.left},${x.top+x.height}`)):L.plot(this.transformPath(`M ${b-k},${x.top-E} L ${b},${x.top}`)),e.style.line(L),e._lines.push(L),r&&r(L,e)});let c=e.top+e.height/2,f=e.height/2+this.getMarginY(e.layerIndex+1),m=this.lineDraw.path(),g=this.isFishbone2()?this.maxx:d-f/Math.tan(un(this.mindMap.opt.fishboneDeg));m.plot(this.transformPath(`M ${e.left+e.width},${c} L ${g},${c}`)),e.style.line(m),e._lines.push(m),r&&r(m,e)}else{let d=-1/0,c=1/0,f=-1/0,m=e.left+e.width*this.indent;if(e.children.forEach((g,x)=>{g.left>f&&(f=g.left);let y=g.top+g.height/2;if(y>d&&(d=y),y1){let b=`M ${m},${y} L ${g.left},${y}`;this.setLineStyle(r,t[x],b,g)}}),h>=0){let g=this.lineDraw.path();a=h>0?a:0;let x=f-e.left-e.width*this.indent;x=Math.max(x,0);let y={node:e,line:g,top:n,x:m,lineLength:x,height:s,expandBtnSize:a,maxy:d,miny:c,ctx:this};this.checkIsTop(e)?Ji.top.renderLine(y):Ji.bottom.renderLine(y),e.style.line(g),e._lines.push(g),r&&r(g,e)}}}renderExpandBtn(e,t){let{width:r,height:n,expandBtnSize:s,isRoot:a}=e;if(!a){let{translateX:o,translateY:l}=t.transform(),h={node:e,btn:t,expandBtnSize:s,translateX:o,translateY:l,width:r,height:n};this.checkIsTop(e)?Ji.top.renderExpandBtn(h):Ji.bottom.renderExpandBtn(h)}}renderGeneralization(e){e.forEach(t=>{let{top:r,bottom:n,right:s,generalizationLineMargin:a,generalizationNodeMargin:o}=this.getNodeGeneralizationRenderBoundaries(t,"h"),l=s+a,h=r,d=s+a,c=n,f=l+20,m=h+(c-h)/2,g=`M ${l},${h} Q ${f},${m} ${d},${c}`;t.generalizationLine.plot(this.transformPath(g)),t.generalizationNode.left=s+o,t.generalizationNode.top=r+(n-r-t.generalizationNode.height)/2})}renderExpandBtnRect(e,t,r,n,s){let a="";s.dir===C.LAYOUT_GROW_DIR.TOP?a=s.layerIndex===1?C.LAYOUT_GROW_DIR.TOP:C.LAYOUT_GROW_DIR.BOTTOM:a=s.layerIndex===1?C.LAYOUT_GROW_DIR.BOTTOM:C.LAYOUT_GROW_DIR.TOP,a===C.LAYOUT_GROW_DIR.TOP?e.size(r,t).x(0).y(-t):e.size(r,t).x(0).y(n)}beforeChange(){this.isFishbone2()&&(this.root.nodeData.data.shape=C.SHAPE.RECTANGLE,this.removeFishTail(),this.unBindEvent(),this.mindMap.removeShape("fishHead"))}},Rp=zp});var eb,m0,tb=T(()=>{pe();Ge();eb="smm-node-edit-wrap",m0=class{constructor(e){this.renderer=e,this.mindMap=e.mindMap,this.currentNode=null,this.textEditNode=null,this.showTextEdit=!1,this.cacheEditingText="",this.hasBodyMousedown=!1,this.textNodePaddingX=5,this.textNodePaddingY=3,this.isNeedUpdateTextEditNode=!1,this.mindMap.addEditNodeClass(eb),this.bindEvent()}bindEvent(){this.show=this.show.bind(this),this.onScale=this.onScale.bind(this),this.onKeydown=this.onKeydown.bind(this),this.mindMap.on("node_dblclick",(e,t,r)=>{this.show({node:e,e:t,isInserting:r})}),this.mindMap.on("draw_click",()=>{this.hideEditTextBox()}),this.mindMap.on("body_mousedown",()=>{this.hasBodyMousedown=!0}),this.mindMap.on("body_click",()=>{this.hasBodyMousedown&&(this.hasBodyMousedown=!1,this.mindMap.opt.isEndNodeTextEditOnClickOuter&&this.hideEditTextBox())}),this.mindMap.on("svg_mousedown",()=>{this.hideEditTextBox()}),this.mindMap.on("expand_btn_click",()=>{this.hideEditTextBox()}),this.mindMap.on("before_node_active",()=>{this.hideEditTextBox()}),this.mindMap.on("mousewheel",()=>{this.mindMap.opt.mousewheelAction===C.MOUSE_WHEEL_ACTION.MOVE&&this.hideEditTextBox()}),this.mindMap.keyCommand.addShortcut("F2",()=>{this.renderer.activeNodeList.length<=0||this.show({node:this.renderer.activeNodeList[0]})}),this.mindMap.on("scale",this.onScale),this.mindMap.opt.enableAutoEnterTextEditWhenKeydown&&window.addEventListener("keydown",this.onKeydown),this.mindMap.on("beforeDestroy",()=>{this.unBindEvent()}),this.mindMap.on("after_update_config",(e,t)=>{e.openRealtimeRenderOnNodeTextEdit!==t.openRealtimeRenderOnNodeTextEdit&&(this.mindMap.richText?this.mindMap.richText.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(e.openRealtimeRenderOnNodeTextEdit):this.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(e.openRealtimeRenderOnNodeTextEdit)),e.enableAutoEnterTextEditWhenKeydown!==t.enableAutoEnterTextEditWhenKeydown&&window[e.enableAutoEnterTextEditWhenKeydown?"addEventListener":"removeEventListener"]("keydown",this.onKeydown)}),this.mindMap.on("afterExecCommand",()=>{this.isShowTextEdit()&&(this.isNeedUpdateTextEditNode=!0)}),this.mindMap.on("node_tree_render_end",()=>{this.isShowTextEdit()&&this.isNeedUpdateTextEditNode&&(this.isNeedUpdateTextEditNode=!1,this.updateTextEditNode())})}unBindEvent(){window.removeEventListener("keydown",this.onKeydown)}onKeydown(e){if(e.target!==document.body)return;let t=this.mindMap.renderer.activeNodeList;if(t.length<=0||t.length>1)return;let r=t[0];r&&this.checkIsAutoEnterTextEditKey(e)&&(e.preventDefault(),this.show({node:r,e,isInserting:!1,isFromKeyDown:!0}))}checkIsAutoEnterTextEditKey(e){let t=e.keyCode;return(t===229||t>=65&&t<=90||t>=48&&t<=57)&&!this.mindMap.keyCommand.hasCombinationKey(e)}registerTmpShortcut(){this.mindMap.keyCommand.addShortcut("Enter",()=>{this.hideEditTextBox()}),this.mindMap.keyCommand.addShortcut("Tab",()=>{this.hideEditTextBox()})}isShowTextEdit(){return this.mindMap.richText?this.mindMap.richText.showTextEdit:this.showTextEdit}setIsShowTextEdit(e){this.showTextEdit=e,e?this.mindMap.keyCommand.stopCheckInSvg():this.mindMap.keyCommand.recoveryCheckInSvg()}async show({node:e,isInserting:t=!1,isFromKeyDown:r=!1,isFromScale:n=!1}){if(e.isUseCustomNodeContent())return;this.getCurrentEditNode()&&this.hideEditTextBox();let{beforeTextEdit:a,openRealtimeRenderOnNodeTextEdit:o}=this.mindMap.opt;if(typeof a=="function"){let m=!1;try{m=await a(e,t)}catch(g){m=!1,this.mindMap.opt.errorHandler(Ti.BEFORE_TEXT_EDIT_ERROR,g)}if(!m)return}let{offsetLeft:l,offsetTop:h}=I2(this.mindMap,e);this.mindMap.view.translateXY(l,h);let d=e._textData.node;o&&d.show();let c=d.node.getBoundingClientRect();o&&d.hide();let f={node:e,rect:c,isInserting:t,isFromKeyDown:r,isFromScale:n};if(this.mindMap.richText){this.mindMap.richText.showEditText(f);return}this.currentNode=e,this.showEditTextBox(f)}onOpenRealtimeRenderOnNodeTextEditConfigUpdate(e){this.textEditNode&&(this.textEditNode.style.background=e?"transparent":this.currentNode?this.getBackground(this.currentNode):"",this.textEditNode.style.boxShadow=e?"none":"0 0 20px rgba(0,0,0,.5)")}onScale(){let e=this.getCurrentEditNode();e&&(this.mindMap.richText?(this.mindMap.richText.cacheEditingText=this.mindMap.richText.getEditText(),this.mindMap.richText.showTextEdit=!1):(this.cacheEditingText=this.getEditText(),this.setIsShowTextEdit(!1)),this.show({node:e,isFromScale:!0}))}showEditTextBox({node:e,rect:t,isInserting:r,isFromKeyDown:n,isFromScale:s}){if(this.showTextEdit)return;let{nodeTextEditZIndex:a,textAutoWrapWidth:o,selectTextOnEnterEditText:l,openRealtimeRenderOnNodeTextEdit:h,autoEmptyTextWhenKeydownEnterEdit:d}=this.mindMap.opt;s||this.mindMap.emit("before_show_text_edit"),this.registerTmpShortcut(),this.textEditNode||(this.textEditNode=document.createElement("div"),this.textEditNode.classList.add(eb),this.textEditNode.style.cssText=` position: fixed; box-sizing: border-box; ${h?"":"box-shadow: 0 0 20px rgba(0,0,0,.5);"} @@ -1483,16 +1483,16 @@ body { outline: none; word-break: break-all; line-break: anywhere; - `,this.textEditNode.setAttribute("contenteditable",!0),this.textEditNode.addEventListener("keyup",y=>{y.stopPropagation()}),this.textEditNode.addEventListener("click",y=>{y.stopPropagation()}),this.textEditNode.addEventListener("mousedown",y=>{y.stopPropagation()}),this.textEditNode.addEventListener("keydown",y=>{this.checkIsAutoEnterTextEditKey(y)&&y.stopPropagation()}),this.textEditNode.addEventListener("paste",y=>{let b=y.clipboardData.getData("text"),{isSmm:S,data:A}=Po(b);S&&A[0]&&A[0].data?yu(y,sa(A[0].data.text)):yu(y),this.emitTextChangeEvent()}),this.textEditNode.addEventListener("input",()=>{this.emitTextChangeEvent()}),(this.mindMap.opt.customInnerElsAppendTo||document.body).appendChild(this.textEditNode));let c=this.mindMap.view.scale,f=e.style.merge("fontSize"),m=(this.cacheEditingText||e.getData("text")).split(/\n/gim).map(x=>mn(x)),g=e._textData.node.attr("data-ismultiLine")==="true";e.style.domText(this.textEditNode,c),h||(this.textEditNode.style.background=this.getBackground(e)),this.textEditNode.style.zIndex=a,n&&d?this.textEditNode.innerHTML="":this.textEditNode.innerHTML=m.join("
    "),this.textEditNode.style.minWidth=t.width+this.textNodePaddingX*2+"px",this.textEditNode.style.minHeight=t.height+"px",this.textEditNode.style.left=Math.floor(t.left)+"px",this.textEditNode.style.top=Math.floor(t.top)+"px",this.textEditNode.style.display="block",this.textEditNode.style.maxWidth=o*c+"px",g?(this.textEditNode.style.lineHeight=js,this.textEditNode.style.transform=`translateY(${(js-1)*f/2*c}px)`):this.textEditNode.style.lineHeight="normal",this.setIsShowTextEdit(!0),r||l&&!n?la(this.textEditNode):oa(this.textEditNode),this.cacheEditingText=""}emitTextChangeEvent(){this.mindMap.emit("node_text_edit_change",{node:this.currentNode,text:this.getEditText(),richText:!1})}updateTextEditNode(){if(this.mindMap.richText){this.mindMap.richText.updateTextEditNode();return}if(!this.showTextEdit||!this.currentNode)return;let e=this.currentNode._textData.node.node.getBoundingClientRect();this.textEditNode.style.minWidth=e.width+this.textNodePaddingX*2+"px",this.textEditNode.style.minHeight=e.height+this.textNodePaddingY*2+"px",this.textEditNode.style.left=Math.floor(e.left)+"px",this.textEditNode.style.top=Math.floor(e.top)+"px"}getBackground(e){if(e.style.merge("gradientStyle")){let r=e.style.merge("startColor"),n=e.style.merge("endColor");return`linear-gradient(to right, ${r}, ${n})`}else{let r=e.style.merge("fillColor"),n=e.style.merge("color");return r==="transparent"?Oo(n)?$0(this.mindMap.themeConfig):"#fff":r}}removeTextEditEl(){if(this.mindMap.richText){this.mindMap.richText.removeTextEditEl();return}if(!this.textEditNode)return;(this.mindMap.opt.customInnerElsAppendTo||document.body).removeChild(this.textEditNode)}getEditText(){return na(this.textEditNode.innerHTML)}hideEditTextBox(){if(this.mindMap.richText)return this.mindMap.richText.hideEditText();if(!this.showTextEdit)return;let e=this.currentNode,t=this.getEditText();this.currentNode=null,this.textEditNode.style.display="none",this.textEditNode.innerHTML="",this.textEditNode.style.fontFamily="inherit",this.textEditNode.style.fontSize="inherit",this.textEditNode.style.fontWeight="normal",this.textEditNode.style.transform="translateY(0)",this.setIsShowTextEdit(!1),this.mindMap.execCommand("SET_NODE_TEXT",e,t),this.mindMap.render(),this.mindMap.emit("hide_text_edit",this.textEditNode,this.renderer.activeNodeList,e)}getCurrentEditNode(){return this.mindMap.richText?this.mindMap.richText.node:this.currentNode}}});var $v,Uv,Np,jv,Gv=T(()=>{$v=pt(O0());Cv();Lv();zv();Dv();Ov();Bv();Fv();Hv();pe();Tu();Ro();$e();yt();Uv={[k.LAYOUT.LOGICAL_STRUCTURE]:pp,[k.LAYOUT.LOGICAL_STRUCTURE_LEFT]:pp,[k.LAYOUT.MIND_MAP]:_v,[k.LAYOUT.CATALOG_ORGANIZATION]:Iv,[k.LAYOUT.ORGANIZATION_STRUCTURE]:Rv,[k.LAYOUT.TIMELINE]:bp,[k.LAYOUT.TIMELINE2]:bp,[k.LAYOUT.VERTICAL_TIMELINE]:Mc,[k.LAYOUT.VERTICAL_TIMELINE2]:Mc,[k.LAYOUT.VERTICAL_TIMELINE3]:Mc,[k.LAYOUT.FISHBONE]:Tp,[k.LAYOUT.FISHBONE2]:Tp},Np=class{constructor(e={}){this.opt=e,this.mindMap=e.mindMap,this.themeConfig=this.mindMap.themeConfig,this.renderTree=this.mindMap.opt.data?(0,$v.default)({},this.mindMap.opt.data):null,this.reRender=!1,this.isRendering=!1,this.hasWaitRendering=!1,this.nodeCache={},this.lastNodeCache={},this.renderSourceList=[],this.renderCallbackList=[],this.activeNodeList=[],this.emitNodeActiveEventTimer=null,this.renderTimer=null,this.root=null,this.textEdit=new l0(this),this.beingCopyData=null,this.highlightBoxNode=null,this.highlightBoxNodeStyle=null,this.lastActiveNodeList=[],this.setLayout(),this.bindEvent(),this.registerCommands(),this.registerShortcutKeys()}setLayout(){this.layout&&this.layout.beforeChange&&this.layout.beforeChange();let{layout:e}=this.mindMap.opt,t=Uv[e]||this.mindMap[e];t||(t=Uv[k.LAYOUT.LOGICAL_STRUCTURE],this.mindMap.opt.layout=k.LAYOUT.LOGICAL_STRUCTURE),this.layout=new t(this,e)}setData(e){this.renderTree=e||null}bindEvent(){let{openPerformance:e,performanceConfig:t,openRealtimeRenderOnNodeTextEdit:r}=this.mindMap.opt;this.mindMap.on("draw_click",s=>{this.clearActiveNodeListOnDrawClick(s,"click")}),this.mindMap.on("contextmenu",s=>{this.clearActiveNodeListOnDrawClick(s,"contextmenu")}),this.mindMap.svg.on("dblclick",()=>{this.mindMap.opt.enableDblclickBackToRootNode&&this.setRootNodeCenter()});let n=Si(()=>{this.renderTree&&this.root&&(this.mindMap.emit("node_tree_render_start"),this.root.render(()=>{this.mindMap.emit("node_tree_render_end")},!1,!0))},t.time);e&&this.mindMap.on("view_data_change",n),this.onNodeTextEditChange=p2(this.onNodeTextEditChange,100,this),r&&this.mindMap.on("node_text_edit_change",this.onNodeTextEditChange),this.mindMap.on("after_update_config",(s,a)=>{s.openPerformance!==a.openPerformance&&(this.mindMap[s.openPerformance?"on":"off"]("view_data_change",n),this.forceLoadNode()),s.openRealtimeRenderOnNodeTextEdit!==a.openRealtimeRenderOnNodeTextEdit&&this.mindMap[s.openRealtimeRenderOnNodeTextEdit?"on":"off"]("node_text_edit_change",this.onNodeTextEditChange)})}onNodeTextEditChange({node:e,text:t}){e._textData=e.createTextNode(t);let{width:r,height:n}=e.getNodeRect();e.width=r,e.height=n,e.layout(),this.mindMap.render(()=>{this.textEdit.updateTextEditNode()})}forceLoadNode(e){e=e||this.root,e&&(this.mindMap.emit("node_tree_render_start"),e.render(()=>{this.mindMap.emit("node_tree_render_end")},!0))}registerCommands(){this.selectAll=this.selectAll.bind(this),this.mindMap.command.add("SELECT_ALL",this.selectAll),this.back=this.back.bind(this),this.mindMap.command.add("BACK",this.back),this.forward=this.forward.bind(this),this.mindMap.command.add("FORWARD",this.forward),this.insertNode=this.insertNode.bind(this),this.mindMap.command.add("INSERT_NODE",this.insertNode),this.insertMultiNode=this.insertMultiNode.bind(this),this.mindMap.command.add("INSERT_MULTI_NODE",this.insertMultiNode),this.insertChildNode=this.insertChildNode.bind(this),this.mindMap.command.add("INSERT_CHILD_NODE",this.insertChildNode),this.insertMultiChildNode=this.insertMultiChildNode.bind(this),this.mindMap.command.add("INSERT_MULTI_CHILD_NODE",this.insertMultiChildNode),this.insertParentNode=this.insertParentNode.bind(this),this.mindMap.command.add("INSERT_PARENT_NODE",this.insertParentNode),this.upNode=this.upNode.bind(this),this.mindMap.command.add("UP_NODE",this.upNode),this.downNode=this.downNode.bind(this),this.mindMap.command.add("DOWN_NODE",this.downNode),this.moveUpOneLevel=this.moveUpOneLevel.bind(this),this.mindMap.command.add("MOVE_UP_ONE_LEVEL",this.moveUpOneLevel),this.insertAfter=this.insertAfter.bind(this),this.mindMap.command.add("INSERT_AFTER",this.insertAfter),this.insertBefore=this.insertBefore.bind(this),this.mindMap.command.add("INSERT_BEFORE",this.insertBefore),this.moveNodeTo=this.moveNodeTo.bind(this),this.mindMap.command.add("MOVE_NODE_TO",this.moveNodeTo),this.removeNode=this.removeNode.bind(this),this.mindMap.command.add("REMOVE_NODE",this.removeNode),this.removeCurrentNode=this.removeCurrentNode.bind(this),this.mindMap.command.add("REMOVE_CURRENT_NODE",this.removeCurrentNode),this.pasteNode=this.pasteNode.bind(this),this.mindMap.command.add("PASTE_NODE",this.pasteNode),this.cutNode=this.cutNode.bind(this),this.mindMap.command.add("CUT_NODE",this.cutNode),this.setNodeStyle=this.setNodeStyle.bind(this),this.mindMap.command.add("SET_NODE_STYLE",this.setNodeStyle),this.setNodeStyles=this.setNodeStyles.bind(this),this.mindMap.command.add("SET_NODE_STYLES",this.setNodeStyles),this.setNodeActive=this.setNodeActive.bind(this),this.mindMap.command.add("SET_NODE_ACTIVE",this.setNodeActive),this.clearActiveNode=this.clearActiveNode.bind(this),this.mindMap.command.add("CLEAR_ACTIVE_NODE",this.clearActiveNode),this.setNodeExpand=this.setNodeExpand.bind(this),this.mindMap.command.add("SET_NODE_EXPAND",this.setNodeExpand),this.expandAllNode=this.expandAllNode.bind(this),this.mindMap.command.add("EXPAND_ALL",this.expandAllNode),this.unexpandAllNode=this.unexpandAllNode.bind(this),this.mindMap.command.add("UNEXPAND_ALL",this.unexpandAllNode),this.expandToLevel=this.expandToLevel.bind(this),this.mindMap.command.add("UNEXPAND_TO_LEVEL",this.expandToLevel),this.setNodeData=this.setNodeData.bind(this),this.mindMap.command.add("SET_NODE_DATA",this.setNodeData),this.setNodeText=this.setNodeText.bind(this),this.mindMap.command.add("SET_NODE_TEXT",this.setNodeText),this.setNodeImage=this.setNodeImage.bind(this),this.mindMap.command.add("SET_NODE_IMAGE",this.setNodeImage),this.setNodeIcon=this.setNodeIcon.bind(this),this.mindMap.command.add("SET_NODE_ICON",this.setNodeIcon),this.setNodeHyperlink=this.setNodeHyperlink.bind(this),this.mindMap.command.add("SET_NODE_HYPERLINK",this.setNodeHyperlink),this.setNodeNote=this.setNodeNote.bind(this),this.mindMap.command.add("SET_NODE_NOTE",this.setNodeNote),this.setNodeAttachment=this.setNodeAttachment.bind(this),this.mindMap.command.add("SET_NODE_ATTACHMENT",this.setNodeAttachment),this.setNodeTag=this.setNodeTag.bind(this),this.mindMap.command.add("SET_NODE_TAG",this.setNodeTag),this.insertFormula=this.insertFormula.bind(this),this.mindMap.command.add("INSERT_FORMULA",this.insertFormula),this.addGeneralization=this.addGeneralization.bind(this),this.mindMap.command.add("ADD_GENERALIZATION",this.addGeneralization),this.removeGeneralization=this.removeGeneralization.bind(this),this.mindMap.command.add("REMOVE_GENERALIZATION",this.removeGeneralization),this.setNodeCustomPosition=this.setNodeCustomPosition.bind(this),this.mindMap.command.add("SET_NODE_CUSTOM_POSITION",this.setNodeCustomPosition),this.resetLayout=this.resetLayout.bind(this),this.mindMap.command.add("RESET_LAYOUT",this.resetLayout),this.setNodeShape=this.setNodeShape.bind(this),this.mindMap.command.add("SET_NODE_SHAPE",this.setNodeShape),this.goTargetNode=this.goTargetNode.bind(this),this.mindMap.command.add("GO_TARGET_NODE",this.goTargetNode),this.removeCustomStyles=this.removeCustomStyles.bind(this),this.mindMap.command.add("REMOVE_CUSTOM_STYLES",this.removeCustomStyles),this.removeAllNodeCustomStyles=this.removeAllNodeCustomStyles.bind(this),this.mindMap.command.add("REMOVE_ALL_NODE_CUSTOM_STYLES",this.removeAllNodeCustomStyles)}registerShortcutKeys(){this.mindMap.keyCommand.addShortcut("Tab",()=>{this.mindMap.execCommand("INSERT_CHILD_NODE")}),this.mindMap.keyCommand.addShortcut("Insert",()=>{this.mindMap.execCommand("INSERT_CHILD_NODE")}),this.mindMap.keyCommand.addShortcut("Enter",()=>{this.mindMap.execCommand("INSERT_NODE")}),this.mindMap.keyCommand.addShortcut("Shift+Tab",()=>{this.mindMap.execCommand("INSERT_PARENT_NODE")}),this.mindMap.keyCommand.addShortcut("Control+g",()=>{this.mindMap.execCommand("ADD_GENERALIZATION")}),this.toggleActiveExpand=this.toggleActiveExpand.bind(this),this.mindMap.keyCommand.addShortcut("/",this.toggleActiveExpand),this.mindMap.keyCommand.addShortcut("Del|Backspace",()=>{this.mindMap.execCommand("REMOVE_NODE")}),this.mindMap.keyCommand.addShortcut("Shift+Backspace",()=>{this.mindMap.execCommand("REMOVE_CURRENT_NODE")}),this.mindMap.on("before_show_text_edit",()=>{this.startTextEdit()}),this.mindMap.on("hide_text_edit",()=>{this.endTextEdit()}),this.mindMap.keyCommand.addShortcut("Control+a",()=>{this.mindMap.execCommand("SELECT_ALL")}),this.mindMap.keyCommand.addShortcut("Control+l",()=>{this.mindMap.execCommand("RESET_LAYOUT")}),this.mindMap.keyCommand.addShortcut("Control+Up",()=>{this.mindMap.execCommand("UP_NODE")}),this.mindMap.keyCommand.addShortcut("Control+Down",()=>{this.mindMap.execCommand("DOWN_NODE")}),this.mindMap.keyCommand.addShortcut("Control+c",()=>{this.copy()}),this.mindMap.keyCommand.addShortcut("Control+x",()=>{this.cut()}),this.mindMap.keyCommand.addShortcut("Control+v",()=>{this.paste()}),this.mindMap.keyCommand.addShortcut("Control+Enter",()=>{this.setRootNodeCenter()})}emitNodeActiveEvent(e=null,t=[...this.activeNodeList]){L2(this.lastActiveNodeList,t)||(this.lastActiveNodeList=[...t],clearTimeout(this.emitNodeActiveEventTimer),this.emitNodeActiveEventTimer=setTimeout(()=>{this.mindMap.emit("node_active",e,t)},0))}clearActiveNodeListOnDrawClick(e,t){if(this.activeNodeList.length<=0)return;let r=!0,{useLeftKeySelectionRightKeyDrag:n}=this.mindMap.opt;if(t==="contextmenu"?!n:n){let s=this.mindMap.event.mousedownPos;r=Math.abs(e.clientX-s.x)<=5&&Math.abs(e.clientY-s.y)<=5}r&&this.mindMap.execCommand("CLEAR_ACTIVE_NODE")}startTextEdit(){this.mindMap.keyCommand.save()}endTextEdit(){this.mindMap.keyCommand.restore()}clearCache(){this.layout.lru.clear(),this.nodeCache={},this.lastNodeCache={}}addRenderParams(e,t){e&&this.renderCallbackList.findIndex(n=>n===e)===-1&&this.renderCallbackList.push(e),t&&this.renderSourceList.findIndex(n=>n===t)===-1&&this.renderSourceList.push(t)}checkHasRenderSource(e){e=Array.isArray(e)?e:[e];for(let t=0;t{e()}),this.isRendering=!1,this.reRender=!1,this.renderCallbackList=[],this.renderSourceList=[],this.mindMap.emit("node_tree_render_end")}render(e,t){this.addRenderParams(e,t),clearTimeout(this.renderTimer),this.renderTimer=setTimeout(()=>{this._render()},0)}_render(){if(this.checkHasRenderSource(k.CHANGE_THEME)&&this.resetUnExpandNodeStyle(),this.isRendering){this.hasWaitRendering=!0;return}if(this.isRendering=!0,this.lastNodeCache=this.nodeCache,this.nodeCache={},this.reRender&&this.clearActiveNodeList(),!this.renderTree){this.onRenderEnd();return}this.mindMap.emit("node_tree_render_start"),this.root=null,this.layout.doLayout(e=>{Object.keys(this.lastNodeCache).forEach(t=>{this.nodeCache[t]||(this.removeNodeFromActiveList(this.lastNodeCache[t]),this.emitNodeActiveEvent(),this.lastNodeCache[t].destroy())}),this.root=e,this.root.render(()=>{if(this.isRendering=!1,this.hasWaitRendering){this.hasWaitRendering=!1,this.render();return}this.onRenderEnd()})}),this.emitNodeActiveEvent()}resetUnExpandNodeStyle(){this.renderTree&&de(this.renderTree,null,e=>{if(!e.data.expand)return de(e,null,t=>{t.data.needUpdate=!0}),!0})}clearActiveNode(){this.activeNodeList.length<=0||(this.clearActiveNodeList(),this.emitNodeActiveEvent(null,[]))}clearActiveNodeList(){this.activeNodeList.forEach(e=>{this.mindMap.execCommand("SET_NODE_ACTIVE",e,!1)}),this.activeNodeList=[]}addNodeToActiveList(e,t=!1){if(this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate&&e.userList.length>0)return;this.findActiveNodeIndex(e)===-1&&(t||this.mindMap.emit("before_node_active",e,this.activeNodeList),this.mindMap.execCommand("SET_NODE_ACTIVE",e,!0),this.activeNodeList.push(e))}removeNodeFromActiveList(e){let t=this.findActiveNodeIndex(e);t!==-1&&(this.mindMap.execCommand("SET_NODE_ACTIVE",e,!1),this.activeNodeList.splice(t,1))}activeMultiNode(e=[]){e.forEach(t=>{this.mindMap.emit("before_node_active",t,this.activeNodeList),this.addNodeToActiveList(t,!0),this.emitNodeActiveEvent(t)})}cancelActiveMultiNode(e=[]){e.forEach(t=>{this.removeNodeFromActiveList(t),this.emitNodeActiveEvent(null)})}findActiveNodeIndex(e){return Ie(e,this.activeNodeList)}selectAll(){this.mindMap.opt.readonly||(de(this.root,null,e=>{e.getData("isActive")||this.addNodeToActiveList(e),e._generalizationList&&e._generalizationList.length>0&&e._generalizationList.forEach(t=>{let r=t.generalizationNode;r.getData("isActive")||this.addNodeToActiveList(r)})},null,!0,0,0),this.emitNodeActiveEvent())}back(e){this.backForward("back",e)}forward(e){this.backForward("forward",e)}backForward(e,t){this.mindMap.execCommand("CLEAR_ACTIVE_NODE");let r=this.mindMap.command[e](t);r&&(this.renderTree=r,this.mindMap.render()),this.mindMap.emit("data_change",r)}getNewNodeBehavior(e=!1,t=!1){let{createNewNodeBehavior:r}=this.mindMap.opt,n=!1,s=!1;switch(r){case k.CREATE_NEW_NODE_BEHAVIOR.DEFAULT:n=t||!e,s=t?!1:e;break;case k.CREATE_NEW_NODE_BEHAVIOR.NOT_ACTIVE:n=!1,s=!1;break;case k.CREATE_NEW_NODE_BEHAVIOR.ACTIVE_ONLY:n=!0,s=!1;break;default:break}return{focusNewNode:n,inserting:s}}insertNode(e=!0,t=[],r=null,n=[]){if(t=Ot(t),this.activeNodeList.length<=0&&t.length<=0)return;this.textEdit.hideEditTextBox();let{defaultInsertSecondLevelNodeText:s,defaultInsertBelowSecondLevelNodeText:a}=this.mindMap.opt,o=t.length>0?t:this.activeNodeList,l=o.length>1,h=this.hasRichTextPlugin(),{focusNewNode:d,inserting:c}=this.getNewNodeBehavior(e,l),f={expand:!0,richText:h,isActive:d};h&&(f.resetRichText=!0),n=Bo(n,f);let m=r&&r.richText,g=!1;o.forEach(x=>{if(x.isGeneralization||x.isRoot)return;n=Kt(n);let y=x.parent,S=x.layerIndex===1?s:a,A=ha(x);m&&f.resetRichText&&delete f.resetRichText;let C={inserting:c,data:{text:S,...f,uid:wt(),...r||{}},children:[...cs(n,g)]};g=!0,y.nodeData.children.splice(A+1,0,C)}),d&&this.clearActiveNodeList(),this.mindMap.render()}insertMultiNode(e,t){if(!t||t.length<=0||(e=Ot(e),this.activeNodeList.length<=0&&e.length<=0))return;this.textEdit.hideEditTextBox();let r=e.length>0?e:this.activeNodeList,n=this.hasRichTextPlugin(),{focusNewNode:s}=this.getNewNodeBehavior(!1,!0),a={expand:!0,richText:n,isActive:s};n&&(a.resetRichText=!0),t=Bo(t,a);let o=!1;r.forEach(l=>{if(l.isGeneralization||l.isRoot)return;t=Kt(t);let h=l.parent,d=ha(l),c=cs(t,o);o=!0,h.nodeData.children.splice(d+1,0,...c)}),s&&this.clearActiveNodeList(),this.mindMap.render()}insertChildNode(e=!0,t=[],r=null,n=[]){if(t=Ot(t),this.activeNodeList.length<=0&&t.length<=0)return;this.textEdit.hideEditTextBox();let{defaultInsertSecondLevelNodeText:s,defaultInsertBelowSecondLevelNodeText:a}=this.mindMap.opt,o=t.length>0?t:this.activeNodeList,l=o.length>1,h=this.hasRichTextPlugin(),{focusNewNode:d,inserting:c}=this.getNewNodeBehavior(e,l),f={expand:!0,richText:h,isActive:d};h&&(f.resetRichText=!0),n=Bo(n,f);let m=r&&r.richText,g=!1;o.forEach(x=>{if(x.isGeneralization)return;n=Kt(n),x.nodeData.children||(x.nodeData.children=[]);let y=x.isRoot?s:a;m&&f.resetRichText&&delete f.resetRichText;let b={inserting:c,data:{text:y,uid:wt(),...f,...r||{}},children:[...cs(n,g)]};g=!0,x.nodeData.children.push(b),x.setData({expand:!0})}),d&&this.clearActiveNodeList(),this.mindMap.render()}insertMultiChildNode(e,t){if(!t||t.length<=0||(e=Ot(e),this.activeNodeList.length<=0&&e.length<=0))return;this.textEdit.hideEditTextBox();let r=e.length>0?e:this.activeNodeList,n=this.hasRichTextPlugin(),{focusNewNode:s}=this.getNewNodeBehavior(!1,!0),a={expand:!0,richText:n,isActive:s};n&&(a.resetRichText=!0),t=Bo(t,a);let o=!1;r.forEach(l=>{l.isGeneralization||(t=Kt(t),l.nodeData.children||(l.nodeData.children=[]),t=cs(t,o),o=!0,l.nodeData.children.push(...t),l.setData({expand:!0}))}),s&&this.clearActiveNodeList(),this.mindMap.render()}insertParentNode(e=!0,t,r){if(t=Ot(t),this.activeNodeList.length<=0&&t.length<=0)return;this.textEdit.hideEditTextBox();let{defaultInsertSecondLevelNodeText:n,defaultInsertBelowSecondLevelNodeText:s}=this.mindMap.opt,a=t.length>0?t:this.activeNodeList,o=a.length>1,l=this.hasRichTextPlugin(),{focusNewNode:h,inserting:d}=this.getNewNodeBehavior(e,o),c={expand:!0,richText:l,isActive:h};l&&(c.resetRichText=!0);let f=r&&r.richText;a.forEach(m=>{if(m.isGeneralization||m.isRoot)return;let g=m.layerIndex===1?n:s;f&&c.resetRichText&&delete c.resetRichText;let x={inserting:d,data:{text:g,uid:wt(),...c,...r||{}},children:[m.nodeData]},y=m.parent,b=ha(m);y.nodeData.children.splice(b,1,x)}),h&&this.clearActiveNodeList(),this.mindMap.render()}upNode(e){if(this.activeNodeList.length<=0&&!e)return;let r=(e?[e]:this.activeNodeList)[0];if(r.isRoot)return;let n=r.parent,s=n.children,a=Ie(r,s);if(a===-1||a===0)return;let o=a-1;s.splice(a,1),s.splice(o,0,r),n.nodeData.children.splice(a,1),n.nodeData.children.splice(o,0,r.nodeData),this.mindMap.render()}downNode(e){if(this.activeNodeList.length<=0&&!e)return;let r=(e?[e]:this.activeNodeList)[0];if(r.isRoot)return;let n=r.parent,s=n.children,a=Ie(r,s);if(a===-1||a===s.length-1)return;let o=a+1;s.splice(a,1),s.splice(o,0,r),n.nodeData.children.splice(a,1),n.nodeData.children.splice(o,0,r.nodeData),this.mindMap.render()}moveUpOneLevel(e){if(e=e||this.activeNodeList[0],!e||e.isRoot||e.layerIndex<=1)return;let t=e.parent,r=t.parent,n=Ie(e,t.children),s=Ie(t,r.children);t.nodeData.children.splice(n,1),r.nodeData.children.splice(s+1,0,e.nodeData),this.mindMap.render()}_handleRemoveCustomStyles(e){let t=!1;return Object.keys(e).forEach(r=>{hs(r)&&(t=!0,delete e[r])}),this.hasRichTextPlugin()&&(t=!0,e.resetRichText=!0),t}removeCustomStyles(e){if(e=e||this.activeNodeList[0],!e)return;this._handleRemoveCustomStyles(e.getData())&&this.reRenderNodeCheckChange(e)}removeAllNodeCustomStyles(e){e=Ot(e);let t=!1;if(e.length>0)e.forEach(r=>{this._handleRemoveCustomStyles(r.getData())&&(t=!0)});else{if(!this.renderTree)return;de(this.renderTree,null,r=>{this._handleRemoveCustomStyles(r.data)&&(t=!0);let s=us(r.data);s.length>0&&s.forEach(a=>{this._handleRemoveCustomStyles(a)&&(t=!0)})})}t&&this.mindMap.reRender()}copy(){this.beingCopyData=this.copyNode(),this.beingCopyData&&(this.mindMap.opt.disabledClipboard||gu(xu(this.beingCopyData)))}cut(){this.mindMap.execCommand("CUT_NODE",e=>{this.beingCopyData=e,this.mindMap.opt.disabledClipboard||gu(xu(e))})}handlePaste(e){let{disabledClipboard:t}=this.mindMap.opt;if(t)return;let r=e.clipboardData||e.originalEvent.clipboardData,n=r.items,s=null,a="";Array.from(n).forEach(o=>{o.type.indexOf("image")>-1&&(s=o.getAsFile()),o.type.indexOf("text")>-1&&(a=r.getData("text"))}),this.paste()}async paste(){let{errorHandler:e,handleIsSplitByWrapOnPasteCreateNewNode:t,handleNodePasteImg:r,disabledClipboard:n,onlyPasteTextWhenHasImgAndText:s}=this.mindMap.opt;if(!n&&pu())try{let a=await C2(),o=a.text||"",l=a.img||null;if(o){let h=null,d=!0;if(this.mindMap.opt.customHandleClipboardText)try{let c=await this.mindMap.opt.customHandleClipboardText(o);if(!Yt(c)){d=!1;let f=Po(c);f.isSmm?h=f.data:o=f.data}}catch(c){e(Mi.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR,c)}if(d){let c=Po(o);c.isSmm?h=c.data:o=c.data}if(h)this.mindMap.execCommand("INSERT_MULTI_CHILD_NODE",[],Array.isArray(h)?h:[h]);else{this.hasRichTextPlugin()&&(o=mn(o));let c=o.split(new RegExp(`\r? + `,this.textEditNode.setAttribute("contenteditable",!0),this.textEditNode.addEventListener("keyup",y=>{y.stopPropagation()}),this.textEditNode.addEventListener("click",y=>{y.stopPropagation()}),this.textEditNode.addEventListener("mousedown",y=>{y.stopPropagation()}),this.textEditNode.addEventListener("keydown",y=>{this.checkIsAutoEnterTextEditKey(y)&&y.stopPropagation()}),this.textEditNode.addEventListener("paste",y=>{let b=y.clipboardData.getData("text"),{isSmm:E,data:k}=jo(b);E&&k[0]&&k[0].data?Cu(y,aa(k[0].data.text)):Cu(y),this.emitTextChangeEvent()}),this.textEditNode.addEventListener("input",()=>{this.emitTextChangeEvent()}),(this.mindMap.opt.customInnerElsAppendTo||document.body).appendChild(this.textEditNode));let c=this.mindMap.view.scale,f=e.style.merge("fontSize"),m=(this.cacheEditingText||e.getData("text")).split(/\n/gim).map(x=>fn(x)),g=e._textData.node.attr("data-ismultiLine")==="true";e.style.domText(this.textEditNode,c),h||(this.textEditNode.style.background=this.getBackground(e)),this.textEditNode.style.zIndex=a,n&&d?this.textEditNode.innerHTML="":this.textEditNode.innerHTML=m.join("
    "),this.textEditNode.style.minWidth=t.width+this.textNodePaddingX*2+"px",this.textEditNode.style.minHeight=t.height+"px",this.textEditNode.style.left=Math.floor(t.left)+"px",this.textEditNode.style.top=Math.floor(t.top)+"px",this.textEditNode.style.display="block",this.textEditNode.style.maxWidth=o*c+"px",g?(this.textEditNode.style.lineHeight=Gs,this.textEditNode.style.transform=`translateY(${(Gs-1)*f/2*c}px)`):this.textEditNode.style.lineHeight="normal",this.setIsShowTextEdit(!0),r||l&&!n?ha(this.textEditNode):la(this.textEditNode),this.cacheEditingText=""}emitTextChangeEvent(){this.mindMap.emit("node_text_edit_change",{node:this.currentNode,text:this.getEditText(),richText:!1})}updateTextEditNode(){if(this.mindMap.richText){this.mindMap.richText.updateTextEditNode();return}if(!this.showTextEdit||!this.currentNode)return;let e=this.currentNode._textData.node.node.getBoundingClientRect();this.textEditNode.style.minWidth=e.width+this.textNodePaddingX*2+"px",this.textEditNode.style.minHeight=e.height+this.textNodePaddingY*2+"px",this.textEditNode.style.left=Math.floor(e.left)+"px",this.textEditNode.style.top=Math.floor(e.top)+"px"}getBackground(e){if(e.style.merge("gradientStyle")){let r=e.style.merge("startColor"),n=e.style.merge("endColor");return`linear-gradient(to right, ${r}, ${n})`}else{let r=e.style.merge("fillColor"),n=e.style.merge("color");return r==="transparent"?Uo(n)?Z0(this.mindMap.themeConfig):"#fff":r}}removeTextEditEl(){if(this.mindMap.richText){this.mindMap.richText.removeTextEditEl();return}if(!this.textEditNode)return;(this.mindMap.opt.customInnerElsAppendTo||document.body).removeChild(this.textEditNode)}getEditText(){return sa(this.textEditNode.innerHTML)}hideEditTextBox(){if(this.mindMap.richText)return this.mindMap.richText.hideEditText();if(!this.showTextEdit)return;let e=this.currentNode,t=this.getEditText();this.currentNode=null,this.textEditNode.style.display="none",this.textEditNode.innerHTML="",this.textEditNode.style.fontFamily="inherit",this.textEditNode.style.fontSize="inherit",this.textEditNode.style.fontWeight="normal",this.textEditNode.style.transform="translateY(0)",this.setIsShowTextEdit(!1),this.mindMap.execCommand("SET_NODE_TEXT",e,t),this.mindMap.render(),this.mindMap.emit("hide_text_edit",this.textEditNode,this.renderer.activeNodeList,e)}getCurrentEditNode(){return this.mindMap.richText?this.mindMap.richText.node:this.currentNode}}});var rb,ib,Dp,nb,sb=T(()=>{rb=yt(j0());$v();Gv();Vv();Xv();Kv();Zv();Jv();tb();pe();Ru();qo();Ge();wt();ib={[C.LAYOUT.LOGICAL_STRUCTURE]:Sp,[C.LAYOUT.LOGICAL_STRUCTURE_LEFT]:Sp,[C.LAYOUT.MIND_MAP]:jv,[C.LAYOUT.CATALOG_ORGANIZATION]:Wv,[C.LAYOUT.ORGANIZATION_STRUCTURE]:Yv,[C.LAYOUT.TIMELINE]:Lp,[C.LAYOUT.TIMELINE2]:Lp,[C.LAYOUT.VERTICAL_TIMELINE]:_c,[C.LAYOUT.VERTICAL_TIMELINE2]:_c,[C.LAYOUT.VERTICAL_TIMELINE3]:_c,[C.LAYOUT.FISHBONE]:Rp,[C.LAYOUT.FISHBONE2]:Rp},Dp=class{constructor(e={}){this.opt=e,this.mindMap=e.mindMap,this.themeConfig=this.mindMap.themeConfig,this.renderTree=this.mindMap.opt.data?(0,rb.default)({},this.mindMap.opt.data):null,this.reRender=!1,this.isRendering=!1,this.hasWaitRendering=!1,this.nodeCache={},this.lastNodeCache={},this.renderSourceList=[],this.renderCallbackList=[],this.activeNodeList=[],this.emitNodeActiveEventTimer=null,this.renderTimer=null,this.root=null,this.textEdit=new m0(this),this.beingCopyData=null,this.highlightBoxNode=null,this.highlightBoxNodeStyle=null,this.lastActiveNodeList=[],this.setLayout(),this.bindEvent(),this.registerCommands(),this.registerShortcutKeys()}setLayout(){this.layout&&this.layout.beforeChange&&this.layout.beforeChange();let{layout:e}=this.mindMap.opt,t=ib[e]||this.mindMap[e];t||(t=ib[C.LAYOUT.LOGICAL_STRUCTURE],this.mindMap.opt.layout=C.LAYOUT.LOGICAL_STRUCTURE),this.layout=new t(this,e)}setData(e){this.renderTree=e||null}bindEvent(){let{openPerformance:e,performanceConfig:t,openRealtimeRenderOnNodeTextEdit:r}=this.mindMap.opt;this.mindMap.on("draw_click",s=>{this.clearActiveNodeListOnDrawClick(s,"click")}),this.mindMap.on("contextmenu",s=>{this.clearActiveNodeListOnDrawClick(s,"contextmenu")}),this.mindMap.svg.on("dblclick",()=>{this.mindMap.opt.enableDblclickBackToRootNode&&this.setRootNodeCenter()});let n=Ai(()=>{this.renderTree&&this.root&&(this.mindMap.emit("node_tree_render_start"),this.root.render(()=>{this.mindMap.emit("node_tree_render_end")},!1,!0))},t.time);e&&this.mindMap.on("view_data_change",n),this.onNodeTextEditChange=C2(this.onNodeTextEditChange,100,this),r&&this.mindMap.on("node_text_edit_change",this.onNodeTextEditChange),this.mindMap.on("after_update_config",(s,a)=>{s.openPerformance!==a.openPerformance&&(this.mindMap[s.openPerformance?"on":"off"]("view_data_change",n),this.forceLoadNode()),s.openRealtimeRenderOnNodeTextEdit!==a.openRealtimeRenderOnNodeTextEdit&&this.mindMap[s.openRealtimeRenderOnNodeTextEdit?"on":"off"]("node_text_edit_change",this.onNodeTextEditChange)})}onNodeTextEditChange({node:e,text:t}){e._textData=e.createTextNode(t);let{width:r,height:n}=e.getNodeRect();e.width=r,e.height=n,e.layout(),this.mindMap.render(()=>{this.textEdit.updateTextEditNode()})}forceLoadNode(e){e=e||this.root,e&&(this.mindMap.emit("node_tree_render_start"),e.render(()=>{this.mindMap.emit("node_tree_render_end")},!0))}registerCommands(){this.selectAll=this.selectAll.bind(this),this.mindMap.command.add("SELECT_ALL",this.selectAll),this.back=this.back.bind(this),this.mindMap.command.add("BACK",this.back),this.forward=this.forward.bind(this),this.mindMap.command.add("FORWARD",this.forward),this.insertNode=this.insertNode.bind(this),this.mindMap.command.add("INSERT_NODE",this.insertNode),this.insertMultiNode=this.insertMultiNode.bind(this),this.mindMap.command.add("INSERT_MULTI_NODE",this.insertMultiNode),this.insertChildNode=this.insertChildNode.bind(this),this.mindMap.command.add("INSERT_CHILD_NODE",this.insertChildNode),this.insertMultiChildNode=this.insertMultiChildNode.bind(this),this.mindMap.command.add("INSERT_MULTI_CHILD_NODE",this.insertMultiChildNode),this.insertParentNode=this.insertParentNode.bind(this),this.mindMap.command.add("INSERT_PARENT_NODE",this.insertParentNode),this.upNode=this.upNode.bind(this),this.mindMap.command.add("UP_NODE",this.upNode),this.downNode=this.downNode.bind(this),this.mindMap.command.add("DOWN_NODE",this.downNode),this.moveUpOneLevel=this.moveUpOneLevel.bind(this),this.mindMap.command.add("MOVE_UP_ONE_LEVEL",this.moveUpOneLevel),this.insertAfter=this.insertAfter.bind(this),this.mindMap.command.add("INSERT_AFTER",this.insertAfter),this.insertBefore=this.insertBefore.bind(this),this.mindMap.command.add("INSERT_BEFORE",this.insertBefore),this.moveNodeTo=this.moveNodeTo.bind(this),this.mindMap.command.add("MOVE_NODE_TO",this.moveNodeTo),this.removeNode=this.removeNode.bind(this),this.mindMap.command.add("REMOVE_NODE",this.removeNode),this.removeCurrentNode=this.removeCurrentNode.bind(this),this.mindMap.command.add("REMOVE_CURRENT_NODE",this.removeCurrentNode),this.pasteNode=this.pasteNode.bind(this),this.mindMap.command.add("PASTE_NODE",this.pasteNode),this.cutNode=this.cutNode.bind(this),this.mindMap.command.add("CUT_NODE",this.cutNode),this.setNodeStyle=this.setNodeStyle.bind(this),this.mindMap.command.add("SET_NODE_STYLE",this.setNodeStyle),this.setNodeStyles=this.setNodeStyles.bind(this),this.mindMap.command.add("SET_NODE_STYLES",this.setNodeStyles),this.setNodeActive=this.setNodeActive.bind(this),this.mindMap.command.add("SET_NODE_ACTIVE",this.setNodeActive),this.clearActiveNode=this.clearActiveNode.bind(this),this.mindMap.command.add("CLEAR_ACTIVE_NODE",this.clearActiveNode),this.setNodeExpand=this.setNodeExpand.bind(this),this.mindMap.command.add("SET_NODE_EXPAND",this.setNodeExpand),this.expandAllNode=this.expandAllNode.bind(this),this.mindMap.command.add("EXPAND_ALL",this.expandAllNode),this.unexpandAllNode=this.unexpandAllNode.bind(this),this.mindMap.command.add("UNEXPAND_ALL",this.unexpandAllNode),this.expandToLevel=this.expandToLevel.bind(this),this.mindMap.command.add("UNEXPAND_TO_LEVEL",this.expandToLevel),this.setNodeData=this.setNodeData.bind(this),this.mindMap.command.add("SET_NODE_DATA",this.setNodeData),this.setNodeText=this.setNodeText.bind(this),this.mindMap.command.add("SET_NODE_TEXT",this.setNodeText),this.setNodeImage=this.setNodeImage.bind(this),this.mindMap.command.add("SET_NODE_IMAGE",this.setNodeImage),this.setNodeIcon=this.setNodeIcon.bind(this),this.mindMap.command.add("SET_NODE_ICON",this.setNodeIcon),this.setNodeHyperlink=this.setNodeHyperlink.bind(this),this.mindMap.command.add("SET_NODE_HYPERLINK",this.setNodeHyperlink),this.setNodeNote=this.setNodeNote.bind(this),this.mindMap.command.add("SET_NODE_NOTE",this.setNodeNote),this.setNodeAttachment=this.setNodeAttachment.bind(this),this.mindMap.command.add("SET_NODE_ATTACHMENT",this.setNodeAttachment),this.setNodeTag=this.setNodeTag.bind(this),this.mindMap.command.add("SET_NODE_TAG",this.setNodeTag),this.insertFormula=this.insertFormula.bind(this),this.mindMap.command.add("INSERT_FORMULA",this.insertFormula),this.addGeneralization=this.addGeneralization.bind(this),this.mindMap.command.add("ADD_GENERALIZATION",this.addGeneralization),this.removeGeneralization=this.removeGeneralization.bind(this),this.mindMap.command.add("REMOVE_GENERALIZATION",this.removeGeneralization),this.setNodeCustomPosition=this.setNodeCustomPosition.bind(this),this.mindMap.command.add("SET_NODE_CUSTOM_POSITION",this.setNodeCustomPosition),this.resetLayout=this.resetLayout.bind(this),this.mindMap.command.add("RESET_LAYOUT",this.resetLayout),this.setNodeShape=this.setNodeShape.bind(this),this.mindMap.command.add("SET_NODE_SHAPE",this.setNodeShape),this.goTargetNode=this.goTargetNode.bind(this),this.mindMap.command.add("GO_TARGET_NODE",this.goTargetNode),this.removeCustomStyles=this.removeCustomStyles.bind(this),this.mindMap.command.add("REMOVE_CUSTOM_STYLES",this.removeCustomStyles),this.removeAllNodeCustomStyles=this.removeAllNodeCustomStyles.bind(this),this.mindMap.command.add("REMOVE_ALL_NODE_CUSTOM_STYLES",this.removeAllNodeCustomStyles)}registerShortcutKeys(){this.mindMap.keyCommand.addShortcut("Tab",()=>{this.mindMap.execCommand("INSERT_CHILD_NODE")}),this.mindMap.keyCommand.addShortcut("Insert",()=>{this.mindMap.execCommand("INSERT_CHILD_NODE")}),this.mindMap.keyCommand.addShortcut("Enter",()=>{this.mindMap.execCommand("INSERT_NODE")}),this.mindMap.keyCommand.addShortcut("Shift+Tab",()=>{this.mindMap.execCommand("INSERT_PARENT_NODE")}),this.mindMap.keyCommand.addShortcut("Control+g",()=>{this.mindMap.execCommand("ADD_GENERALIZATION")}),this.toggleActiveExpand=this.toggleActiveExpand.bind(this),this.mindMap.keyCommand.addShortcut("/",this.toggleActiveExpand),this.mindMap.keyCommand.addShortcut("Del|Backspace",()=>{this.mindMap.execCommand("REMOVE_NODE")}),this.mindMap.keyCommand.addShortcut("Shift+Backspace",()=>{this.mindMap.execCommand("REMOVE_CURRENT_NODE")}),this.mindMap.on("before_show_text_edit",()=>{this.startTextEdit()}),this.mindMap.on("hide_text_edit",()=>{this.endTextEdit()}),this.mindMap.keyCommand.addShortcut("Control+a",()=>{this.mindMap.execCommand("SELECT_ALL")}),this.mindMap.keyCommand.addShortcut("Control+l",()=>{this.mindMap.execCommand("RESET_LAYOUT")}),this.mindMap.keyCommand.addShortcut("Control+Up",()=>{this.mindMap.execCommand("UP_NODE")}),this.mindMap.keyCommand.addShortcut("Control+Down",()=>{this.mindMap.execCommand("DOWN_NODE")}),this.mindMap.keyCommand.addShortcut("Control+c",()=>{this.copy()}),this.mindMap.keyCommand.addShortcut("Control+x",()=>{this.cut()}),this.mindMap.keyCommand.addShortcut("Control+v",()=>{this.paste()}),this.mindMap.keyCommand.addShortcut("Control+Enter",()=>{this.setRootNodeCenter()})}emitNodeActiveEvent(e=null,t=[...this.activeNodeList]){G2(this.lastActiveNodeList,t)||(this.lastActiveNodeList=[...t],clearTimeout(this.emitNodeActiveEventTimer),this.emitNodeActiveEventTimer=setTimeout(()=>{this.mindMap.emit("node_active",e,t)},0))}clearActiveNodeListOnDrawClick(e,t){if(this.activeNodeList.length<=0)return;let r=!0,{useLeftKeySelectionRightKeyDrag:n}=this.mindMap.opt;if(t==="contextmenu"?!n:n){let s=this.mindMap.event.mousedownPos;r=Math.abs(e.clientX-s.x)<=5&&Math.abs(e.clientY-s.y)<=5}r&&this.mindMap.execCommand("CLEAR_ACTIVE_NODE")}startTextEdit(){this.mindMap.keyCommand.save()}endTextEdit(){this.mindMap.keyCommand.restore()}clearCache(){this.layout.lru.clear(),this.nodeCache={},this.lastNodeCache={}}addRenderParams(e,t){e&&this.renderCallbackList.findIndex(n=>n===e)===-1&&this.renderCallbackList.push(e),t&&this.renderSourceList.findIndex(n=>n===t)===-1&&this.renderSourceList.push(t)}checkHasRenderSource(e){e=Array.isArray(e)?e:[e];for(let t=0;t{e()}),this.isRendering=!1,this.reRender=!1,this.renderCallbackList=[],this.renderSourceList=[],this.mindMap.emit("node_tree_render_end")}render(e,t){this.addRenderParams(e,t),clearTimeout(this.renderTimer),this.renderTimer=setTimeout(()=>{this._render()},0)}_render(){if(this.checkHasRenderSource(C.CHANGE_THEME)&&this.resetUnExpandNodeStyle(),this.isRendering){this.hasWaitRendering=!0;return}if(this.isRendering=!0,this.lastNodeCache=this.nodeCache,this.nodeCache={},this.reRender&&this.clearActiveNodeList(),!this.renderTree){this.onRenderEnd();return}this.mindMap.emit("node_tree_render_start"),this.root=null,this.layout.doLayout(e=>{Object.keys(this.lastNodeCache).forEach(t=>{this.nodeCache[t]||(this.removeNodeFromActiveList(this.lastNodeCache[t]),this.emitNodeActiveEvent(),this.lastNodeCache[t].destroy())}),this.root=e,this.root.render(()=>{if(this.isRendering=!1,this.hasWaitRendering){this.hasWaitRendering=!1,this.render();return}this.onRenderEnd()})}),this.emitNodeActiveEvent()}resetUnExpandNodeStyle(){this.renderTree&&ue(this.renderTree,null,e=>{if(!e.data.expand)return ue(e,null,t=>{t.data.needUpdate=!0}),!0})}clearActiveNode(){this.activeNodeList.length<=0||(this.clearActiveNodeList(),this.emitNodeActiveEvent(null,[]))}clearActiveNodeList(){this.activeNodeList.forEach(e=>{this.mindMap.execCommand("SET_NODE_ACTIVE",e,!1)}),this.activeNodeList=[]}addNodeToActiveList(e,t=!1){if(this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate&&e.userList.length>0)return;this.findActiveNodeIndex(e)===-1&&(t||this.mindMap.emit("before_node_active",e,this.activeNodeList),this.mindMap.execCommand("SET_NODE_ACTIVE",e,!0),this.activeNodeList.push(e))}removeNodeFromActiveList(e){let t=this.findActiveNodeIndex(e);t!==-1&&(this.mindMap.execCommand("SET_NODE_ACTIVE",e,!1),this.activeNodeList.splice(t,1))}activeMultiNode(e=[]){e.forEach(t=>{this.mindMap.emit("before_node_active",t,this.activeNodeList),this.addNodeToActiveList(t,!0),this.emitNodeActiveEvent(t)})}cancelActiveMultiNode(e=[]){e.forEach(t=>{this.removeNodeFromActiveList(t),this.emitNodeActiveEvent(null)})}findActiveNodeIndex(e){return ze(e,this.activeNodeList)}selectAll(){this.mindMap.opt.readonly||(ue(this.root,null,e=>{e.getData("isActive")||this.addNodeToActiveList(e),e._generalizationList&&e._generalizationList.length>0&&e._generalizationList.forEach(t=>{let r=t.generalizationNode;r.getData("isActive")||this.addNodeToActiveList(r)})},null,!0,0,0),this.emitNodeActiveEvent())}back(e){this.backForward("back",e)}forward(e){this.backForward("forward",e)}backForward(e,t){this.mindMap.execCommand("CLEAR_ACTIVE_NODE");let r=this.mindMap.command[e](t);r&&(this.renderTree=r,this.mindMap.render()),this.mindMap.emit("data_change",r)}getNewNodeBehavior(e=!1,t=!1){let{createNewNodeBehavior:r}=this.mindMap.opt,n=!1,s=!1;switch(r){case C.CREATE_NEW_NODE_BEHAVIOR.DEFAULT:n=t||!e,s=t?!1:e;break;case C.CREATE_NEW_NODE_BEHAVIOR.NOT_ACTIVE:n=!1,s=!1;break;case C.CREATE_NEW_NODE_BEHAVIOR.ACTIVE_ONLY:n=!0,s=!1;break;default:break}return{focusNewNode:n,inserting:s}}insertNode(e=!0,t=[],r=null,n=[]){if(t=Ot(t),this.activeNodeList.length<=0&&t.length<=0)return;this.textEdit.hideEditTextBox();let{defaultInsertSecondLevelNodeText:s,defaultInsertBelowSecondLevelNodeText:a}=this.mindMap.opt,o=t.length>0?t:this.activeNodeList,l=o.length>1,h=this.hasRichTextPlugin(),{focusNewNode:d,inserting:c}=this.getNewNodeBehavior(e,l),f={expand:!0,richText:h,isActive:d};h&&(f.resetRichText=!0),n=$o(n,f);let m=r&&r.richText,g=!1;o.forEach(x=>{if(x.isGeneralization||x.isRoot)return;n=Qt(n);let y=x.parent,E=x.layerIndex===1?s:a,k=da(x);m&&f.resetRichText&&delete f.resetRichText;let L={inserting:c,data:{text:E,...f,uid:Tt(),...r||{}},children:[...cs(n,g)]};g=!0,y.nodeData.children.splice(k+1,0,L)}),d&&this.clearActiveNodeList(),this.mindMap.render()}insertMultiNode(e,t){if(!t||t.length<=0||(e=Ot(e),this.activeNodeList.length<=0&&e.length<=0))return;this.textEdit.hideEditTextBox();let r=e.length>0?e:this.activeNodeList,n=this.hasRichTextPlugin(),{focusNewNode:s}=this.getNewNodeBehavior(!1,!0),a={expand:!0,richText:n,isActive:s};n&&(a.resetRichText=!0),t=$o(t,a);let o=!1;r.forEach(l=>{if(l.isGeneralization||l.isRoot)return;t=Qt(t);let h=l.parent,d=da(l),c=cs(t,o);o=!0,h.nodeData.children.splice(d+1,0,...c)}),s&&this.clearActiveNodeList(),this.mindMap.render()}insertChildNode(e=!0,t=[],r=null,n=[]){if(t=Ot(t),this.activeNodeList.length<=0&&t.length<=0)return;this.textEdit.hideEditTextBox();let{defaultInsertSecondLevelNodeText:s,defaultInsertBelowSecondLevelNodeText:a}=this.mindMap.opt,o=t.length>0?t:this.activeNodeList,l=o.length>1,h=this.hasRichTextPlugin(),{focusNewNode:d,inserting:c}=this.getNewNodeBehavior(e,l),f={expand:!0,richText:h,isActive:d};h&&(f.resetRichText=!0),n=$o(n,f);let m=r&&r.richText,g=!1;o.forEach(x=>{if(x.isGeneralization)return;n=Qt(n),x.nodeData.children||(x.nodeData.children=[]);let y=x.isRoot?s:a;m&&f.resetRichText&&delete f.resetRichText;let b={inserting:c,data:{text:y,uid:Tt(),...f,...r||{}},children:[...cs(n,g)]};g=!0,x.nodeData.children.push(b),x.setData({expand:!0})}),d&&this.clearActiveNodeList(),this.mindMap.render()}insertMultiChildNode(e,t){if(!t||t.length<=0||(e=Ot(e),this.activeNodeList.length<=0&&e.length<=0))return;this.textEdit.hideEditTextBox();let r=e.length>0?e:this.activeNodeList,n=this.hasRichTextPlugin(),{focusNewNode:s}=this.getNewNodeBehavior(!1,!0),a={expand:!0,richText:n,isActive:s};n&&(a.resetRichText=!0),t=$o(t,a);let o=!1;r.forEach(l=>{l.isGeneralization||(t=Qt(t),l.nodeData.children||(l.nodeData.children=[]),t=cs(t,o),o=!0,l.nodeData.children.push(...t),l.setData({expand:!0}))}),s&&this.clearActiveNodeList(),this.mindMap.render()}insertParentNode(e=!0,t,r){if(t=Ot(t),this.activeNodeList.length<=0&&t.length<=0)return;this.textEdit.hideEditTextBox();let{defaultInsertSecondLevelNodeText:n,defaultInsertBelowSecondLevelNodeText:s}=this.mindMap.opt,a=t.length>0?t:this.activeNodeList,o=a.length>1,l=this.hasRichTextPlugin(),{focusNewNode:h,inserting:d}=this.getNewNodeBehavior(e,o),c={expand:!0,richText:l,isActive:h};l&&(c.resetRichText=!0);let f=r&&r.richText;a.forEach(m=>{if(m.isGeneralization||m.isRoot)return;let g=m.layerIndex===1?n:s;f&&c.resetRichText&&delete c.resetRichText;let x={inserting:d,data:{text:g,uid:Tt(),...c,...r||{}},children:[m.nodeData]},y=m.parent,b=da(m);y.nodeData.children.splice(b,1,x)}),h&&this.clearActiveNodeList(),this.mindMap.render()}upNode(e){if(this.activeNodeList.length<=0&&!e)return;let r=(e?[e]:this.activeNodeList)[0];if(r.isRoot)return;let n=r.parent,s=n.children,a=ze(r,s);if(a===-1||a===0)return;let o=a-1;s.splice(a,1),s.splice(o,0,r),n.nodeData.children.splice(a,1),n.nodeData.children.splice(o,0,r.nodeData),this.mindMap.render()}downNode(e){if(this.activeNodeList.length<=0&&!e)return;let r=(e?[e]:this.activeNodeList)[0];if(r.isRoot)return;let n=r.parent,s=n.children,a=ze(r,s);if(a===-1||a===s.length-1)return;let o=a+1;s.splice(a,1),s.splice(o,0,r),n.nodeData.children.splice(a,1),n.nodeData.children.splice(o,0,r.nodeData),this.mindMap.render()}moveUpOneLevel(e){if(e=e||this.activeNodeList[0],!e||e.isRoot||e.layerIndex<=1)return;let t=e.parent,r=t.parent,n=ze(e,t.children),s=ze(t,r.children);t.nodeData.children.splice(n,1),r.nodeData.children.splice(s+1,0,e.nodeData),this.mindMap.render()}_handleRemoveCustomStyles(e){let t=!1;return Object.keys(e).forEach(r=>{hs(r)&&(t=!0,delete e[r])}),this.hasRichTextPlugin()&&(t=!0,e.resetRichText=!0),t}removeCustomStyles(e){if(e=e||this.activeNodeList[0],!e)return;this._handleRemoveCustomStyles(e.getData())&&this.reRenderNodeCheckChange(e)}removeAllNodeCustomStyles(e){e=Ot(e);let t=!1;if(e.length>0)e.forEach(r=>{this._handleRemoveCustomStyles(r.getData())&&(t=!0)});else{if(!this.renderTree)return;ue(this.renderTree,null,r=>{this._handleRemoveCustomStyles(r.data)&&(t=!0);let s=us(r.data);s.length>0&&s.forEach(a=>{this._handleRemoveCustomStyles(a)&&(t=!0)})})}t&&this.mindMap.reRender()}copy(){this.beingCopyData=this.copyNode(),this.beingCopyData&&(this.mindMap.opt.disabledClipboard||Au(ku(this.beingCopyData)))}cut(){this.mindMap.execCommand("CUT_NODE",e=>{this.beingCopyData=e,this.mindMap.opt.disabledClipboard||Au(ku(e))})}handlePaste(e){let{disabledClipboard:t}=this.mindMap.opt;if(t)return;let r=e.clipboardData||e.originalEvent.clipboardData,n=r.items,s=null,a="";Array.from(n).forEach(o=>{o.type.indexOf("image")>-1&&(s=o.getAsFile()),o.type.indexOf("text")>-1&&(a=r.getData("text"))}),this.paste()}async paste(){let{errorHandler:e,handleIsSplitByWrapOnPasteCreateNewNode:t,handleNodePasteImg:r,disabledClipboard:n,onlyPasteTextWhenHasImgAndText:s}=this.mindMap.opt;if(!n&&Su())try{let a=await $2(),o=a.text||"",l=a.img||null;if(o){let h=null,d=!0;if(this.mindMap.opt.customHandleClipboardText)try{let c=await this.mindMap.opt.customHandleClipboardText(o);if(!Kt(c)){d=!1;let f=jo(c);f.isSmm?h=f.data:o=f.data}}catch(c){e(Ti.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR,c)}if(d){let c=jo(o);c.isSmm?h=c.data:o=c.data}if(h)this.mindMap.execCommand("INSERT_MULTI_CHILD_NODE",[],Array.isArray(h)?h:[h]);else{this.hasRichTextPlugin()&&(o=fn(o));let c=o.split(new RegExp(`\r? |(?!!f);c.length>1&&t?t().then(()=>{this.mindMap.execCommand("INSERT_MULTI_CHILD_NODE",[],c.map(f=>({data:{text:f},children:[]})))}).catch(()=>{this.mindMap.execCommand("INSERT_CHILD_NODE",!1,[],{text:o})}):this.mindMap.execCommand("INSERT_CHILD_NODE",!1,[],{text:o})}}if(l&&(!o||!s))try{let h=null;r&&typeof r=="function"?h=await r(l):h=await v2(l),this.activeNodeList.length>0&&this.activeNodeList.forEach(d=>{this.mindMap.execCommand("SET_NODE_IMAGE",d,{url:h.url,title:"",width:h.size.width,height:h.size.height})})}catch(h){e(Mi.LOAD_CLIPBOARD_IMAGE_ERROR,h)}}catch(a){e(Mi.READ_CLIPBOARD_ERROR,a)}else this.beingCopyData&&this.mindMap.execCommand("PASTE_NODE",this.beingCopyData)}insertBefore(e,t){this.insertTo(e,t,"before")}insertAfter(e,t){this.insertTo(e,t,"after")}insertTo(e,t,r="before"){let n=Ot(e);n=n.filter(s=>!s.isRoot),r==="after"&&n.reverse(),n.forEach(s=>{let a=s.parent,o=a.children,l=Ie(s,o);if(l===-1)return;o.splice(l,1),a.nodeData.children.splice(l,1);let h=t.parent,d=h.children,c=Ie(t,d);c!==-1&&(r==="after"&&c++,d.splice(c,0,s),h.nodeData.children.splice(c,0,s.nodeData))}),this.mindMap.render()}removeNode(e=[]){if(e=Ot(e),this.activeNodeList.length<=0&&e.length<=0)return;let t=null,r=e.length>0,n=r?e:this.activeNodeList,s=n.find(a=>a.isRoot);if(s)this.clearActiveNodeList(),s.children=[],s.nodeData.children=[];else{t=this.getNextActiveNode(n);for(let a=0;a0?e:this.activeNodeList;r=r.filter(s=>!s.isRoot);let n=this.getNextActiveNode(r);for(let s=0;s0?t=n[s-1]:t=r.parent}return t}copyNode(){if(this.activeNodeList.length<=0)return null;let e=ds(this.activeNodeList);return e=Fo(e),e.map(t=>ls({},t,!0))}cutNode(e){if(this.activeNodeList.length<=0)return;let t=ds(this.activeNodeList).filter(n=>!n.isRoot);t=Fo(t);let r=t.map(n=>ls({},n,!0));t.forEach(n=>{V0(n)}),this.clearActiveNodeList(),this.mindMap.render(),e&&typeof e=="function"&&e(r)}moveNodeTo(e,t){let r=Ot(e);r=r.filter(n=>!n.isRoot),r.forEach(n=>{this.removeNodeFromActiveList(n),V0(n),t.setData({expand:!0}),t.nodeData.children.push(n.nodeData)}),this.emitNodeActiveEvent(),this.mindMap.render()}pasteNode(e){e=Ot(e),this.mindMap.execCommand("INSERT_MULTI_CHILD_NODE",[],e)}setNodeStyle(e,t,r){let n={[t]:r};this.setNodeDataRender(e,n),zo.includes(t)&&(e.parent||e).renderLine(!0)}setNodeStyles(e,t){let r={...t};this.setNodeDataRender(e,r);let n=Object.keys(t),s=!1;n.forEach(a=>{zo.includes(a)&&(s=!0)}),s&&(e.parent||e).renderLine(!0)}setNodeActive(e,t){this.mindMap.execCommand("SET_NODE_DATA",e,{isActive:t}),e.updateNodeByActive(t)}setNodeExpand(e,t){this.mindMap.execCommand("SET_NODE_DATA",e,{expand:t}),this.mindMap.render()}expandAllNode(e=""){if(!this.renderTree)return;let t=(r,n)=>{!n&&r.data.uid===e&&(n=!0),n&&!r.data.expand&&(r.data.expand=!0),r.children&&r.children.length>0&&r.children.forEach(s=>{t(s,n)})};t(this.renderTree,!e),this.mindMap.render()}unexpandAllNode(e=!0,t=""){if(!this.renderTree)return;let r=(n,s,a)=>{!a&&n.data.uid===t&&(a=!0),a&&!s&&n.children&&n.children.length>0&&(n.data.expand=!1),n.children&&n.children.length>0&&n.children.forEach(o=>{r(o,!1,a)})};r(this.renderTree,!0,!t),this.mindMap.render(()=>{e&&this.setRootNodeCenter()})}expandToLevel(e){this.renderTree&&(de(this.renderTree,null,(t,r,n,s)=>{s0&&(t.data.expand=!1)},null,!0,0,0),this.mindMap.render())}toggleActiveExpand(){this.activeNodeList.forEach(e=>{e.nodeData.children.length<=0||e.isRoot||this.toggleNodeExpand(e)})}toggleNodeExpand(e){this.mindMap.execCommand("SET_NODE_EXPAND",e,!e.getData("expand"))}setNodeText(e,t,r,n){r=r===void 0?e.getData("richText"):r,this.setNodeDataRender(e,{text:t,richText:r,resetRichText:n})}setNodeImage(e,t){let{url:r,title:n,width:s,height:a,custom:o=!1}=t||{url:"",title:"",width:0,height:0,custom:!1};this.setNodeDataRender(e,{image:r,imageTitle:n||"",imageSize:{width:s,height:a,custom:o}})}setNodeIcon(e,t){this.setNodeDataRender(e,{icon:t})}setNodeHyperlink(e,t,r=""){this.setNodeDataRender(e,{hyperlink:t,hyperlinkTitle:r})}setNodeNote(e,t){this.setNodeDataRender(e,{note:t})}setNodeAttachment(e,t,r=""){this.setNodeDataRender(e,{attachmentUrl:t,attachmentName:r})}setNodeTag(e,t){this.setNodeDataRender(e,{tag:t})}insertFormula(e,t=[]){if(!this.hasRichTextPlugin()||!this.mindMap.formula)return;t=Ot(t),(t.length>0?t:this.activeNodeList).forEach(n=>{this.mindMap.formula.insertFormulaToNode(n,e)})}addGeneralization(e,t=!0){if(this.activeNodeList.length<=0)return;let r=this.activeNodeList.filter(d=>!d.isRoot&&!d.isGeneralization&&!d.checkHasSelfGeneralization()),n=A2(r);if(n.length<=0)return;let s=this.hasRichTextPlugin(),{focusNewNode:a,inserting:o}=this.getNewNodeBehavior(t,n.length>1),l=!1,h=e&&e.richText;n.forEach(d=>{let c={inserting:o,...e||{text:this.mindMap.opt.defaultGeneralizationText},range:d.range||null,uid:wt(),richText:s,isActive:a};s&&!h&&(c.resetRichText=s);let f=d.node.getData("generalization");if(f=f?Array.isArray(f)?f:[f]:[],d.range){if(!!f.find(g=>g.range&&g.range[0]===d.range[0]&&g.range[1]===d.range[1]))return;f.push(c)}else f.push(c);l=!0,this.mindMap.execCommand("SET_NODE_DATA",d.node,{generalization:f}),d.node.setData({expand:!0})}),l&&(a&&this.clearActiveNodeList(),this.mindMap.render(()=>{this.mindMap.render()}))}removeGeneralization(){this.activeNodeList.length<=0||(this.activeNodeList.forEach(e=>{e.checkHasGeneralization()&&this.mindMap.execCommand("SET_NODE_DATA",e,{generalization:null})}),this.mindMap.render(),this.closeHighlightNode())}setNodeCustomPosition(e,t=void 0,r=void 0){[e].forEach(s=>{this.mindMap.execCommand("SET_NODE_DATA",s,{customLeft:t,customTop:r})})}resetLayout(){de(this.root,null,e=>{e.customLeft=void 0,e.customTop=void 0,this.mindMap.execCommand("SET_NODE_DATA",e,{customLeft:void 0,customTop:void 0}),this.mindMap.render()},null,!0,0,0)}setNodeShape(e,t){if(!t||!P2.includes(t))return;[e].forEach(n=>{this.setNodeStyle(n,"shape",t)})}goTargetNode(e,t=()=>{}){let r=typeof e=="string"?e:e.getData("uid");r&&this.expandToNodeUid(r,()=>{let n=this.findNodeByUid(r);n&&(n.active(),this.moveNodeToCenter(n),t(n))})}setNodeData(e,t){Object.keys(t).forEach(r=>{e.nodeData.data[r]=t[r]})}setNodeDataRender(e,t,r=!1){if(this.mindMap.execCommand("SET_NODE_DATA",e,t),E2(t)){this.mindMap.emit("node_tree_render_end");return}this.reRenderNodeCheckChange(e,r)}reRenderNodeCheckChange(e,t){e.reRender()?t||this.mindMap.render():this.mindMap.emit("node_tree_render_end")}moveNodeToCenter(e,t){let{resetScaleOnMoveNodeToCenter:r}=this.mindMap.opt;t!==void 0&&(r=t);let{transform:n,state:s}=this.mindMap.view.getTransformData(),{left:a,top:o,width:l,height:h}=e;r||(a*=n.scaleX,o*=n.scaleY,l*=n.scaleX,h*=n.scaleY);let d=this.mindMap.width/2,c=this.mindMap.height/2,f=a+l/2,m=o+h/2,g=d-s.x,x=c-s.y,y=g-f,b=x-m;this.mindMap.view.translateX(y),this.mindMap.view.translateY(b),r&&this.mindMap.view.setScale(1)}setRootNodeCenter(){this.moveNodeToCenter(this.root)}expandToNodeUid(e,t=()=>{}){if(!this.renderTree){t();return}let r=[],n=!1,s={};Xt(this.renderTree,(o,l)=>{if(o.data.uid===e)return r=l?[...s[l.data.uid],l]:[],"stop";if(us(o.data).forEach(d=>{d.uid===e&&(r=l?[...s[l.data.uid],l,o]:[],n=!0)}),n)return"stop";s[o.data.uid]=l?[...s[l.data.uid],l]:[]});let a=!1;if(r.forEach(o=>{o.data.expand||(a=!0,o.data.expand=!0)}),n){let o=r[r.length-1];o&&de(o,null,l=>{l.data.expand||(a=!0,l.data.expand=!0)})}a?this.mindMap.render(t):t()}findNodeByUid(e){if(!this.root)return;let t=null;return de(this.root,null,r=>{if(r.getData("uid")===e)return t=r,!0;let n=!1;if((r._generalizationList||[]).forEach(s=>{s.generalizationNode.getData("uid")===e&&(t=s.generalizationNode,n=!0)}),n)return!0}),t}highlightNode(e,t,r){if(this.isRendering)return;r={stroke:"rgb(94, 200, 248)",fill:"transparent",...r||{}},this.highlightBoxNode?this.highlightBoxNodeStyle&&(this.highlightBoxNodeStyle.stroke!==r.stroke||this.highlightBoxNodeStyle.fill!==r.fill)&&this.highlightBoxNode.stroke({color:r.stroke||"transparent"}).fill({color:r.fill||"transparent"}):this.highlightBoxNode=new Gi().stroke({color:r.stroke||"transparent"}).fill({color:r.fill||"transparent"}),this.highlightBoxNodeStyle={...r};let n=1/0,s=1/0,a=-1/0,o=-1/0;t?e.children.slice(t[0],t[1]+1).forEach(h=>{h.lefta&&(a=d),c>o&&(o=c)}):(n=e.left,s=e.top,a=e.left+e.width,o=e.top+e.height),this.highlightBoxNode.plot([[n,s],[a,s],[a,o],[n,o]]),this.mindMap.otherDraw.add(this.highlightBoxNode)}closeHighlightNode(){this.highlightBoxNode&&this.highlightBoxNode.remove()}hasRichTextPlugin(){return!!this.mindMap.richText}},jv=Np});var jn,Vv=T(()=>{Ro();jn={default:B0}});var Ep,Gn,Wv=T(()=>{Ep={Backspace:8,Tab:9,Enter:13,Shift:16,Control:17,Alt:18,CapsLock:20,Esc:27,Spacebar:32,PageUp:33,PageDown:34,End:35,Home:36,Insert:45,Left:37,Up:38,Right:39,Down:40,Del:46,NumLock:144,Cmd:91,CmdFF:224,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"`":192,"=":187,"-":189,"/":191,".":190};for(let i=0;i<=9;i++)Ep[i]=i+48;"abcdefghijklmnopqrstuvwxyz".split("").forEach((i,e)=>{Ep[i]=e+65});Gn=Ep});var h0,Yv=T(()=>{Wv();h0=class{constructor(e){this.opt=e,this.mindMap=e.mindMap,this.shortcutMap={},this.shortcutMapCache={},this.isPause=!1,this.isInSvg=!1,this.isStopCheckInSvg=!1,this.defaultEnableCheck=this.defaultEnableCheck.bind(this),this.bindEvent()}extendKeyMap(e,t){Gn[e]=t}removeKeyMap(e){typeof Gn[e]<"u"&&delete Gn[e]}pause(){this.isPause=!0}recovery(){this.isPause=!1}save(){Object.keys(this.shortcutMapCache).length>0||(this.shortcutMapCache=this.shortcutMap,this.shortcutMap={})}restore(){Object.keys(this.shortcutMapCache).length<=0||(this.shortcutMap=this.shortcutMapCache,this.shortcutMapCache={})}stopCheckInSvg(){let{enableShortcutOnlyWhenMouseInSvg:e}=this.mindMap.opt;e&&(this.isStopCheckInSvg=!0)}recoveryCheckInSvg(){let{enableShortcutOnlyWhenMouseInSvg:e}=this.mindMap.opt;e&&(this.isStopCheckInSvg=!0)}bindEvent(){this.onKeydown=this.onKeydown.bind(this),this.mindMap.on("svg_mouseenter",()=>{this.isInSvg=!0}),this.mindMap.on("svg_mouseleave",()=>{this.isInSvg=!1}),window.addEventListener("keydown",this.onKeydown),this.mindMap.on("beforeDestroy",()=>{this.unBindEvent()})}unBindEvent(){window.removeEventListener("keydown",this.onKeydown)}defaultEnableCheck(e){let t=e.target;if(t===document.body)return!0;for(let r=0;r{if(this.checkKey(e,a)){if(this.checkKey(e,"Control+v")||(e.stopPropagation(),e.preventDefault()),typeof r=="function"&&r(a,[...this.mindMap.renderer.activeNodeList]))return;this.shortcutMap[a].forEach(o=>{o()})}}))}checkKey(e,t){let r=this.getOriginEventCodeArr(e),n=this.getKeyCodeArr(t);if(r.length!==n.length)return!1;for(let s=0;so===r[s]);if(a===-1)return!1;n.splice(a,1)}return!0}getOriginEventCodeArr(e){let t=[];return(e.ctrlKey||e.metaKey)&&t.push(Gn.Control),e.altKey&&t.push(Gn.Alt),e.shiftKey&&t.push(Gn.Shift),t.includes(e.keyCode)||t.push(e.keyCode),t}hasCombinationKey(e){return e.ctrlKey||e.metaKey||e.altKey||e.shiftKey}getKeyCodeArr(e){let t=e.split(/\s*\+\s*/),r=[];return t.forEach(n=>{r.push(Gn[n])}),r}addShortcut(e,t){e.split(/\s*\|\s*/).forEach(r=>{this.shortcutMap[r]?this.shortcutMap[r].push(t):this.shortcutMap[r]=[t]})}removeShortcut(e,t){e.split(/\s*\|\s*/).forEach(r=>{if(this.shortcutMap[r])if(t){let n=this.shortcutMap[r].findIndex(s=>s===t);n!==-1&&this.shortcutMap[r].splice(n,1)}else this.shortcutMap[r]=[],delete this.shortcutMap[r]})}getShortcutFn(e){let t=[];return e.split(/\s*\|\s*/).forEach(r=>{t=this.shortcutMap[r]||[]}),t}}});var Kv,Xv=T(()=>{Kv={name:"simple-mind-map",version:"0.14.0-fix.1",description:"\u4E00\u4E2A\u7B80\u5355\u7684web\u5728\u7EBF\u601D\u7EF4\u5BFC\u56FE",authors:[{name:"\u8857\u89D2\u5C0F\u6797",email:"1013335014@qq.com"},{name:"\u7406\u60F3\u9752\u5E74\u5B9E\u9A8C\u5BA4",url:"http://lxqnsys.com/"}],types:"./types/index.d.ts",typings:"./types/index.d.ts",license:"MIT",repository:{type:"git",url:"https://github.com/wanglin2/mind-map"},scripts:{lint:"eslint src/",format:"prettier --write .",types:"npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck & node ./bin/createPluginsTypeFiles.js",wsServe:"node ./bin/wsServer.mjs"},module:"index.js",main:"./dist/simpleMindMap.umd.min.js",dependencies:{"@svgdotjs/svg.js":"3.2.0",deepmerge:"^1.5.2",eventemitter3:"^4.0.7",jszip:"^3.10.1",katex:"^0.16.8","mdast-util-from-markdown":"^1.3.0","pdf-lib":"^1.17.1",quill:"^2.0.3",tern:"^0.24.3",uuid:"^9.0.0",ws:"^7.5.9","xml-js":"^1.6.11","y-webrtc":"^10.2.5",yjs:"^13.6.8"},keywords:["javascript","svg","mind-map","mindMap","MindMap"],devDependencies:{eslint:"^8.25.0",prettier:"^2.7.1"}}});var Sp,Zv,Qv=T(()=>{pe();$e();Xv();Sp=class{constructor(e={}){this.opt=e,this.mindMap=e.mindMap,this.commands={},this.history=[],this.activeHistoryIndex=0,this.registerShortcutKeys(),this.originAddHistory=this.addHistory.bind(this),this.addHistory=Si(this.addHistory,this.mindMap.opt.addHistoryTime,this),this.isPause=!1}pause(){this.isPause=!0}recovery(){this.isPause=!1}clearHistory(){this.history=[],this.activeHistoryIndex=0,this.mindMap.emit("back_forward",0,0)}registerShortcutKeys(){this.mindMap.keyCommand.addShortcut("Control+z",()=>{this.mindMap.execCommand("BACK")}),this.mindMap.keyCommand.addShortcut("Control+y",()=>{this.mindMap.execCommand("FORWARD")})}exec(e,...t){if(this.commands[e]){if(this.commands[e].forEach(r=>{r(...t)}),this.mindMap.emit("afterExecCommand",e,...t),["BACK","FORWARD","SET_NODE_ACTIVE","CLEAR_ACTIVE_NODE"].includes(e))return;this.addHistory()}}add(e,t){this.commands[e]?this.commands[e].push(t):this.commands[e]=[t]}remove(e,t){if(this.commands[e])if(!t)this.commands[e]=[],delete this.commands[e];else{let r=this.commands[e].find(n=>n===t);r!==-1&&this.commands[e].splice(r,1)}}addHistory(){if(this.mindMap.opt.readonly||this.isPause)return;this.mindMap.emit("beforeAddHistory");let e=this.history.length>0?this.history[this.activeHistoryIndex]:null,t=this.getCopyData(),r=JSON.stringify(t);e&&e===r||(this.emitDataUpdatesEvent(e,r),this.history=this.history.slice(0,this.activeHistoryIndex+1),this.history.push(r),this.history.length>this.mindMap.opt.maxHistoryCount&&this.history.shift(),this.activeHistoryIndex=this.history.length-1,this.mindMap.emit("data_change",t),this.mindMap.emit("back_forward",this.activeHistoryIndex,this.history.length))}back(e=1){if(!this.mindMap.opt.readonly&&this.activeHistoryIndex-e>=0){let t=this.history[this.activeHistoryIndex];this.activeHistoryIndex-=e,this.mindMap.emit("back_forward",this.activeHistoryIndex,this.history.length);let r=this.history[this.activeHistoryIndex],n=JSON.parse(r);return this.emitDataUpdatesEvent(t,r),n}}forward(e=1){if(this.mindMap.opt.readonly)return;let t=this.history.length;if(this.activeHistoryIndex+e<=t-1){let r=this.history[this.activeHistoryIndex];this.activeHistoryIndex+=e,this.mindMap.emit("back_forward",this.activeHistoryIndex,this.history.length);let n=this.history[this.activeHistoryIndex],s=JSON.parse(n);return this.emitDataUpdatesEvent(r,n),s}}getCopyData(){if(!this.mindMap.renderer.renderTree)return null;let e=uu({},this.mindMap.renderer.renderTree,!0);return e.smmVersion=Kv.version,e}removeDataUid(e){e=Kt(e);let t=r=>{delete r.data.uid,r.children&&r.children.length>0&&r.children.forEach(n=>{t(n)})};return t(e),e}emitDataUpdatesEvent(e,t){try{let r="data_change_detail";if(this.mindMap.event.listenerCount(r)>0&&e&&t){let s=JSON.parse(e),a=JSON.parse(t),o=Kt(vu(s)),l=Kt(vu(a)),h=[],d=(c,f)=>(c.children&&c.children.length>0&&c.children.forEach((m,g)=>{c.children[g]=typeof m=="string"?f[m]:f[m.data.uid],d(c.children[g],f)}),c);Object.keys(l).forEach(c=>{o[c]?H0(o[c],l[c])||h.push({action:"update",oldData:d(o[c],o),data:d(l[c],l)}):h.push({action:"create",data:d(l[c],l)})}),Object.keys(o).forEach(c=>{l[c]||h.push({action:"delete",data:d(o[c],o)})}),this.mindMap.emit(r,h)}}catch(r){this.mindMap.opt.errorHandler(Mi.DATA_CHANGE_DETAIL_EVENT_ERROR,r)}}},Zv=Sp});var Ap,Jv,eb=T(()=>{pe();Ap=class{constructor(){this.has={},this.queue=[],this.nextTick=x2(this.flush,this)}push(e,t){if(this.has[e]){this.replaceTask(e,t);return}this.has[e]=!0,this.queue.push({name:e,fn:t}),this.nextTick()}replaceTask(e,t){let r=this.queue.findIndex(n=>n.name===e);r!==-1&&(this.queue[r]={name:e,fn:t})}flush(){let e=this.queue.slice(0);this.queue=[],e.forEach(({name:t,fn:r})=>{this.has[t]=!1,r()})}},Jv=Ap});var kp,tb=T(()=>{$e();kp={el:null,data:null,viewData:null,readonly:!1,layout:k.LAYOUT.LOGICAL_STRUCTURE,fishboneDeg:45,theme:"default",themeConfig:{},scaleRatio:.2,translateRatio:1,minZoomRatio:20,maxZoomRatio:400,customCheckIsTouchPad:null,mouseScaleCenterUseMousePosition:!0,maxTag:5,expandBtnSize:20,imgTextMargin:5,textContentMargin:2,customNoteContentShow:null,textAutoWrapWidth:500,customHandleMousewheel:null,mousewheelAction:k.MOUSE_WHEEL_ACTION.MOVE,mousewheelMoveStep:100,mousewheelZoomActionReverse:!0,defaultInsertSecondLevelNodeText:"\u4E8C\u7EA7\u8282\u70B9",defaultInsertBelowSecondLevelNodeText:"\u5206\u652F\u4E3B\u9898",expandBtnStyle:{color:"#808080",fill:"#fff",fontSize:13,strokeColor:"#333333"},expandBtnIcon:{open:"",close:""},expandBtnNumHandler:null,isShowExpandNum:!0,enableShortcutOnlyWhenMouseInSvg:!0,customCheckEnableShortcut:null,initRootNodePosition:null,nodeTextEditZIndex:3e3,nodeNoteTooltipZIndex:3e3,isEndNodeTextEditOnClickOuter:!0,maxHistoryCount:500,alwaysShowExpandBtn:!1,notShowExpandBtn:!1,iconList:[],maxNodeCacheCount:1e3,fitPadding:50,enableCtrlKeyNodeSelection:!0,useLeftKeySelectionRightKeyDrag:!1,beforeTextEdit:null,isUseCustomNodeContent:!1,customCreateNodeContent:null,customInnerElsAppendTo:null,enableAutoEnterTextEditWhenKeydown:!1,autoEmptyTextWhenKeydownEnterEdit:!1,customHandleClipboardText:null,disableMouseWheelZoom:!1,errorHandler:(i,e)=>{console.error(i,e)},enableDblclickBackToRootNode:!1,hoverRectColor:"rgb(94, 200, 248)",hoverRectPadding:2,selectTextOnEnterEditText:!1,deleteNodeActive:!0,fit:!1,tagsColorMap:{},cooperateStyle:{avatarSize:22,fontSize:12},onlyOneEnableActiveNodeOnCooperate:!1,defaultGeneralizationText:"\u6982\u8981",handleIsSplitByWrapOnPasteCreateNewNode:null,addHistoryTime:100,isDisableDrag:!1,createNewNodeBehavior:k.CREATE_NEW_NODE_BEHAVIOR.DEFAULT,defaultNodeImage:"",isLimitMindMapInCanvas:!1,handleNodePasteImg:null,customCreateNodePath:null,customCreateNodePolygon:null,customTransformNodeLinePath:null,beforeShortcutRun:null,resetScaleOnMoveNodeToCenter:!1,createNodePrefixContent:null,createNodePostfixContent:null,disabledClipboard:!1,customHyperlinkJump:null,openPerformance:!1,performanceConfig:{time:250,padding:100,removeNodeWhenOutCanvas:!0},emptyTextMeasureHeightText:"abc123\u6211\u548C\u4F60",openRealtimeRenderOnNodeTextEdit:!1,mousedownEventPreventDefault:!1,onlyPasteTextWhenHasImgAndText:!0,enableDragModifyNodeWidth:!0,minNodeTextModifyWidth:20,maxNodeTextModifyWidth:-1,customHandleLine:null,addHistoryOnInit:!0,noteIcon:{icon:"",style:{}},hyperlinkIcon:{icon:"",style:{}},attachmentIcon:{icon:"",style:{}},isShowCreateChildBtnIcon:!0,quickCreateChildBtnIcon:{icon:"",style:{}},customQuickCreateChildBtnClick:null,addCustomContentToNode:null,enableInheritAncestorLineStyle:!0,selectTranslateStep:3,selectTranslateLimit:20,enableFreeDrag:!1,autoMoveWhenMouseInEdgeOnDrag:!0,dragMultiNodeRectConfig:{width:40,height:20,fill:"rgb(94, 200, 248)"},dragPlaceholderRectFill:"rgb(94, 200, 248)",dragPlaceholderLineConfig:{color:"rgb(94, 200, 248)",width:2},dragOpacityConfig:{cloneNodeOpacity:.5,beingDragNodeOpacity:.3},handleDragCloneNode:null,beforeDragEnd:null,beforeDragStart:null,watermarkConfig:{onlyExport:!1,text:"",lineSpacing:100,textSpacing:100,angle:30,textStyle:{color:"#999",opacity:.5,fontSize:14},belowNode:!1},exportPaddingX:10,exportPaddingY:10,resetCss:` +)\r`,"g")).filter(f=>!!f);c.length>1&&t?t().then(()=>{this.mindMap.execCommand("INSERT_MULTI_CHILD_NODE",[],c.map(f=>({data:{text:f},children:[]})))}).catch(()=>{this.mindMap.execCommand("INSERT_CHILD_NODE",!1,[],{text:o})}):this.mindMap.execCommand("INSERT_CHILD_NODE",!1,[],{text:o})}}if(l&&(!o||!s))try{let h=null;r&&typeof r=="function"?h=await r(l):h=await z2(l),this.activeNodeList.length>0&&this.activeNodeList.forEach(d=>{this.mindMap.execCommand("SET_NODE_IMAGE",d,{url:h.url,title:"",width:h.size.width,height:h.size.height})})}catch(h){e(Ti.LOAD_CLIPBOARD_IMAGE_ERROR,h)}}catch(a){e(Ti.READ_CLIPBOARD_ERROR,a)}else this.beingCopyData&&this.mindMap.execCommand("PASTE_NODE",this.beingCopyData)}insertBefore(e,t){this.insertTo(e,t,"before")}insertAfter(e,t){this.insertTo(e,t,"after")}insertTo(e,t,r="before"){let n=Ot(e);n=n.filter(s=>!s.isRoot),r==="after"&&n.reverse(),n.forEach(s=>{let a=s.parent,o=a.children,l=ze(s,o);if(l===-1)return;o.splice(l,1),a.nodeData.children.splice(l,1);let h=t.parent,d=h.children,c=ze(t,d);c!==-1&&(r==="after"&&c++,d.splice(c,0,s),h.nodeData.children.splice(c,0,s.nodeData))}),this.mindMap.render()}removeNode(e=[]){if(e=Ot(e),this.activeNodeList.length<=0&&e.length<=0)return;let t=null,r=e.length>0,n=r?e:this.activeNodeList,s=n.find(a=>a.isRoot);if(s)this.clearActiveNodeList(),s.children=[],s.nodeData.children=[];else{t=this.getNextActiveNode(n);for(let a=0;a0?e:this.activeNodeList;r=r.filter(s=>!s.isRoot);let n=this.getNextActiveNode(r);for(let s=0;s0?t=n[s-1]:t=r.parent}return t}copyNode(){if(this.activeNodeList.length<=0)return null;let e=ds(this.activeNodeList);return e=Go(e),e.map(t=>ls({},t,!0))}cutNode(e){if(this.activeNodeList.length<=0)return;let t=ds(this.activeNodeList).filter(n=>!n.isRoot);t=Go(t);let r=t.map(n=>ls({},n,!0));t.forEach(n=>{eh(n)}),this.clearActiveNodeList(),this.mindMap.render(),e&&typeof e=="function"&&e(r)}moveNodeTo(e,t){let r=Ot(e);r=r.filter(n=>!n.isRoot),r.forEach(n=>{this.removeNodeFromActiveList(n),eh(n),t.setData({expand:!0}),t.nodeData.children.push(n.nodeData)}),this.emitNodeActiveEvent(),this.mindMap.render()}pasteNode(e){e=Ot(e),this.mindMap.execCommand("INSERT_MULTI_CHILD_NODE",[],e)}setNodeStyle(e,t,r){let n={[t]:r};this.setNodeDataRender(e,n),Fo.includes(t)&&(e.parent||e).renderLine(!0)}setNodeStyles(e,t){let r={...t};this.setNodeDataRender(e,r);let n=Object.keys(t),s=!1;n.forEach(a=>{Fo.includes(a)&&(s=!0)}),s&&(e.parent||e).renderLine(!0)}setNodeActive(e,t){this.mindMap.execCommand("SET_NODE_DATA",e,{isActive:t}),e.updateNodeByActive(t)}setNodeExpand(e,t){this.mindMap.execCommand("SET_NODE_DATA",e,{expand:t}),this.mindMap.render()}expandAllNode(e=""){if(!this.renderTree)return;let t=(r,n)=>{!n&&r.data.uid===e&&(n=!0),n&&!r.data.expand&&(r.data.expand=!0),r.children&&r.children.length>0&&r.children.forEach(s=>{t(s,n)})};t(this.renderTree,!e),this.mindMap.render()}unexpandAllNode(e=!0,t=""){if(!this.renderTree)return;let r=(n,s,a)=>{!a&&n.data.uid===t&&(a=!0),a&&!s&&n.children&&n.children.length>0&&(n.data.expand=!1),n.children&&n.children.length>0&&n.children.forEach(o=>{r(o,!1,a)})};r(this.renderTree,!0,!t),this.mindMap.render(()=>{e&&this.setRootNodeCenter()})}expandToLevel(e){this.renderTree&&(ue(this.renderTree,null,(t,r,n,s)=>{s0&&(t.data.expand=!1)},null,!0,0,0),this.mindMap.render())}toggleActiveExpand(){this.activeNodeList.forEach(e=>{e.nodeData.children.length<=0||e.isRoot||this.toggleNodeExpand(e)})}toggleNodeExpand(e){this.mindMap.execCommand("SET_NODE_EXPAND",e,!e.getData("expand"))}setNodeText(e,t,r,n){r=r===void 0?e.getData("richText"):r,this.setNodeDataRender(e,{text:t,richText:r,resetRichText:n})}setNodeImage(e,t){let{url:r,title:n,width:s,height:a,custom:o=!1}=t||{url:"",title:"",width:0,height:0,custom:!1};this.setNodeDataRender(e,{image:r,imageTitle:n||"",imageSize:{width:s,height:a,custom:o}})}setNodeIcon(e,t){this.setNodeDataRender(e,{icon:t})}setNodeHyperlink(e,t,r=""){this.setNodeDataRender(e,{hyperlink:t,hyperlinkTitle:r})}setNodeNote(e,t){this.setNodeDataRender(e,{note:t})}setNodeAttachment(e,t,r=""){this.setNodeDataRender(e,{attachmentUrl:t,attachmentName:r})}setNodeTag(e,t){this.setNodeDataRender(e,{tag:t})}insertFormula(e,t=[]){if(!this.hasRichTextPlugin()||!this.mindMap.formula)return;t=Ot(t),(t.length>0?t:this.activeNodeList).forEach(n=>{this.mindMap.formula.insertFormulaToNode(n,e)})}addGeneralization(e,t=!0){if(this.activeNodeList.length<=0)return;let r=this.activeNodeList.filter(d=>!d.isRoot&&!d.isGeneralization&&!d.checkHasSelfGeneralization()),n=H2(r);if(n.length<=0)return;let s=this.hasRichTextPlugin(),{focusNewNode:a,inserting:o}=this.getNewNodeBehavior(t,n.length>1),l=!1,h=e&&e.richText;n.forEach(d=>{let c={inserting:o,...e||{text:this.mindMap.opt.defaultGeneralizationText},range:d.range||null,uid:Tt(),richText:s,isActive:a};s&&!h&&(c.resetRichText=s);let f=d.node.getData("generalization");if(f=f?Array.isArray(f)?f:[f]:[],d.range){if(!!f.find(g=>g.range&&g.range[0]===d.range[0]&&g.range[1]===d.range[1]))return;f.push(c)}else f.push(c);l=!0,this.mindMap.execCommand("SET_NODE_DATA",d.node,{generalization:f}),d.node.setData({expand:!0})}),l&&(a&&this.clearActiveNodeList(),this.mindMap.render(()=>{this.mindMap.render()}))}removeGeneralization(){this.activeNodeList.length<=0||(this.activeNodeList.forEach(e=>{e.checkHasGeneralization()&&this.mindMap.execCommand("SET_NODE_DATA",e,{generalization:null})}),this.mindMap.render(),this.closeHighlightNode())}setNodeCustomPosition(e,t=void 0,r=void 0){[e].forEach(s=>{this.mindMap.execCommand("SET_NODE_DATA",s,{customLeft:t,customTop:r})})}resetLayout(){ue(this.root,null,e=>{e.customLeft=void 0,e.customTop=void 0,this.mindMap.execCommand("SET_NODE_DATA",e,{customLeft:void 0,customTop:void 0}),this.mindMap.render()},null,!0,0,0)}setNodeShape(e,t){if(!t||!Q2.includes(t))return;[e].forEach(n=>{this.setNodeStyle(n,"shape",t)})}goTargetNode(e,t=()=>{}){let r=typeof e=="string"?e:e.getData("uid");r&&this.expandToNodeUid(r,()=>{let n=this.findNodeByUid(r);n&&(n.active(),this.moveNodeToCenter(n),t(n))})}setNodeData(e,t){Object.keys(t).forEach(r=>{e.nodeData.data[r]=t[r]})}setNodeDataRender(e,t,r=!1){if(this.mindMap.execCommand("SET_NODE_DATA",e,t),F2(t)){this.mindMap.emit("node_tree_render_end");return}this.reRenderNodeCheckChange(e,r)}reRenderNodeCheckChange(e,t){e.reRender()?t||this.mindMap.render():this.mindMap.emit("node_tree_render_end")}moveNodeToCenter(e,t){let{resetScaleOnMoveNodeToCenter:r}=this.mindMap.opt;t!==void 0&&(r=t);let{transform:n,state:s}=this.mindMap.view.getTransformData(),{left:a,top:o,width:l,height:h}=e;r||(a*=n.scaleX,o*=n.scaleY,l*=n.scaleX,h*=n.scaleY);let d=this.mindMap.width/2,c=this.mindMap.height/2,f=a+l/2,m=o+h/2,g=d-s.x,x=c-s.y,y=g-f,b=x-m;this.mindMap.view.translateX(y),this.mindMap.view.translateY(b),r&&this.mindMap.view.setScale(1)}setRootNodeCenter(){this.moveNodeToCenter(this.root)}expandToNodeUid(e,t=()=>{}){if(!this.renderTree){t();return}let r=[],n=!1,s={};Zt(this.renderTree,(o,l)=>{if(o.data.uid===e)return r=l?[...s[l.data.uid],l]:[],"stop";if(us(o.data).forEach(d=>{d.uid===e&&(r=l?[...s[l.data.uid],l,o]:[],n=!0)}),n)return"stop";s[o.data.uid]=l?[...s[l.data.uid],l]:[]});let a=!1;if(r.forEach(o=>{o.data.expand||(a=!0,o.data.expand=!0)}),n){let o=r[r.length-1];o&&ue(o,null,l=>{l.data.expand||(a=!0,l.data.expand=!0)})}a?this.mindMap.render(t):t()}findNodeByUid(e){if(!this.root)return;let t=null;return ue(this.root,null,r=>{if(r.getData("uid")===e)return t=r,!0;let n=!1;if((r._generalizationList||[]).forEach(s=>{s.generalizationNode.getData("uid")===e&&(t=s.generalizationNode,n=!0)}),n)return!0}),t}highlightNode(e,t,r){if(this.isRendering)return;r={stroke:"rgb(94, 200, 248)",fill:"transparent",...r||{}},this.highlightBoxNode?this.highlightBoxNodeStyle&&(this.highlightBoxNodeStyle.stroke!==r.stroke||this.highlightBoxNodeStyle.fill!==r.fill)&&this.highlightBoxNode.stroke({color:r.stroke||"transparent"}).fill({color:r.fill||"transparent"}):this.highlightBoxNode=new ji().stroke({color:r.stroke||"transparent"}).fill({color:r.fill||"transparent"}),this.highlightBoxNodeStyle={...r};let n=1/0,s=1/0,a=-1/0,o=-1/0;t?e.children.slice(t[0],t[1]+1).forEach(h=>{h.lefta&&(a=d),c>o&&(o=c)}):(n=e.left,s=e.top,a=e.left+e.width,o=e.top+e.height),this.highlightBoxNode.plot([[n,s],[a,s],[a,o],[n,o]]),this.mindMap.otherDraw.add(this.highlightBoxNode)}closeHighlightNode(){this.highlightBoxNode&&this.highlightBoxNode.remove()}hasRichTextPlugin(){return!!this.mindMap.richText}},nb=Dp});var $n,ab=T(()=>{qo();$n={default:G0}});var Op,jn,ob=T(()=>{Op={Backspace:8,Tab:9,Enter:13,Shift:16,Control:17,Alt:18,CapsLock:20,Esc:27,Spacebar:32,PageUp:33,PageDown:34,End:35,Home:36,Insert:45,Left:37,Up:38,Right:39,Down:40,Del:46,NumLock:144,Cmd:91,CmdFF:224,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"`":192,"=":187,"-":189,"/":191,".":190};for(let i=0;i<=9;i++)Op[i]=i+48;"abcdefghijklmnopqrstuvwxyz".split("").forEach((i,e)=>{Op[i]=e+65});jn=Op});var p0,lb=T(()=>{ob();p0=class{constructor(e){this.opt=e,this.mindMap=e.mindMap,this.shortcutMap={},this.shortcutMapCache={},this.isPause=!1,this.isInSvg=!1,this.isStopCheckInSvg=!1,this.defaultEnableCheck=this.defaultEnableCheck.bind(this),this.bindEvent()}extendKeyMap(e,t){jn[e]=t}removeKeyMap(e){typeof jn[e]<"u"&&delete jn[e]}pause(){this.isPause=!0}recovery(){this.isPause=!1}save(){Object.keys(this.shortcutMapCache).length>0||(this.shortcutMapCache=this.shortcutMap,this.shortcutMap={})}restore(){Object.keys(this.shortcutMapCache).length<=0||(this.shortcutMap=this.shortcutMapCache,this.shortcutMapCache={})}stopCheckInSvg(){let{enableShortcutOnlyWhenMouseInSvg:e}=this.mindMap.opt;e&&(this.isStopCheckInSvg=!0)}recoveryCheckInSvg(){let{enableShortcutOnlyWhenMouseInSvg:e}=this.mindMap.opt;e&&(this.isStopCheckInSvg=!0)}bindEvent(){this.onKeydown=this.onKeydown.bind(this),this.mindMap.on("svg_mouseenter",()=>{this.isInSvg=!0}),this.mindMap.on("svg_mouseleave",()=>{this.isInSvg=!1}),window.addEventListener("keydown",this.onKeydown),this.mindMap.on("beforeDestroy",()=>{this.unBindEvent()})}unBindEvent(){window.removeEventListener("keydown",this.onKeydown)}defaultEnableCheck(e){let t=e.target;if(t===document.body)return!0;for(let r=0;r{if(this.checkKey(e,a)){if(this.checkKey(e,"Control+v")||(e.stopPropagation(),e.preventDefault()),typeof r=="function"&&r(a,[...this.mindMap.renderer.activeNodeList]))return;this.shortcutMap[a].forEach(o=>{o()})}}))}checkKey(e,t){let r=this.getOriginEventCodeArr(e),n=this.getKeyCodeArr(t);if(r.length!==n.length)return!1;for(let s=0;so===r[s]);if(a===-1)return!1;n.splice(a,1)}return!0}getOriginEventCodeArr(e){let t=[];return(e.ctrlKey||e.metaKey)&&t.push(jn.Control),e.altKey&&t.push(jn.Alt),e.shiftKey&&t.push(jn.Shift),t.includes(e.keyCode)||t.push(e.keyCode),t}hasCombinationKey(e){return e.ctrlKey||e.metaKey||e.altKey||e.shiftKey}getKeyCodeArr(e){let t=e.split(/\s*\+\s*/),r=[];return t.forEach(n=>{r.push(jn[n])}),r}addShortcut(e,t){e.split(/\s*\|\s*/).forEach(r=>{this.shortcutMap[r]?this.shortcutMap[r].push(t):this.shortcutMap[r]=[t]})}removeShortcut(e,t){e.split(/\s*\|\s*/).forEach(r=>{if(this.shortcutMap[r])if(t){let n=this.shortcutMap[r].findIndex(s=>s===t);n!==-1&&this.shortcutMap[r].splice(n,1)}else this.shortcutMap[r]=[],delete this.shortcutMap[r]})}getShortcutFn(e){let t=[];return e.split(/\s*\|\s*/).forEach(r=>{t=this.shortcutMap[r]||[]}),t}}});var db,hb=T(()=>{db={name:"simple-mind-map",version:"0.14.0-fix.1",description:"\u4E00\u4E2A\u7B80\u5355\u7684web\u5728\u7EBF\u601D\u7EF4\u5BFC\u56FE",authors:[{name:"\u8857\u89D2\u5C0F\u6797",email:"1013335014@qq.com"},{name:"\u7406\u60F3\u9752\u5E74\u5B9E\u9A8C\u5BA4",url:"http://lxqnsys.com/"}],types:"./types/index.d.ts",typings:"./types/index.d.ts",license:"MIT",repository:{type:"git",url:"https://github.com/wanglin2/mind-map"},scripts:{lint:"eslint src/",format:"prettier --write .",types:"npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck & node ./bin/createPluginsTypeFiles.js",wsServe:"node ./bin/wsServer.mjs"},module:"index.js",main:"./dist/simpleMindMap.umd.min.js",dependencies:{"@svgdotjs/svg.js":"3.2.0",deepmerge:"^1.5.2",eventemitter3:"^4.0.7",jszip:"^3.10.1",katex:"^0.16.8","mdast-util-from-markdown":"^1.3.0","pdf-lib":"^1.17.1",quill:"^2.0.3",tern:"^0.24.3",uuid:"^9.0.0",ws:"^7.5.9","xml-js":"^1.6.11","y-webrtc":"^10.2.5",yjs:"^13.6.8"},keywords:["javascript","svg","mind-map","mindMap","MindMap"],devDependencies:{eslint:"^8.25.0",prettier:"^2.7.1"}}});var Bp,cb,ub=T(()=>{pe();Ge();hb();Bp=class{constructor(e={}){this.opt=e,this.mindMap=e.mindMap,this.commands={},this.history=[],this.activeHistoryIndex=0,this.registerShortcutKeys(),this.originAddHistory=this.addHistory.bind(this),this.addHistory=Ai(this.addHistory,this.mindMap.opt.addHistoryTime,this),this.isPause=!1}pause(){this.isPause=!0}recovery(){this.isPause=!1}clearHistory(){this.history=[],this.activeHistoryIndex=0,this.mindMap.emit("back_forward",0,0)}registerShortcutKeys(){this.mindMap.keyCommand.addShortcut("Control+z",()=>{this.mindMap.execCommand("BACK")}),this.mindMap.keyCommand.addShortcut("Control+y",()=>{this.mindMap.execCommand("FORWARD")})}exec(e,...t){if(this.commands[e]){if(this.commands[e].forEach(r=>{r(...t)}),this.mindMap.emit("afterExecCommand",e,...t),["BACK","FORWARD","SET_NODE_ACTIVE","CLEAR_ACTIVE_NODE"].includes(e))return;this.addHistory()}}add(e,t){this.commands[e]?this.commands[e].push(t):this.commands[e]=[t]}remove(e,t){if(this.commands[e])if(!t)this.commands[e]=[],delete this.commands[e];else{let r=this.commands[e].find(n=>n===t);r!==-1&&this.commands[e].splice(r,1)}}addHistory(){if(this.mindMap.opt.readonly||this.isPause)return;this.mindMap.emit("beforeAddHistory");let e=this.history.length>0?this.history[this.activeHistoryIndex]:null,t=this.getCopyData(),r=JSON.stringify(t);e&&e===r||(this.emitDataUpdatesEvent(e,r),this.history=this.history.slice(0,this.activeHistoryIndex+1),this.history.push(r),this.history.length>this.mindMap.opt.maxHistoryCount&&this.history.shift(),this.activeHistoryIndex=this.history.length-1,this.mindMap.emit("data_change",t),this.mindMap.emit("back_forward",this.activeHistoryIndex,this.history.length))}back(e=1){if(!this.mindMap.opt.readonly&&this.activeHistoryIndex-e>=0){let t=this.history[this.activeHistoryIndex];this.activeHistoryIndex-=e,this.mindMap.emit("back_forward",this.activeHistoryIndex,this.history.length);let r=this.history[this.activeHistoryIndex],n=JSON.parse(r);return this.emitDataUpdatesEvent(t,r),n}}forward(e=1){if(this.mindMap.opt.readonly)return;let t=this.history.length;if(this.activeHistoryIndex+e<=t-1){let r=this.history[this.activeHistoryIndex];this.activeHistoryIndex+=e,this.mindMap.emit("back_forward",this.activeHistoryIndex,this.history.length);let n=this.history[this.activeHistoryIndex],s=JSON.parse(n);return this.emitDataUpdatesEvent(r,n),s}}getCopyData(){if(!this.mindMap.renderer.renderTree)return null;let e=Tu({},this.mindMap.renderer.renderTree,!0);return e.smmVersion=db.version,e}removeDataUid(e){e=Qt(e);let t=r=>{delete r.data.uid,r.children&&r.children.length>0&&r.children.forEach(n=>{t(n)})};return t(e),e}emitDataUpdatesEvent(e,t){try{let r="data_change_detail";if(this.mindMap.event.listenerCount(r)>0&&e&&t){let s=JSON.parse(e),a=JSON.parse(t),o=Qt(_u(s)),l=Qt(_u(a)),h=[],d=(c,f)=>(c.children&&c.children.length>0&&c.children.forEach((m,g)=>{c.children[g]=typeof m=="string"?f[m]:f[m.data.uid],d(c.children[g],f)}),c);Object.keys(l).forEach(c=>{o[c]?X0(o[c],l[c])||h.push({action:"update",oldData:d(o[c],o),data:d(l[c],l)}):h.push({action:"create",data:d(l[c],l)})}),Object.keys(o).forEach(c=>{l[c]||h.push({action:"delete",data:d(o[c],o)})}),this.mindMap.emit(r,h)}}catch(r){this.mindMap.opt.errorHandler(Ti.DATA_CHANGE_DETAIL_EVENT_ERROR,r)}}},cb=Bp});var Pp,fb,mb=T(()=>{pe();Pp=class{constructor(){this.has={},this.queue=[],this.nextTick=L2(this.flush,this)}push(e,t){if(this.has[e]){this.replaceTask(e,t);return}this.has[e]=!0,this.queue.push({name:e,fn:t}),this.nextTick()}replaceTask(e,t){let r=this.queue.findIndex(n=>n.name===e);r!==-1&&(this.queue[r]={name:e,fn:t})}flush(){let e=this.queue.slice(0);this.queue=[],e.forEach(({name:t,fn:r})=>{this.has[t]=!1,r()})}},fb=Pp});var Fp,pb=T(()=>{Ge();Fp={el:null,data:null,viewData:null,readonly:!1,layout:C.LAYOUT.LOGICAL_STRUCTURE,fishboneDeg:45,theme:"default",themeConfig:{},scaleRatio:.2,translateRatio:1,minZoomRatio:20,maxZoomRatio:400,customCheckIsTouchPad:null,mouseScaleCenterUseMousePosition:!0,maxTag:5,expandBtnSize:20,imgTextMargin:5,textContentMargin:2,customNoteContentShow:null,textAutoWrapWidth:500,customHandleMousewheel:null,mousewheelAction:C.MOUSE_WHEEL_ACTION.MOVE,mousewheelMoveStep:100,mousewheelZoomActionReverse:!0,defaultInsertSecondLevelNodeText:"\u4E8C\u7EA7\u8282\u70B9",defaultInsertBelowSecondLevelNodeText:"\u5206\u652F\u4E3B\u9898",expandBtnStyle:{color:"#808080",fill:"#fff",fontSize:13,strokeColor:"#333333"},expandBtnIcon:{open:"",close:""},expandBtnNumHandler:null,isShowExpandNum:!0,enableShortcutOnlyWhenMouseInSvg:!0,customCheckEnableShortcut:null,initRootNodePosition:null,nodeTextEditZIndex:3e3,nodeNoteTooltipZIndex:3e3,isEndNodeTextEditOnClickOuter:!0,maxHistoryCount:500,alwaysShowExpandBtn:!1,notShowExpandBtn:!1,iconList:[],maxNodeCacheCount:1e3,fitPadding:50,enableCtrlKeyNodeSelection:!0,useLeftKeySelectionRightKeyDrag:!1,beforeTextEdit:null,isUseCustomNodeContent:!1,customCreateNodeContent:null,customInnerElsAppendTo:null,enableAutoEnterTextEditWhenKeydown:!1,autoEmptyTextWhenKeydownEnterEdit:!1,customHandleClipboardText:null,disableMouseWheelZoom:!1,errorHandler:(i,e)=>{console.error(i,e)},enableDblclickBackToRootNode:!1,hoverRectColor:"rgb(94, 200, 248)",hoverRectPadding:2,selectTextOnEnterEditText:!1,deleteNodeActive:!0,fit:!1,tagsColorMap:{},cooperateStyle:{avatarSize:22,fontSize:12},onlyOneEnableActiveNodeOnCooperate:!1,defaultGeneralizationText:"\u6982\u8981",handleIsSplitByWrapOnPasteCreateNewNode:null,addHistoryTime:100,isDisableDrag:!1,createNewNodeBehavior:C.CREATE_NEW_NODE_BEHAVIOR.DEFAULT,defaultNodeImage:"",isLimitMindMapInCanvas:!1,handleNodePasteImg:null,customCreateNodePath:null,customCreateNodePolygon:null,customTransformNodeLinePath:null,beforeShortcutRun:null,resetScaleOnMoveNodeToCenter:!1,createNodePrefixContent:null,createNodePostfixContent:null,disabledClipboard:!1,customHyperlinkJump:null,openPerformance:!1,performanceConfig:{time:250,padding:100,removeNodeWhenOutCanvas:!0},emptyTextMeasureHeightText:"abc123\u6211\u548C\u4F60",openRealtimeRenderOnNodeTextEdit:!1,mousedownEventPreventDefault:!1,onlyPasteTextWhenHasImgAndText:!0,enableDragModifyNodeWidth:!0,minNodeTextModifyWidth:20,maxNodeTextModifyWidth:-1,customHandleLine:null,addHistoryOnInit:!0,noteIcon:{icon:"",style:{}},hyperlinkIcon:{icon:"",style:{}},attachmentIcon:{icon:"",style:{}},isShowCreateChildBtnIcon:!0,quickCreateChildBtnIcon:{icon:"",style:{}},customQuickCreateChildBtnClick:null,addCustomContentToNode:null,enableInheritAncestorLineStyle:!0,selectTranslateStep:3,selectTranslateLimit:20,enableFreeDrag:!1,autoMoveWhenMouseInEdgeOnDrag:!0,dragMultiNodeRectConfig:{width:40,height:20,fill:"rgb(94, 200, 248)"},dragPlaceholderRectFill:"rgb(94, 200, 248)",dragPlaceholderLineConfig:{color:"rgb(94, 200, 248)",width:2},dragOpacityConfig:{cloneNodeOpacity:.5,beingDragNodeOpacity:.3},handleDragCloneNode:null,beforeDragEnd:null,beforeDragStart:null,watermarkConfig:{onlyExport:!1,text:"",lineSpacing:100,textSpacing:100,angle:30,textStyle:{color:"#999",opacity:.5,fontSize:14},belowNode:!1},exportPaddingX:10,exportPaddingY:10,resetCss:` * { margin: 0; padding: 0; box-sizing: border-box; } - `,minExportImgCanvasScale:2,addContentToHeader:null,addContentToFooter:null,handleBeingExportSvg:null,maxCanvasSize:16384,defaultAssociativeLineText:"\u5173\u8054",associativeLineIsAlwaysAboveNode:!0,associativeLineInitPointsPosition:{from:"",to:""},enableAdjustAssociativeLinePoints:!0,beforeAssociativeLineConnection:null,disableTouchZoom:!1,minTouchZoomScale:20,maxTouchZoomScale:-1,isLimitMindMapInCanvasWhenHasScrollbar:!0,isOnlySearchCurrentRenderNodes:!1,beforeCooperateUpdate:null,rainbowLinesConfig:{open:!1,colorsList:[]},demonstrateConfig:null,enableEditFormulaInRichTextEdit:!0,katexFontPath:"https://unpkg.com/katex@0.16.11/dist/",getKatexOutputType:null,transformRichTextOnEnterEdit:null,beforeHideRichTextEdit:null,outerFramePaddingX:10,outerFramePaddingY:10,defaultOuterFrameText:"\u5916\u6846",onlyPainterNodeCustomStyles:!1,beforeDeleteNodeImg:null,imgResizeBtnSize:25,minImgResizeWidth:50,minImgResizeHeight:50,maxImgResizeWidthInheritTheme:!1,maxImgResizeWidth:1/0,maxImgResizeHeight:1/0,customDeleteBtnInnerHTML:"",customResizeBtnInnerHTML:""}});var ib={};tt(ib,{default:()=>HP});var Cp,_t,_p,HP,rb=T(()=>{Tv();kv();Gv();Cp=pt(O0());Vv();Y0();Yv();Qv();eb();$e();yt();pe();Ro();tb();_t=class i{constructor(e={}){if(i.instanceCount++,this.opt=this.handleOpt((0,Cp.default)(kp,e)),this.opt.data=this.handleData(this.opt.data),this.el=this.opt.el,!this.el)throw new Error("\u7F3A\u5C11\u5BB9\u5668\u5143\u7D20el");this.getElRectInfo(),this.initWidth=this.width,this.initHeight=this.height,this.cssEl=null,this.cssTextMap={},this.nodeInnerPrefixList=[],this.nodeInnerPostfixList=[],this.editNodeClassList=[],this.extendShapeList=[],this.initContainer(),this.initTheme(),this.initCache(),i.pluginList.filter(t=>t.preload).forEach(t=>{this.initPlugin(t)}),this.event=new Av({mindMap:this}),this.keyCommand=new h0({mindMap:this}),this.command=new Zv({mindMap:this}),this.renderer=new jv({mindMap:this}),this.view=new Mv({mindMap:this}),this.batchExecution=new Jv,i.pluginList.filter(t=>!t.preload).forEach(t=>{this.initPlugin(t)}),this.addCss(),this.render(this.opt.fit?()=>this.view.fit():()=>{}),this.opt.addHistoryOnInit&&this.opt.data&&this.command.addHistory()}handleOpt(e){return $c.includes(e.layout)||(e.layout=k.LAYOUT.LOGICAL_STRUCTURE),e.theme=e.theme&&jn[e.theme]?e.theme:"default",e}handleData(e){return Yt(e)||Object.keys(e).length<=0?null:(e=Kt(e||{}),e.data&&!e.data.expand&&(e.data.expand=!0),cs([e],!1,null,!0),e)}initContainer(){let{associativeLineIsAlwaysAboveNode:e}=this.opt;this.el.classList.add("smm-mind-map-container");let t=()=>{this.associativeLineDraw=this.draw.group(),this.associativeLineDraw.addClass("smm-associative-line-container")};this.svg=Ae().addTo(this.el).size(this.width,this.height),this.draw=this.svg.group(),this.draw.addClass("smm-container"),this.lineDraw=this.draw.group(),this.lineDraw.addClass("smm-line-container"),e||t(),this.nodeDraw=this.draw.group(),this.nodeDraw.addClass("smm-node-container"),e&&t(),this.otherDraw=this.draw.group(),this.otherDraw.addClass("smm-other-container")}clearDraw(){this.lineDraw.clear(),this.associativeLineDraw.clear(),this.nodeDraw.clear(),this.otherDraw.clear()}appendCss(e,t){this.cssTextMap[e]=t,this.removeCss(),this.addCss()}removeAppendCss(e){this.cssTextMap[e]&&(delete this.cssTextMap[e],this.removeCss(),this.addCss())}joinCss(){return C3+Object.keys(this.cssTextMap).map(e=>this.cssTextMap[e]).join(` -`)}addCss(){this.cssEl=document.createElement("style"),this.cssEl.type="text/css",this.cssEl.innerHTML=this.joinCss(),document.head.appendChild(this.cssEl)}removeCss(){this.cssEl&&document.head.removeChild(this.cssEl)}checkEditNodeClassIndex(e){return this.editNodeClassList.findIndex(t=>t===e)}addEditNodeClass(e){this.checkEditNodeClassIndex(e)===-1&&this.editNodeClassList.push(e)}deleteEditNodeClass(e){let t=this.checkEditNodeClassIndex(e);t!==-1&&this.editNodeClassList.splice(t,1)}render(e,t=""){this.initTheme(),this.renderer.render(e,t)}reRender(e,t=""){this.renderer.reRender=!0,this.renderer.clearCache(),this.clearDraw(),this.render(e,t)}getElRectInfo(){if(this.elRect=this.el.getBoundingClientRect(),this.width=this.elRect.width,this.height=this.elRect.height,this.width<=0||this.height<=0)throw new Error("\u5BB9\u5668\u5143\u7D20el\u7684\u5BBD\u9AD8\u4E0D\u80FD\u4E3A0")}resize(){let e=this.width,t=this.height;this.getElRectInfo(),this.svg.size(this.width,this.height),(e!==this.width||t!==this.height)&&(this.demonstrate?this.demonstrate.isInDemonstrate||this.render():this.render()),this.emit("resize")}on(e,t){this.event.on(e,t)}emit(e,...t){this.event.emit(e,...t)}off(e,t){this.event.off(e,t)}initCache(){this.commonCaches={measureCustomNodeContentSizeEl:null,measureRichtextNodeTextSizeEl:null}}initTheme(){this.themeConfig=wu(jn[this.opt.theme]||jn.default,this.opt.themeConfig),qo.setBackgroundStyle(this.el,this.themeConfig)}setTheme(e,t=!1){this.execCommand("CLEAR_ACTIVE_NODE"),this.opt.theme=e,t||this.render(null,k.CHANGE_THEME),this.emit("view_theme_change",e)}getTheme(){return this.opt.theme}setThemeConfig(e,t=!1){let r=N2(this.themeConfig,e);if(this.opt.themeConfig=e,!t){let n=u2(r);this.render(null,n?"":k.CHANGE_THEME)}}getCustomThemeConfig(){return this.opt.themeConfig}getThemeConfig(e){return e===void 0?this.themeConfig:this.themeConfig[e]}getConfig(e){return e===void 0?this.opt:this.opt[e]}updateConfig(e={}){this.emit("before_update_config",this.opt);let t={...this.opt};this.opt=this.handleOpt(Cp.default.all([kp,this.opt,e])),this.emit("after_update_config",this.opt,t)}getLayout(){return this.opt.layout}setLayout(e,t=!1){$c.includes(e)||(e=k.LAYOUT.LOGICAL_STRUCTURE),this.opt.layout=e,this.view.reset(),this.renderer.setLayout(),t||this.render(null,k.CHANGE_LAYOUT),this.emit("layout_change",e)}execCommand(...e){this.command.exec(...e)}updateData(e){e=this.handleData(e),this.emit("before_update_data",e),this.renderer.setData(e),this.render(),this.command.addHistory(),this.emit("update_data",e)}setData(e){e=this.handleData(e),this.emit("before_set_data",e),this.opt.data=e,this.execCommand("CLEAR_ACTIVE_NODE"),this.command.clearHistory(),this.command.addHistory(),this.renderer.setData(e),this.reRender(),this.emit("set_data",e)}setFullData(e){e.root&&this.setData(e.root),e.layout&&this.setLayout(e.layout),e.theme&&(e.theme.template&&this.setTheme(e.theme.template),e.theme.config&&this.setThemeConfig(e.theme.config)),e.view&&this.view.setTransformData(e.view)}getData(e){let t=this.command.getCopyData(),r={};return e?r={layout:this.getLayout(),root:t,theme:{template:this.getTheme(),config:this.getCustomThemeConfig()},view:this.view.getTransformData()}:r=t,Kt(r)}async export(...e){try{if(!this.doExport)throw new Error("\u8BF7\u6CE8\u518CExport\u63D2\u4EF6\uFF01");return await this.doExport.export(...e)}catch(t){this.opt.errorHandler(Mi.EXPORT_ERROR,t)}}toPos(e,t){return{x:e-this.elRect.left,y:t-this.elRect.top}}setMode(e){if(![k.MODE.READONLY,k.MODE.EDIT].includes(e))return;let t=e===k.MODE.READONLY;t!==this.opt.readonly&&(t&&(this.renderer.textEdit.isShowTextEdit()&&(this.renderer.textEdit.hideEditTextBox(),this.command.originAddHistory()),this.execCommand("CLEAR_ACTIVE_NODE")),this.opt.readonly=t,!t&&this.command.history.length<=0&&this.command.originAddHistory(),this.emit("mode_change",e))}getSvgData({paddingX:e=0,paddingY:t=0,ignoreWatermark:r=!1,addContentToHeader:n,addContentToFooter:s,node:a}={}){let{watermarkConfig:o,openPerformance:l}=this.opt;l&&this.renderer.forceLoadNode(a);let{cssTextList:h,header:d,headerHeight:c,footer:f,footerHeight:m}=R2({addContentToHeader:n,addContentToFooter:s}),g=this.svg,x=this.draw,y=g.width(),b=g.height(),S=x.transform(),A=this.elRect;x.scale(1/S.scaleX,1/S.scaleY);let C=x.rbox(),I=null;a&&(I=bu(a,C.x,C.y,e,t));let O=0;C.width+=e*2,C.height+=t*2+O+c+m,x.translate(e,t),g.size(C.width,C.height),x.translate(-C.x+A.left,-C.y+A.top);let q=g.clone(),P=this.watermark&&this.watermark.hasWatermark();if(!r&&P){this.watermark.isInExport=!0;let{onlyExport:re}=o;C.width>y||C.height>b?(this.width=C.width,this.height=C.height,this.watermark.onResize(),q=g.clone(),this.width=y,this.height=b,this.watermark.onResize()):re&&(this.watermark.onResize(),q=g.clone()),re&&this.watermark.clear(),this.watermark.isInExport=!1}[this.joinCss(),...h].forEach(re=>{q.add(Ae(``))}),d&&c>0&&(q.findOne(".smm-container").translate(0,c),d.width(C.width),d.y(t),q.add(d,0)),f&&m>0&&(f.width(C.width),f.y(C.height-t-m),q.add(f));let W=g.find("defs"),ne=q.find("defs");return W.forEach((re,oe)=>{let ke=ne[oe];if(!ke)return;let Q=re.children(),Y=ke.children();for(let le=0;ler.name===e.name)||this.extendShapeList.push(e)}removeShape(e){let t=this.extendShapeList.findIndex(r=>r.name===e);t!==-1&&this.extendShapeList.splice(t,1)}getSvgObjects(){return{SVG:Ae,G:_e,Rect:Ve}}addPlugin(e,t){i.hasPlugin(e)===-1&&i.usePlugin(e,t),this.initPlugin(e)}removePlugin(e){let t=i.hasPlugin(e);t!==-1&&(i.pluginList.splice(t,1),this[e.instanceName]&&(this[e.instanceName].beforePluginRemove&&this[e.instanceName].beforePluginRemove(),delete this[e.instanceName]))}initPlugin(e){this[e.instanceName]||(this[e.instanceName]=new e({mindMap:this,pluginOpt:e.pluginOpt}))}destroy(){this.emit("beforeDestroy"),this.renderer.textEdit.hideEditTextBox(),this.renderer.textEdit.removeTextEditEl(),[...i.pluginList].forEach(e=>{this[e.instanceName]&&this[e.instanceName].beforePluginDestroy&&this[e.instanceName].beforePluginDestroy(),this[e.instanceName]=null}),this.event.unbind(),this.svg.remove(),qo.removeBackgroundStyle(this.el),this.el.classList.remove("smm-mind-map-container"),this.el.innerHTML="",this.el=null,this.removeCss(),i.instanceCount--}},_p=[];_t.extendNodeDataNoStylePropList=(i=[])=>{_p.push(...i),$s.push(...i)};_t.resetNodeDataNoStylePropList=()=>{_p.forEach(i=>{let e=$s.findIndex(t=>t===i);e!==-1&&$s.splice(e,1)}),_p=[]};_t.pluginList=[];_t.usePlugin=(i,e={})=>(_t.hasPlugin(i)!==-1||(i.pluginOpt=e,_t.pluginList.push(i)),_t);_t.hasPlugin=i=>_t.pluginList.findIndex(e=>e===i);_t.instanceCount=0;_t.defineTheme=(i,e={})=>{if(jn[i])return new Error("\u8BE5\u4E3B\u9898\u540D\u79F0\u5DF2\u5B58\u5728");jn[i]=wu(B0,e)};_t.removeTheme=i=>{jn[i]&&(jn[i]=null)};HP=_t});var Ic=globalThis.__LEPTOS_TIPTAP_BRIDGE__;if(Ic==null||Ic.modules==null)throw new Error("leptos-tiptap bridge bindings are unavailable");var j=Ic.modules["@tiptap/core"];if(j==null)throw new Error('leptos-tiptap bridge module "@tiptap/core" is unavailable');var tq=j.CommandManager,iq=j.Editor,rq=j.Extension,nq=j.InputRule,sq=j.Mark,f3=j.Node,aq=j.NodePos,oq=j.NodeView,lq=j.PasteRule,hq=j.Tracker,dq=j.callOrReturn,cq=j.canInsertNode,uq=j.combineTransactionSteps,fq=j.createChainableState,mq=j.createDocument,pq=j.createNodeFromContent,gq=j.createStyleTag,xq=j.defaultBlockAt,yq=j.deleteProps,vq=j.elementFromString,bq=j.escapeForRegEx,wq=j.extensions,Mq=j.findChildren,Tq=j.findChildrenInRange,Nq=j.findDuplicates,Eq=j.findParentNode,Sq=j.findParentNodeClosestToPos,Aq=j.fromString,kq=j.generateHTML,Cq=j.generateJSON,_q=j.generateText,Lq=j.getAttributes,Iq=j.getAttributesFromExtensions,zq=j.getChangedRanges,Rq=j.getDebugJSON,Dq=j.getExtensionField,Oq=j.getHTMLFromFragment,Bq=j.getMarkAttributes,Pq=j.getMarkRange,Fq=j.getMarkType,qq=j.getMarksBetween,Hq=j.getNodeAtPosition,Uq=j.getNodeAttributes,$q=j.getNodeType,jq=j.getRenderedAttributes,Gq=j.getSchema,Vq=j.getSchemaByResolvedExtensions,Wq=j.getSchemaTypeByName,Yq=j.getSchemaTypeNameByName,Xq=j.getSplittedAttributes,Kq=j.getText,Zq=j.getTextBetween,Qq=j.getTextContentFromNodes,Jq=j.getTextSerializersFromSchema,eH=j.injectExtensionAttributesToParseRule,tH=j.inputRulesPlugin,iH=j.isActive,rH=j.isAtEndOfNode,nH=j.isAtStartOfNode,sH=j.isEmptyObject,aH=j.isExtensionRulesEnabled,oH=j.isFunction,lH=j.isList,hH=j.isMacOS,dH=j.isMarkActive,cH=j.isNodeActive,uH=j.isNodeEmpty,fH=j.isNodeSelection,mH=j.isNumber,pH=j.isPlainObject,gH=j.isRegExp,xH=j.isSafari,yH=j.isString,vH=j.isTextSelection,bH=j.isiOS,wH=j.markInputRule,MH=j.markPasteRule,m3=j.mergeAttributes,TH=j.mergeDeep,NH=j.minMax,EH=j.nodeInputRule,SH=j.nodePasteRule,AH=j.objectIncludes,kH=j.pasteRulesPlugin,CH=j.posToDOMRect,_H=j.removeDuplicates,LH=j.resolveFocusPosition,IH=j.rewriteUnknownContent,zH=j.selectionToInsertionEnd,RH=j.splitExtensions,DH=j.textInputRule,OH=j.textPasteRule,BH=j.textblockTypeInputRule,PH=j.wrappingInputRule;var p3=f3.create({name:"paragraph",priority:1e3,addOptions(){return{HTMLAttributes:{}}},group:"block",content:"inline*",parseHTML(){return[{tag:"p"}]},renderHTML({HTMLAttributes:i}){return["p",m3(this.options.HTMLAttributes,i),0]},addCommands(){return{setParagraph:()=>({commands:i})=>i.setNode(this.name)}},addKeyboardShortcuts(){return{"Mod-Alt-0":()=>this.editor.commands.setParagraph()}}});var g3="__LEPTOS_TIPTAP_BRIDGE__";function nw(){let i=globalThis,e=i[g3];if(e!=null)return e;let t={modules:{},registerExtension:()=>{throw new Error("leptos-tiptap bridge runtime is not initialized")}};return i[g3]=t,t}function x3(){return nw()}function y3(i){x3().registerExtension(i)}var Dc={};tt(Dc,{applyMetadataOnlyMindmapCommands:()=>M3,applyMindmapCommandsLocally:()=>hw,isLocallyApplicableMindmapCommandSet:()=>lw,isMetadataOnlyMindmapCommandSet:()=>ow});var sw={data:{text:"\u4E2D\u5FC3\u4E3B\u9898"},children:[]},gt=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),nr=i=>JSON.parse(JSON.stringify(i)),b3=i=>{let e=gt(i)?nr(i):nr(sw);return gt(e.data)||(e.data={text:"\u4E2D\u5FC3\u4E3B\u9898"}),Array.isArray(e.children)||(e.children=[]),e},w3=(i,e)=>{if(!gt(i)||!gt(e))return nr(e);let t={...i};return Object.entries(e).forEach(([r,n])=>{if(n===null){delete t[r];return}t[r]=gt(t[r])&>(n)?w3(t[r],n):nr(n)}),t},zc=(i,e,t)=>{let r=e.split(".").filter(Boolean);if(r.length===0)return!1;let n=i;for(let s of r.slice(0,-1))gt(n[s])||(n[s]={}),n=n[s];return n[r[r.length-1]]=nr(t),!0},N0=(i,e)=>{if(!gt(i))return null;let t=gt(i.data)?i.data:null;if(typeof t?.uid=="string"&&t.uid===e)return i;let r=Array.isArray(i.children)?i.children:[];for(let n of r){let s=N0(n,e);if(s)return s}return null},Rc=(i,e)=>{for(let t=0;t({data:{uid:i.uid??`node_${Date.now().toString(36)}`,text:i.text,...typeof i.hyperlink=="string"?{hyperlink:i.hyperlink}:{},...typeof i.note=="string"?{note:i.note}:{},...Array.isArray(i.refs)?{refs:nr(i.refs)}:{}},children:[]}),aw=(i,e)=>{let t=String(e.path||"").trim();if(!t)return!1;if(t.startsWith("root.data.")){let n=t.slice(10),s=gt(i.data)?i.data:i.data={};return zc(s,n,e.value)}if(t.startsWith("nodes.")){let[,n,s,...a]=t.split(".");if(!n||s!=="data"||a.length===0)return!1;let o=N0(i,n);if(!o)return!1;let l=gt(o.data)?o.data:o.data={};return zc(l,a.join("."),e.value)}let r=gt(i.compatPayload)?i.compatPayload:i.compatPayload={};return zc(r,t,e.value)},ow=i=>Array.isArray(i)&&i.every(e=>!gt(e)||typeof e.type!="string"?!1:["setLayout","setTheme","patchView","compatPayloadPatch"].includes(e.type)),lw=i=>Array.isArray(i)&&i.every(e=>!gt(e)||typeof e.type!="string"?!1:["updateText","insertChild","insertSiblingAfter","deleteNode","setLayout","setTheme","patchView","compatPayloadPatch"].includes(e.type)),M3=(i,e)=>{let t=b3(i),r=[],n=0;return e.forEach(s=>{if(s.type==="setLayout"){t.layout=nr(s.layout),n+=1;return}if(s.type==="setTheme"){t.theme=nr(s.theme),s.themeConfig!==void 0&&s.themeConfig!==null&&(t.themeConfig=nr(s.themeConfig)),n+=1;return}if(s.type==="patchView"){t.view=w3(t.view??{},s.patch),n+=1;return}s.type==="compatPayloadPatch"&&(aw(t,s)?n+=1:r.push(`\u65E0\u6CD5\u5E94\u7528 compatPayloadPatch:${s.path}`))}),{applied:n,data:t,errors:r}},hw=(i,e)=>{let t=b3(i),r=[],n=0;return e.forEach(s=>{if(s.type==="setLayout"||s.type==="setTheme"||s.type==="patchView"||s.type==="compatPayloadPatch"){let a=M3(t,[s]);Object.assign(t,a.data),n+=a.applied,r.push(...a.errors);return}if(s.type==="updateText"){let a=N0(t,s.nodeId);if(!a){r.push(`\u672A\u627E\u5230\u8282\u70B9:${s.nodeId}`);return}let o=gt(a.data)?a.data:a.data={};o.text=s.text,n+=1;return}if(s.type==="insertChild"){let a=N0(t,s.parentNodeId);if(!a){r.push(`\u672A\u627E\u5230\u7236\u8282\u70B9:${s.parentNodeId}`);return}(Array.isArray(a.children)?a.children:a.children=[]).push(v3(s.node)),n+=1;return}if(s.type==="insertSiblingAfter"){let a=Rc([t],s.targetNodeId);if(!a){r.push(`\u672A\u627E\u5230\u540C\u7EA7\u8282\u70B9:${s.targetNodeId}`);return}if(a.parentChildren[a.index]===t){r.push("\u6839\u8282\u70B9\u4E0D\u652F\u6301\u63D2\u5165\u540C\u7EA7\u8282\u70B9");return}a.parentChildren.splice(a.index+1,0,v3(s.node)),n+=1;return}if(s.type==="deleteNode"){let a=Rc([t],s.nodeId);if(!a){r.push(`\u672A\u627E\u5230\u5220\u9664\u8282\u70B9:${s.nodeId}`);return}if(a.parentChildren[a.index]===t){r.push("\u6839\u8282\u70B9\u4E0D\u652F\u6301\u5220\u9664");return}a.parentChildren.splice(a.index,1),n+=1}}),{applied:n,data:t,errors:r}};var Dp={};tt(Dp,{MindmapCommandBridgeError:()=>lo,createLeptosMindmapAdapter:()=>rF,createMindmapAdapterProjectionEndpoint:()=>lb,createMindmapCommandApplyEndpoint:()=>zp,executeMindmapCommandApply:()=>hb,executeMindmapCommandApplyAndRefreshProjection:()=>tF,executeMindmapDataPutAndRefreshProjection:()=>iF,isMindmapAdapterProjection:()=>Ip,requestMindmapAdapterProjection:()=>Rp});var Pc={};tt(Pc,{DEFAULT_MINDMAP_LAYOUT:()=>E0,DEFAULT_MINDMAP_THEME:()=>S0,buildMindmapEditorScene:()=>cw,buildMindmapProjection:()=>dw,buildMindmapSimpleMindMapScene:()=>uw,canonicalizeMindmapData:()=>ci,createDefaultMindmapThemeConfig:()=>A0,defaultMindmapData:()=>Us,extractMindmapTitle:()=>Bc,normalizeMindmapData:()=>Oc,summarizeMindmapProjectionNodes:()=>T3});var Us={data:{text:"\u4E2D\u5FC3\u4E3B\u9898"},children:[]},E0="logicalStructure",S0="default",A0=()=>({lineColor:"#7aa2ff",lineStyle:"curve",rootLineKeepSameInCurve:!0,rootLineStartPositionKeepSameInCurve:!0,generalizationLineColor:"#ef6a5b",backgroundColor:"#f6f8fc",root:{fillColor:"#e25563",color:"#ffffff",fontWeight:"bold",borderColor:"transparent",borderWidth:0,borderRadius:8},second:{fillColor:"#4f7df3",color:"#ffffff",borderColor:"transparent",borderWidth:0,borderRadius:8},node:{fillColor:"transparent",color:"#315aa9",borderColor:"transparent",borderWidth:0},generalization:{fillColor:"#ffffff",color:"#ef6a5b",borderColor:"#ef6a5b",borderWidth:1,borderRadius:8}}),Hs=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),Oc=i=>{if(!i||typeof i!="object")return Us;let e=i.root??i,t=r=>{if(!Hs(r))return;let n=Hs(r.data)?r.data:{};r.data=n;let s=n.text;n.text=typeof s=="string"?s:String(s??"");let a=n.generalization,o=l=>{if(!Hs(l))return;let h=l.text;l.text=typeof h=="string"?h:String(h??"")};Array.isArray(a)?a.forEach(o):o(a),Array.isArray(r.children)&&r.children.forEach(t)};return t(e),i},ci=i=>{let e=Oc(i)??Us,t=e&&typeof e=="object"&&"root"in e?e.root:e;return Oc(t)??Us},Bc=i=>{let t=ci(i)?.data?.text;return typeof t=="string"&&t.trim()?t.trim():"\u672A\u547D\u540D\u5BFC\u56FE"},T3=i=>{let t=[{node:ci(i),depth:0}],r=[];for(;t.length>0;){let n=t.shift();if(!n)break;let s=n.node?.data?.uid,a=n.node?.data?.text,o=Array.isArray(n.node?.children)?n.node.children:[];r.push({uid:typeof s=="string"&&s.trim()?s:`depth:${n.depth}:index:${r.length}`,text:typeof a=="string"&&a.trim()?a.trim():"\u672A\u547D\u540D\u8282\u70B9",depth:n.depth,childCount:o.length}),o.forEach(l=>{t.push({node:ci(l),depth:n.depth+1})})}return r},dw=i=>{let e=ci(i.data),t=T3(e),r=Hs(i.meta)?i.meta:null,n=t[0]?.uid??null;return{schema:"mnote.mindmap_projection.v1",projectionId:`mindmap_projection:${i.documentId}:${i.mindmapId}`,projection:"mindmap_subtree",source:i.source??"rust-kernel",owner:i.owner??"rust-kernel",documentId:i.documentId,mindmapId:i.mindmapId,rootNodeId:n,title:Bc(e),nodeCount:t.length,nodes:t,data:e,meta:r}},cw=i=>{let e=ci(i.data),t=Hs(i.meta)?i.meta:null,r=[],n=[],s=(l,h,d)=>{let c=l?.data?.uid,f=l?.data?.text,m=typeof c=="string"&&c.trim()?c.trim():`depth:${h}:index:${r.length}`,g=Array.isArray(l?.children)?l.children:[];r.push({id:m,uid:m,text:typeof f=="string"&&f.trim()?f.trim():"\u672A\u547D\u540D\u8282\u70B9",depth:h,childCount:g.length,parentId:d}),d&&n.push({id:`${d}->${m}`,source:d,target:m}),g.forEach(x=>{s(ci(x),h+1,m)})};s(e,0,null);let a=r[0]?.id??null,o=i.rootNodeId&&i.rootNodeId.trim()?i.rootNodeId.trim():a;return{schema:"mnote.mindmap_editor_scene.v1",source:i.source??"rust-kernel",owner:i.owner??"rust-kernel",documentId:i.documentId,mindmapId:i.mindmapId,rootNodeId:o,title:Bc(e),nodes:r,edges:n,capabilities:{canEditText:!0,canAddChild:!0,canAddSiblingAfter:!0,canDeleteNode:!0},data:e,meta:t}},uw=i=>{let e=ci(i.data),t=Hs(i.meta)?i.meta:null,r=e?.data?.uid,n=typeof r=="string"&&r.trim()?r.trim():"root";return e.data.uid||(e.data.uid=n),{schema:"mnote.mindmap.simple_mind_map_scene.v1",runtime:"simple-mind-map",documentId:i.documentId,mindmapId:i.mindmapId,rootNodeId:n,root:e,layout:E0,theme:S0,themeConfig:A0(),view:{x:0,y:0,scale:1},config:{},compatPayload:{},kernelRevision:1,source:i.source??"rust-kernel",owner:i.owner??"rust-kernel",meta:t}};var UP=["Drag","KeyboardNavigation","Export","Select","AssociativeLine","Search","OuterFrame"],$P=["Scrollbar","MiniMap","Painter","Formula"],jP=["data_change","view_data_change","node_active","back_forward","scale","translate"],GP=new Set(["BACK","FORWARD","INSERT_NODE","INSERT_CHILD_NODE","REMOVE_NODE","DELETE_NODE","ADD_GENERALIZATION","ADD_OUTER_FRAME","SET_NOTATION"]),ao=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),nb=(i,e)=>{if(typeof i=="string"&&i.trim())return i.trim();if(ao(i)){let t=i.template;if(typeof t=="string"&&t.trim())return t.trim()}return e},sb=i=>ao(i)?i:{},ab=i=>!ao(i)||Object.keys(i).length===0?A0():i,VP=i=>!ao(i)||!ao(i.state)?null:{...i,state:i.state,transform:ao(i.transform)?i.transform:{}},WP=i=>{let e=ci(i.projection.root??Us),t=sb(i.projection.config),r=sb(i.runtimeOptions);return{...t,...r,el:i.el,data:e,fit:typeof r.fit=="boolean"?r.fit:typeof t.fit=="boolean"?t.fit:!0,layout:nb(i.projection.layout,E0),theme:nb(i.projection.theme,S0),themeConfig:ab(i.projection.themeConfig),viewData:VP(i.projection.view),initRootNodePosition:["center","center"]}},YP={Drag:()=>Promise.resolve().then(()=>(J2(),Q2)),KeyboardNavigation:()=>Promise.resolve().then(()=>(t6(),e6)),Export:()=>Promise.resolve().then(()=>(d6(),h6)),Select:()=>Promise.resolve().then(()=>(u6(),c6)),AssociativeLine:()=>Promise.resolve().then(()=>(b6(),v6)),Search:()=>Promise.resolve().then(()=>(M6(),w6)),OuterFrame:()=>Promise.resolve().then(()=>(k6(),A6)),Scrollbar:()=>Promise.resolve().then(()=>(_6(),C6)),MiniMap:()=>Promise.resolve().then(()=>(I6(),L6)),Painter:()=>Promise.resolve().then(()=>(R6(),z6)),Formula:()=>Promise.resolve().then(()=>(wv(),bv))},XP=()=>[...UP,...$P],KP=async(i=XP())=>{if(typeof window>"u"||typeof document>"u")throw new Error("simple_mind_map_browser_required");let[{default:e},t]=await Promise.all([Promise.resolve().then(()=>(rb(),ib)),Promise.all(i.map(async s=>[s,(await YP[s]()).default]))]),r=e,n=[];return t.forEach(([s,a])=>{if(!a||typeof r.usePlugin!="function")return;typeof r.hasPlugin=="function"&&r.hasPlugin(a)!==-1||r.usePlugin(a),n.push(s)}),{MindMap:r,registeredPlugins:n}},ZP=i=>(e,...t)=>{if(!GP.has(e))return{ok:!1,command:e,error:"unsupported_command"};if(typeof i.execCommand!="function")return{ok:!1,command:e,error:"runtime_unavailable"};try{return{ok:!0,command:e,result:i.execCommand(e,...t)}}catch{return{ok:!1,command:e,error:"command_failed"}}},QP=i=>{let e=[];return jP.forEach(t=>{let r=(...n)=>{let s=t==="data_change"?i.instance.getData?.():t==="view_data_change"?i.instance.getData?.(!0)??i.instance.getData?.():void 0;i.onEvent?.({type:t,args:n,snapshot:s,kernelRevision:i.projection.kernelRevision})};i.instance.on?.(t,r),e.push(()=>i.instance.off?.(t,r))}),()=>e.splice(0).forEach(t=>t())},ob=async i=>{let e=await KP(i.pluginNames),t=ab(i.projection.themeConfig),r=WP({el:i.el,projection:i.projection,runtimeOptions:i.runtimeOptions}),n=new e.MindMap(r);n.setThemeConfig?.(t),i.mode&&n.setMode?.(i.mode);let s=QP({instance:n,projection:i.projection,onEvent:i.onEvent}),a=ZP(n);return{instance:n,execCommand:a,getSnapshot:()=>n.getData?.(!0)??n.getData?.(),destroy:()=>{n.renderer?.textEdit?.hideEditTextBox?.(),s(),n.destroy?.()}}};var lo=class extends Error{constructor(e,t=null){super(e),this.name="MindmapCommandBridgeError",this.code="command_failed",this.status=t}},oo=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),Lp=i=>{if(!oo(i))return"";let e=[i.error,i.message,i.details].map(t=>typeof t=="string"?t.trim():Array.isArray(t)&&t.length>0?t.join("|"):"").find(Boolean);return e?`:${e}`:""},JP=async i=>{if(typeof i.text!="function"){let t=await i.json().catch(()=>null);return Lp(t)}let e=await i.text().catch(()=>"");if(!e.trim())return"";try{return Lp(JSON.parse(e))||`:${e.trim()}`}catch{return`:${e.trim()}`}},Ip=i=>oo(i)?i.schema==="mnote.mindmap.simple_mind_map_scene.v1"&&i.runtime==="simple-mind-map"&&"root"in i&&typeof i.kernelRevision=="number":!1,lb=(i,e)=>{let t=encodeURIComponent(i),r=encodeURIComponent(e);return`/api/mindmap/${t}/${r}?view=simple_mind_map_scene&queryName=mindmap.simple_mind_map_scene.get`},zp=(i,e)=>{let t=encodeURIComponent(i),r=encodeURIComponent(e);return`/api/mindmap/${t}/${r}`},Rp=async i=>{let e=i.fetcher??fetch,t=i.endpoint??lb(i.documentId,i.mindmapId),r=await e(t,{headers:{Accept:"application/json"}});if(!r.ok)throw new Error(`projection_load_failed:${r.status}`);let n=await r.json(),s=oo(n)&&"result"in n?n.result:n;if(!Ip(s))throw new Error("projection_load_failed:invalid_adapter_projection");return s},eF=i=>{if(!oo(i))return null;let e=[i.kernelRevision,i.projectionRevision,oo(i.result)?i.result.kernelRevision:null,oo(i.result)?i.result.projectionRevision:null];for(let t of e)if(typeof t=="number"&&Number.isFinite(t))return t;return null},hb=async i=>{if(i.commands.length===0)throw new lo("command_failed:empty_commands");let e=i.fetcher??fetch,t=i.endpoint??zp(i.documentId,i.mindmapId),r=JSON.stringify({commandName:"mindmap.command.apply",documentId:i.documentId,mindmapId:i.mindmapId,commands:i.commands,projectionRevision:i.projectionRevision??null}),n=await e(t,{method:"POST",keepalive:r.length<=6e4,headers:{Accept:"application/json","Content-Type":"application/json"},body:r}),s=await n.json().catch(()=>null);if(!n.ok){let a=Lp(s);throw new lo(`command_failed:${n.status}${a}`,n.status)}return{ok:!0,kernelRevision:eF(s),raw:s}},tF=async i=>(await hb(i),Rp({documentId:i.documentId,mindmapId:i.mindmapId,endpoint:i.projectionEndpoint,fetcher:i.fetcher})),iF=async i=>{let e=i.fetcher??fetch,t=i.endpoint??zp(i.documentId,i.mindmapId),r=await e(t,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({data:i.data,createOnly:!1})});if(!r.ok){let n=await JP(r);throw new lo(`command_failed:${r.status}${n}`,r.status)}return await r.json().catch(()=>null),Rp({documentId:i.documentId,mindmapId:i.mindmapId,endpoint:i.projectionEndpoint,fetcher:e})},rF=async i=>{if(!Ip(i.projection))throw new Error("adapter_init_failed:invalid_projection");return ob(i)};var Fp={};tt(Fp,{createCompatPayloadPatch:()=>Tc,createLayoutCommand:()=>Pp,createThemeCommand:()=>Bp,createToolbarKernelCommand:()=>Op,createViewPatchCommand:()=>nF,diffMindmapRuntimeDataToKernelCommands:()=>hF});var db=i=>({text:i?.text?.trim()||"\u65B0\u8282\u70B9",...i?.uid?{uid:i.uid}:{},...i?.hyperlink?{hyperlink:i.hyperlink}:{},...i?.note?{note:i.note}:{},...i?.refs?{refs:i.refs}:{}}),Op=i=>{let e=i.activeNodeId?.trim();switch(i.runtimeCommand){case"INSERT_CHILD_NODE":return e?{type:"insertChild",mindmapId:i.mindmapId,parentNodeId:e,node:db(i.newNode)}:null;case"INSERT_NODE":return e?{type:"insertSiblingAfter",mindmapId:i.mindmapId,targetNodeId:e,node:db(i.newNode)}:null;case"REMOVE_NODE":case"DELETE_NODE":return e?{type:"deleteNode",mindmapId:i.mindmapId,nodeId:e}:null;case"ADD_GENERALIZATION":return{type:"compatPayloadPatch",mindmapId:i.mindmapId,path:"root.data.generalization",value:{text:"\u6982\u8981"},source:"toolbar"};case"ADD_OUTER_FRAME":return{type:"compatPayloadPatch",mindmapId:i.mindmapId,path:"outerFrame",value:{enabled:!0,activeNodeId:e},source:"toolbar"};default:return null}},nF=(i,e)=>({type:"patchView",mindmapId:i,patch:e}),Bp=(i,e,t)=>({type:"setTheme",mindmapId:i,theme:e,themeConfig:t}),Pp=(i,e)=>({type:"setLayout",mindmapId:i,layout:e}),Tc=i=>({type:"compatPayloadPatch",mindmapId:i.mindmapId,path:i.path,value:i.value,source:i.source,...i.actionId?{actionId:i.actionId}:{},...typeof i.runtimeRevision=="number"?{runtimeRevision:i.runtimeRevision}:{},...typeof i.kernelRevision=="number"?{kernelRevision:i.kernelRevision}:{}}),sF=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),aF=(i,e)=>{let t=i.data?.uid;return typeof t=="string"&&t.trim()?t.trim():e},oF=i=>{let e=i.data?.text;return typeof e=="string"?e:String(e??"")},cb=i=>{let e=ci(i),t=new Map,r=(n,s,a,o)=>{let l=aF(n,o),h=sF(n.data)?n.data:{};t.set(l,{uid:l,parentUid:s,order:a,text:oF(n),data:h,node:n}),(Array.isArray(n.children)?n.children:[]).forEach((c,f)=>{r(ci(c),l,f,`${o}.${f}`)})};return r(e,null,0,"root"),t},ub=i=>JSON.stringify(i??null),fb=i=>({uid:i.uid,text:i.text||"\u65B0\u8282\u70B9",...typeof i.data.hyperlink=="string"?{hyperlink:i.data.hyperlink}:{},...typeof i.data.note=="string"?{note:i.data.note}:{},...Array.isArray(i.data.refs)?{refs:i.data.refs}:{}}),lF=(i,e,t,r)=>{let n=new Set(["uid","text","refs"]);new Set([...Object.keys(e.data),...Object.keys(t.data)]).forEach(a=>{n.has(a)||ub(e.data[a])!==ub(t.data[a])&&r.push(Tc({mindmapId:i,path:`nodes.${t.uid}.data.${a}`,value:t.data[a],source:"adapter-diff"}))})},hF=i=>{let e=cb(i.previous),t=cb(i.next),r=[],n=[];return t.forEach((s,a)=>{let o=e.get(a);if(!o){let l=Array.from(t.values()).find(h=>h.parentUid===s.parentUid&&h.order===s.order-1&&e.has(h.uid));l?r.push({type:"insertSiblingAfter",mindmapId:i.mindmapId,targetNodeId:l.uid,node:fb(s)}):s.parentUid&&r.push({type:"insertChild",mindmapId:i.mindmapId,parentNodeId:s.parentUid,node:fb(s)});return}o.text!==s.text&&r.push({type:"updateText",mindmapId:i.mindmapId,nodeId:a,text:s.text}),(o.parentUid!==s.parentUid||o.order!==s.order)&&r.push({type:"moveNode",mindmapId:i.mindmapId,nodeId:a,newParentNodeId:s.parentUid??"",order:s.order}),lF(i.mindmapId,o,s,n)}),e.forEach((s,a)=>{!t.has(a)&&s.parentUid&&r.push({type:"deleteNode",mindmapId:i.mindmapId,nodeId:a})}),{commands:r,compatPatches:n}};var Hp={};tt(Hp,{getMindmapActionMapping:()=>Nc,listMindmapActionMappings:()=>qp,mapMindmapActionToCommand:()=>cF});var zs=i=>e=>e?`nodes.${e}.data.${i}`:null,mb=[{actionId:"undo",target:"runtimeCommand",runtimeCommand:"BACK",requiresActiveNode:!1,readonlyAllowed:!1},{actionId:"redo",target:"runtimeCommand",runtimeCommand:"FORWARD",requiresActiveNode:!1,readonlyAllowed:!1},{actionId:"editNode",target:"localView",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"insertSiblingAfter",target:"runtimeCommand",runtimeCommand:"INSERT_NODE",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"insertChild",target:"runtimeCommand",runtimeCommand:"INSERT_CHILD_NODE",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"deleteNode",target:"runtimeCommand",runtimeCommand:"REMOVE_NODE",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"summary",target:"runtimeCommand",runtimeCommand:"ADD_GENERALIZATION",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"associativeLine",target:"compatPatch",runtimeCommand:"ADD_ASSOCIATIVE_LINE",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"setTheme",target:"kernelCommand",requiresActiveNode:!1,readonlyAllowed:!1},{actionId:"setLayout",target:"kernelCommand",requiresActiveNode:!1,readonlyAllowed:!1},{actionId:"tag",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("tag")},{actionId:"hyperlink",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("hyperlink")},{actionId:"note",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("note")},{actionId:"image",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("image")},{actionId:"icon",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("icon")},{actionId:"formula",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("formula")},{actionId:"painter",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("style")},{actionId:"import",target:"compatPatch",requiresActiveNode:!1,readonlyAllowed:!1,compatPath:()=>"import"},{actionId:"export",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"centerRoot",target:"localView",runtimeMethod:"centerRoot",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"zoomIn",target:"localView",runtimeMethod:"zoomIn",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"zoomOut",target:"localView",runtimeMethod:"zoomOut",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"fitView",target:"localView",runtimeMethod:"fitView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"fullscreenCanvas",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"fullscreenPage",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"exitFullscreen",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"search",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"showMenu",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"expandCollapse",target:"localView",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"copyNodeText",target:"localView",requiresActiveNode:!0,readonlyAllowed:!0},{actionId:"readonly",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0}],qp=()=>mb,Nc=i=>mb.find(e=>e.actionId===i)??null,dF=i=>{let e=i?.trim();return e||null},cF=i=>{let e=Nc(i.actionId);if(!e)return null;let t=dF(i.activeNodeId);if(e.requiresActiveNode&&!t)return null;if(i.actionId==="setTheme")return{command:Bp(i.mindmapId,i.value,i.themeConfig)};if(i.actionId==="setLayout")return{command:Pp(i.mindmapId,i.value)};if(i.actionId==="editNode")return{command:{type:"updateText",mindmapId:i.mindmapId,nodeId:t??"root",text:typeof i.value=="string"?i.value:""}};if(e.runtimeCommand&&["INSERT_CHILD_NODE","INSERT_NODE","DELETE_NODE","REMOVE_NODE"].includes(e.runtimeCommand)){let r=Op({mindmapId:i.mindmapId,runtimeCommand:e.runtimeCommand,activeNodeId:t,newNode:i.node});return r?{runtimeCommand:e.runtimeCommand,command:r}:{runtimeCommand:e.runtimeCommand}}if(e.compatPath){let r=e.compatPath(t);return r?{command:Tc({mindmapId:i.mindmapId,path:r,value:i.value??!0,source:i.source??"toolbar",actionId:i.actionId,runtimeRevision:i.runtimeRevision,kernelRevision:i.kernelRevision})}:null}return e.runtimeCommand?{runtimeCommand:e.runtimeCommand}:{}};var $p={};tt($p,{createDefaultMindmapShellInteractionState:()=>gb,deriveMindmapUiState:()=>wF,reduceMindmapChromeVisibility:()=>bF});var Up={};tt(Up,{MINDMAP_DEBUG_CHROME_QUERY_PARAM:()=>pb,MINDMAP_TOOLBAR_MORE_ACTION_ID:()=>uF,isMindmapDebugChromeEnabled:()=>pF,mindmapDefaultUiSchema:()=>ho,mindmapToolbarFileActionOrder:()=>mF,mindmapToolbarPrimaryActionOrder:()=>fF});var pb="mnoteMindmapDebugChrome",uF="more",fF=["undo","redo","editNode","insertSiblingAfter","deleteNode","insertChild","tag","hyperlink","note","image","icon","summary","associativeLine","formula"],mF=["import","export"],Ee=(i,e,t,r,n,s,a)=>({id:i,iconKey:e,shortLabel:t,longLabel:r,priority:n,overflowGroup:s,cluster:a}),ho={toolbarGroups:[{id:"history",label:"\u5386\u53F2",actions:["undo","redo"],collapsePriority:4},{id:"node",label:"\u8282\u70B9",actions:["editNode","insertSiblingAfter","deleteNode","insertChild"],collapsePriority:1},{id:"insert",label:"\u63D2\u5165",actions:["tag","hyperlink","note","image","icon","summary","associativeLine","formula"],collapsePriority:2},{id:"file",label:"\u6587\u4EF6",actions:["import","export"],collapsePriority:5},{id:"view",label:"\u89C6\u56FE",actions:["painter","centerRoot","zoomOut","zoomIn","search","readonly"],collapsePriority:3}],toolbarActionMeta:{undo:Ee("undo","undo","\u64A4\u9500","\u64A4\u9500",10,"history","main"),redo:Ee("redo","redo","\u91CD\u505A","\u91CD\u505A",20,"history","main"),editNode:Ee("editNode","type","\u7F16\u8F91","\u7F16\u8F91\u8282\u70B9",30,"node","main"),insertSiblingAfter:Ee("insertSiblingAfter","sibling","\u540C\u7EA7","\u63D2\u5165\u540C\u7EA7\u8282\u70B9",40,"node","main"),deleteNode:Ee("deleteNode","trash","\u5220\u9664","\u5220\u9664\u8282\u70B9",50,"node","main"),insertChild:Ee("insertChild","child","\u5B50\u7EA7","\u63D2\u5165\u5B50\u8282\u70B9",60,"node","main"),tag:Ee("tag","tag","\u6807\u7B7E","\u6807\u7B7E",70,"insert","main"),hyperlink:Ee("hyperlink","link","\u94FE\u63A5","\u8D85\u94FE\u63A5",80,"insert","main"),note:Ee("note","note","\u5907\u6CE8","\u5907\u6CE8",90,"insert","main"),image:Ee("image","image","\u56FE\u7247","\u56FE\u7247",100,"insert","main"),icon:Ee("icon","smile","\u56FE\u6807","\u56FE\u6807",110,"insert","main"),summary:Ee("summary","summary","\u6982\u8981","\u6982\u8981",120,"insert","main"),associativeLine:Ee("associativeLine","route","\u5173\u8054","\u5173\u8054\u7EBF",130,"insert","main"),formula:Ee("formula","formula","\u516C\u5F0F","\u516C\u5F0F",140,"insert","main"),painter:Ee("painter","paintbrush","\u683C\u5F0F","\u683C\u5F0F\u5237",210,"view","view"),import:Ee("import","import","\u5BFC\u5165","\u5BFC\u5165",310,"file","file"),export:Ee("export","export","\u5BFC\u51FA","\u5BFC\u51FA",320,"file","file"),setTheme:Ee("setTheme","palette","\u4E3B\u9898","\u4E3B\u9898",410,"view","view"),setLayout:Ee("setLayout","layout","\u7ED3\u6784","\u7ED3\u6784",420,"view","view"),zoomIn:Ee("zoomIn","zoom-in","\u653E\u5927","\u653E\u5927",510,"view","view"),zoomOut:Ee("zoomOut","zoom-out","\u7F29\u5C0F","\u7F29\u5C0F",500,"view","view"),fitView:Ee("fitView","fit","\u9002\u5E94","\u9002\u5E94\u753B\u5E03",520,"view","view"),centerRoot:Ee("centerRoot","target","\u56DE\u6839","\u56DE\u5230\u6839\u8282\u70B9",490,"view","view"),fullscreenCanvas:Ee("fullscreenCanvas","fullscreen","\u5168\u5C4F","\u5168\u5C4F\u67E5\u770B",550,"view","view"),fullscreenPage:Ee("fullscreenPage","fullscreen-page","\u5168\u9875","\u5168\u5C4F\u7F16\u8F91",560,"view","view"),exitFullscreen:Ee("exitFullscreen","exit-fullscreen","\u9000\u51FA","\u9000\u51FA\u5168\u5C4F",570,"view","view"),search:Ee("search","search","\u641C\u7D22","\u641C\u7D22",530,"view","view"),showMenu:Ee("showMenu","menu","\u83DC\u5355","\u663E\u793A\u83DC\u5355",580,"view","view"),expandCollapse:Ee("expandCollapse","expand","\u5C55\u5F00","\u5C55\u5F00/\u6536\u8D77",610,"view","view"),copyNodeText:Ee("copyNodeText","copy","\u590D\u5236","\u590D\u5236\u6587\u672C",620,"view","view"),readonly:Ee("readonly","lock","\u53EA\u8BFB","\u53EA\u8BFB",540,"view","view")},sidebarPanels:[{id:"nodeStyle",kind:"nodeStyle",label:"\u8282\u70B9\u6837\u5F0F",icon:"palette",runtimeCapability:"node-style",phase:1,options:[{id:"node-fill-blue",label:"\u6D77\u84DD",actionId:"painter",value:"#dbeafe",controlType:"swatch",preview:"#dbeafe",compatPath:"nodes.$active.data.fillColor"},{id:"node-fill-green",label:"\u8584\u8377",actionId:"painter",value:"#dcfce7",controlType:"swatch",preview:"#dcfce7",compatPath:"nodes.$active.data.fillColor"},{id:"node-fill-amber",label:"\u6696\u9EC4",actionId:"painter",value:"#fef3c7",controlType:"swatch",preview:"#fef3c7",compatPath:"nodes.$active.data.fillColor"},{id:"node-text-dark",label:"\u6DF1\u8272\u6587\u5B57",actionId:"painter",value:"#0f172a",controlType:"swatch",preview:"#0f172a",compatPath:"nodes.$active.data.color"},{id:"node-text-blue",label:"\u84DD\u8272\u6587\u5B57",actionId:"painter",value:"#1d4ed8",controlType:"swatch",preview:"#1d4ed8",compatPath:"nodes.$active.data.color"},{id:"node-font-14",label:"14 px",actionId:"painter",value:14,controlType:"segmented",compatPath:"nodes.$active.data.fontSize"},{id:"node-font-18",label:"18 px",actionId:"painter",value:18,controlType:"segmented",compatPath:"nodes.$active.data.fontSize"},{id:"node-font-bold",label:"\u52A0\u7C97",actionId:"painter",value:!0,controlType:"toggle",compatPath:"nodes.$active.data.fontWeight"},{id:"node-font-italic",label:"\u659C\u4F53",actionId:"painter",value:!0,controlType:"toggle",compatPath:"nodes.$active.data.fontStyle"},{id:"node-shape-round",label:"\u5706\u89D2\u77E9\u5F62",actionId:"painter",value:"roundedRectangle",controlType:"button",compatPath:"nodes.$active.data.shape"},{id:"node-shape-rect",label:"\u77E9\u5F62",actionId:"painter",value:"rectangle",controlType:"button",compatPath:"nodes.$active.data.shape"},{id:"node-border-blue",label:"\u84DD\u8272\u8FB9\u6846",actionId:"painter",value:"#60a5fa",controlType:"swatch",preview:"#60a5fa",compatPath:"nodes.$active.data.borderColor"},{id:"node-line-teal",label:"\u9752\u8272\u5206\u652F\u7EBF",actionId:"painter",value:"#14b8a6",controlType:"swatch",preview:"#14b8a6",compatPath:"nodes.$active.data.lineColor"},{id:"node-line-width-2",label:"\u8FB9\u7EBF 2",actionId:"painter",value:2,controlType:"segmented",compatPath:"nodes.$active.data.lineWidth"}]},{id:"baseStyle",kind:"baseStyle",label:"\u5BFC\u56FE\u6837\u5F0F",icon:"sliders",runtimeCapability:"base-style",phase:1,options:[{id:"base-curve-line",label:"\u66F2\u7EBF",actionId:"painter",value:"curve",controlType:"segmented",compatPath:"style.map.lineStyle"},{id:"base-direct-line",label:"\u76F4\u7EBF",actionId:"painter",value:"straight",controlType:"segmented",compatPath:"style.map.lineStyle"},{id:"base-rainbow-lines",label:"\u5F69\u8679\u7EBF\u6761",actionId:"painter",value:{enabled:!0},controlType:"toggle",compatPath:"style.map.rainbowLines"},{id:"base-line-width-2",label:"\u7EBF\u5BBD 2",actionId:"painter",value:2,controlType:"segmented",compatPath:"style.map.lineWidth"},{id:"base-line-width-4",label:"\u7EBF\u5BBD 4",actionId:"painter",value:4,controlType:"segmented",compatPath:"style.map.lineWidth"},{id:"base-background-light",label:"\u6D45\u8272\u80CC\u666F",actionId:"painter",value:"#f8fafc",controlType:"swatch",preview:"#f8fafc",compatPath:"style.map.backgroundColor"},{id:"base-node-spacing-36",label:"\u8282\u70B9\u95F4\u8DDD 36",actionId:"painter",value:36,controlType:"numberInput",compatPath:"style.map.nodeSpacing"},{id:"base-summary-bracket",label:"\u62EC\u53F7\u6982\u8981",actionId:"painter",value:"bracket",controlType:"select",compatPath:"style.map.summaryStyle"}]},{id:"theme",kind:"theme",label:"\u4E3B\u9898",icon:"swatch",runtimeCapability:"theme",phase:1,options:[{id:"theme-classic",label:"Classic",actionId:"setTheme",value:"classic",controlType:"swatch",preview:"#60a5fa",description:"\u9ED8\u8BA4\u4E3B\u9898"},{id:"theme-classic4",label:"KMind",actionId:"setTheme",value:"classic4",controlType:"swatch",preview:"#22c55e",description:"KMind-like"},{id:"theme-simple",label:"Simple",actionId:"setTheme",value:"simple",controlType:"swatch",preview:"#f59e0b",description:"\u8F7B\u91CF\u4E3B\u9898"},{id:"theme-dark",label:"Dark",actionId:"setTheme",value:"dark",controlType:"swatch",preview:"#334155",description:"\u6DF1\u8272\u4E3B\u9898"}]},{id:"structure",kind:"structure",label:"\u7ED3\u6784",icon:"layout",runtimeCapability:"layout",phase:1,options:[{id:"layout-logical",label:"\u903B\u8F91\u7ED3\u6784\u56FE",actionId:"setLayout",value:"logicalStructure",controlType:"layoutCard",preview:"logicalStructure"},{id:"layout-mind-map",label:"\u601D\u7EF4\u5BFC\u56FE",actionId:"setLayout",value:"mindMap",controlType:"layoutCard",preview:"mindMap"},{id:"layout-organization",label:"\u7EC4\u7EC7\u7ED3\u6784\u56FE",actionId:"setLayout",value:"organizationStructure",controlType:"layoutCard",preview:"organizationStructure"},{id:"layout-catalog",label:"\u76EE\u5F55\u7EC4\u7EC7\u56FE",actionId:"setLayout",value:"catalogOrganization",controlType:"layoutCard",preview:"catalogOrganization"},{id:"layout-timeline",label:"\u65F6\u95F4\u8F74",actionId:"setLayout",value:"timeline",controlType:"layoutCard",preview:"timeline"},{id:"layout-fishbone",label:"\u9C7C\u9AA8\u56FE",actionId:"setLayout",value:"fishbone",controlType:"layoutCard",preview:"fishbone"}]},{id:"outline",kind:"outline",label:"\u5927\u7EB2",icon:"list-tree",runtimeCapability:"outline",phase:1,options:[]},{id:"shortcutKey",kind:"shortcutKey",label:"\u5FEB\u6377\u952E",icon:"sparkles",runtimeCapability:"shortcut-key",phase:1,options:[{id:"shortcut-insert-child",label:"Tab",description:"\u63D2\u5165\u5B50\u8282\u70B9",actionId:null,value:null,controlType:"treeItem",readonly:!0},{id:"shortcut-insert-sibling",label:"Enter",description:"\u63D2\u5165\u540C\u7EA7\u8282\u70B9",actionId:null,value:null,controlType:"treeItem",readonly:!0},{id:"shortcut-delete",label:"Delete",description:"\u5220\u9664\u8282\u70B9",actionId:null,value:null,controlType:"treeItem",readonly:!0}]},{id:"settings",kind:"settings",label:"\u8BBE\u7F6E",icon:"hexagon",runtimeCapability:"settings",phase:1,options:[{id:"settings-readonly-hint",label:"\u53EA\u8BFB\u6A21\u5F0F",description:"\u5BFC\u822A\u680F\u5207\u6362",actionId:null,value:null,controlType:"toggle",readonly:!0},{id:"settings-mouse",label:"\u9F20\u6807\u884C\u4E3A",description:"\u5DE6\u952E\u9009\u4E2D\uFF0C\u53F3\u952E\u62D6\u62FD",actionId:null,value:"leftSelectRightDrag",controlType:"select",readonly:!0}]}],navigatorItems:[{id:"stats",label:"\u7EDF\u8BA1",actionId:null,readOnly:!0,displayMode:"text"},{id:"centerRoot",label:"\u56DE\u6839\u8282\u70B9",actionId:"centerRoot",readOnly:!0,displayMode:"button"},{id:"search",label:"\u641C\u7D22",actionId:"search",readOnly:!0,displayMode:"button"},{id:"zoomOut",label:"\u7F29\u5C0F",actionId:"zoomOut",readOnly:!0,displayMode:"button"},{id:"zoom",label:"\u7F29\u653E",actionId:null,readOnly:!0,displayMode:"input"},{id:"zoomIn",label:"\u653E\u5927",actionId:"zoomIn",readOnly:!0,displayMode:"button"},{id:"fullscreen",label:"\u5168\u5C4F",actionId:"fullscreenCanvas",readOnly:!0,displayMode:"button"},{id:"readonly",label:"\u53EA\u8BFB",actionId:"readonly",readOnly:!0,displayMode:"button"}],contextMenuItems:[{id:"insertChild",label:"\u63D2\u5165\u5B50\u8282\u70B9",actionId:"insertChild",requiresNode:!0,phase:1},{id:"insertSiblingAfter",label:"\u63D2\u5165\u540C\u7EA7\u8282\u70B9",actionId:"insertSiblingAfter",requiresNode:!0,phase:1},{id:"deleteNode",label:"\u5220\u9664\u8282\u70B9",actionId:"deleteNode",requiresNode:!0,phase:1},{id:"summary",label:"\u6982\u8981",actionId:"summary",requiresNode:!0,phase:1},{id:"associativeLine",label:"\u5173\u8054\u7EBF",actionId:"associativeLine",requiresNode:!0,phase:1},{id:"expandCollapse",label:"\u5C55\u5F00/\u6536\u8D77",actionId:"expandCollapse",requiresNode:!0,phase:1},{id:"copyNodeText",label:"\u590D\u5236\u6587\u672C",actionId:"copyNodeText",requiresNode:!0,phase:1},{id:"centerRoot",label:"\u56DE\u6839\u8282\u70B9",actionId:"centerRoot",requiresNode:!1,phase:1},{id:"fitView",label:"\u9002\u5E94\u753B\u5E03",actionId:"fitView",requiresNode:!1,phase:1},{id:"search",label:"\u641C\u7D22",actionId:"search",requiresNode:!1,phase:1},{id:"readonly",label:"\u53EA\u8BFB\u5207\u6362",actionId:"readonly",requiresNode:!1,phase:1},{id:"showMenu",label:"\u663E\u793A\u83DC\u5355",actionId:"showMenu",requiresNode:!1,phase:1}]},pF=i=>{try{let e=new URL(i).searchParams.get(pb);return e==="1"||e==="true"}catch{return!1}};var gF=()=>{let i=[...ho.toolbarGroups.flatMap(e=>e.actions),...ho.navigatorItems.flatMap(e=>e.actionId?[e.actionId]:[]),...ho.contextMenuItems.map(e=>e.actionId),...qp().map(e=>e.actionId)];return[...new Set(i)]},xF=i=>{let e=i?.trim();return e||null},yF=i=>typeof i!="number"||!Number.isFinite(i)?100:Math.max(10,Math.min(500,Math.round(i))),vF=new Set(["tag","hyperlink","note","image","icon","associativeLine","formula","import","export"]),gb=(i={})=>({chromeVisibility:i.chromeVisibility??"visible",toolbarOverflow:{availableWidth:i.toolbarOverflow?.availableWidth??null,visibleActionIds:i.toolbarOverflow?.visibleActionIds??[],overflowActionIds:i.toolbarOverflow?.overflowActionIds??[],moreOpen:i.toolbarOverflow?.moreOpen??!1},fullscreen:{mode:i.fullscreen?.mode??"none",isFullscreen:i.fullscreen?.isFullscreen??!1,target:i.fullscreen?.target??"mindmap-root",apiAvailable:i.fullscreen?.apiAvailable??!0},sidebar:{triggerVisible:i.sidebar?.triggerVisible??!0,panelOpen:i.sidebar?.panelOpen??!0,activePanelId:i.sidebar?.activePanelId??ho.sidebarPanels[0]?.id??null,drawerWidth:i.sidebar?.drawerWidth??300,collapsedByToggle:i.sidebar?.collapsedByToggle??!1},navigator:{searchOpen:i.navigator?.searchOpen??!1,minimapOpen:i.navigator?.minimapOpen??!1,readonly:i.navigator?.readonly??!1,zoomPercent:yF(i.navigator?.zoomPercent),mouseBehavior:i.navigator?.mouseBehavior??"leftSelectRightDrag"}}),bF=(i,e)=>e==="pointerLeave"?"hiddenByPointerLeave":e==="pointerEnter"?i:e==="restoreClick"?"visible":e==="hideToggle"?"hiddenByToggle":e==="showToggle"?"visible":e==="enterFullscreen"?i==="visible"?"visible":"hiddenByFullscreen":e==="exitFullscreen"&&i==="hiddenByFullscreen"?"visible":i,wF=i=>{let e=xF(i.activeNodeId),t=i.runtimeCapabilities?new Set(i.runtimeCapabilities):null,r={},n=gb({...i.shell,navigator:{...i.shell?.navigator,readonly:i.readonly}});return gF().forEach(s=>{let a=Nc(s);if(!a){r[s]=!0;return}if(a.requiresActiveNode&&!e){r[s]=!0;return}if(i.readonly&&!a.readonlyAllowed){r[s]=!0;return}if(t&&!t.has(a.target)){r[s]=!0;return}if(vF.has(s)){r[s]=!0;return}r[s]=!1}),{activeNodeId:e,readonly:i.readonly,disabledActions:r,shell:n}};var jp={};tt(jp,{resolveMindmapShortcutAction:()=>MF,shouldInterceptMindmapShortcut:()=>TF});var MF=i=>i.ctrlKey||i.metaKey||i.altKey||i.shiftKey?null:i.key==="Enter"?"insertSiblingAfter":i.key==="Tab"||i.key==="Insert"?"insertChild":i.key==="Delete"||i.key==="Backspace"?"deleteNode":i.key==="F2"?"editNode":null,TF=i=>i.debugChromeEnabled||!i.bridgeReady||i.readonly||i.isComposing||i.isEditableTarget?!1:i.targetInsideRoot?!0:i.keyboardShortcutArmed;function d0(){return{status:"idle"}}function xb(i,e){return{status:"armed",armedAt:e,reason:i}}function yb(){return d0()}function vb(i,e,t=1500){return i.status!=="armed"?{state:i,suppressed:!1,expired:!1}:e-i.armedAt>t?{state:d0(),suppressed:!1,expired:!0}:{state:d0(),suppressed:!0,expired:!1}}function bb(i){return{endpoint:`/api/mindmap/${encodeURIComponent(i.documentId)}/${encodeURIComponent(i.mindmapId)}`,init:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({data:i.data,createOnly:!0})}}}function Vn(i){let e=i;return e.default&&typeof e.default=="object"?e.default:i}var{applyMindmapCommandsLocally:NF,isLocallyApplicableMindmapCommandSet:EF}=Vn(Dc),{createLeptosMindmapAdapter:SF,executeMindmapCommandApply:Gp,executeMindmapCommandApplyAndRefreshProjection:AF,requestMindmapAdapterProjection:kF}=Vn(Dp),{createViewPatchCommand:CF,createCompatPayloadPatch:_F,diffMindmapRuntimeDataToKernelCommands:LF}=Vn(Fp),{getMindmapActionMapping:wb,mapMindmapActionToCommand:IF}=Vn(Hp),{canonicalizeMindmapData:vi}=Vn(Pc),{deriveMindmapUiState:Vp,reduceMindmapChromeVisibility:Ec}=Vn($p),{resolveMindmapShortcutAction:zF,shouldInterceptMindmapShortcut:RF}=Vn(jp),{isMindmapDebugChromeEnabled:DF,mindmapDefaultUiSchema:Sc,mindmapToolbarFileActionOrder:OF,mindmapToolbarPrimaryActionOrder:Xp}=Vn(Up);function BF(i){return typeof i=="string"&&i.length>0?{"data-block-id":i,id:i}:{}}function PF(i){if(i.mnoteBlockType!=="mindmap")return{};let e={"data-mnote-block-type":"mindmap"};return typeof i.mindmapId=="string"&&(e["data-mnote-mindmap-id"]=i.mindmapId),typeof i.rootNodeId=="string"&&(e["data-mnote-root-node-id"]=i.rootNodeId),typeof i.projectionVersion=="number"&&(e["data-mnote-projection-version"]=String(i.projectionVersion)),e}function Rs(i){return String(i??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function Wp(i){if(typeof i!="string")return null;let e=i.trim();return e.length>0?e:null}function FF(i){let e=Wp(i?.closest("[data-document-id]")?.getAttribute("data-document-id"));if(e)return e;if(i&&"isConnected"in i&&i.isConnected===!1)return null;let t=window.location.pathname.match(/\/documents\/([^/?#]+)/);if(t?.[1])return decodeURIComponent(t[1]);let r=window.location.pathname.match(/\/mindmap\/([^/?#]+)\/([^/?#]+)/);if(r?.[1])return decodeURIComponent(r[1]);let n=Wp(document.body?.getAttribute("data-document-id"));if(n)return n;let s=Wp(document.querySelector(".document-shell[data-document-id], [data-pane-document-id], [data-document-id]")?.getAttribute("data-document-id")??document.querySelector("[data-pane-document-id]")?.getAttribute("data-pane-document-id"));return s||null}function Mb(i,e){let r=vi(i.root).data?.uid;return typeof r=="string"&&r.trim().length>0?r.trim():e}function Yp(i){let t=vi(i.root).data?.text;return typeof t=="string"&&t.trim().length>0?t.trim():"\u672A\u547D\u540D\u8282\u70B9"}function Ds(i){if(typeof i=="string"&&i.trim().length>0)return i.trim();if(!i||typeof i!="object")return null;let e=i;if(typeof e.uid=="string"&&e.uid.trim().length>0)return e.uid.trim();let t=e.data&&typeof e.data=="object"&&!Array.isArray(e.data)?e.data:{};if(typeof t.uid=="string"&&t.uid.trim().length>0)return t.uid.trim();let r=e.getData;if(typeof r=="function")try{let n=r.call(e,"uid");if(typeof n=="string"&&n.trim().length>0)return n.trim()}catch{return null}return null}function Ac(i,e){let t=window,r=t.__MNOTE_LEPTOS_MINDMAP_BRIDGES__??{};t.__MNOTE_LEPTOS_MINDMAP_BRIDGES__=r,e?r[i]=e:delete r[i]}function Lt(i){return typeof i=="object"&&i!==null&&!Array.isArray(i)}function Os(i){return JSON.parse(JSON.stringify(i))}function qF(i){if(!(i instanceof HTMLElement))return!1;let e=i.tagName;return e==="INPUT"||e==="TEXTAREA"||e==="SELECT"?!0:i.isContentEditable?!i.matches(".ProseMirror"):!1}function HF(i){if(!Lt(i))return null;let e=i.view;return Lt(e)&&Lt(e.state)?e:null}function UF(i){if(!(i!=="insertChild"&&i!=="insertSiblingAfter"))return{uid:`node_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`,text:"\u65B0\u8282\u70B9"}}function $F(i,e){let t=e?.trim();if(!t)return!1;let r=vi(i),n=!1,s=a=>{if(n)return;let o=vi(a);if(o.data?.uid===t){n=!0;return}(Array.isArray(o.children)?o.children:[]).forEach(s)};return s(r),n}function Tb(i){let e=vi(i),t=Array.isArray(e.children)?[...e.children]:[];for(;t.length>0;){let r=vi(t.shift()),n=r.data?.uid;if(typeof n=="string"&&n.trim().length>0)return n.trim();Array.isArray(r.children)&&t.push(...r.children)}return null}function jF(i,e){let t=e?.trim();if(!t)return null;let r=null,n=s=>{if(r)return;let a=vi(s);if(a.data?.uid===t){r=a;return}(Array.isArray(a.children)?a.children:[]).forEach(n)};return n(i),r}function GF(i,e){let t=jF(i,e),r=t&&Lt(t.data)?t.data.text:null;return typeof r=="string"?r:""}function VF(i,e=10){let t=vi(i),r=[],n=(s,a)=>{if(r.length>=e)return;let o=vi(s),l=typeof o.data?.text=="string"&&o.data.text.trim().length>0?o.data.text.trim():"\u672A\u547D\u540D\u8282\u70B9";r.push(`${" ".repeat(Math.max(a-1,0))}${l}`),(Array.isArray(o.children)?o.children:[]).forEach(d=>n(d,a+1))};return n(t,1),r}function Nb(i,e){let t=i?.trim();return t?t.includes("$active")?e?t.replaceAll("$active",e):null:t:null}var Eb={undo:"\u64A4\u9500",redo:"\u91CD\u505A",editNode:"\u7F16\u8F91\u8282\u70B9",insertSiblingAfter:"\u540C\u7EA7\u8282\u70B9",insertChild:"\u5B50\u8282\u70B9",deleteNode:"\u5220\u9664",tag:"\u6807\u7B7E",hyperlink:"\u94FE\u63A5",note:"\u5907\u6CE8",image:"\u56FE\u7247",icon:"\u56FE\u6807",summary:"\u6982\u8981",associativeLine:"\u5173\u8054\u7EBF",formula:"\u516C\u5F0F",painter:"\u683C\u5F0F\u5237",import:"\u5BFC\u5165",export:"\u5BFC\u51FA",setTheme:"\u4E3B\u9898",setLayout:"\u7ED3\u6784",zoomIn:"\u653E\u5927",zoomOut:"\u7F29\u5C0F",fitView:"\u9002\u5E94",centerRoot:"\u56DE\u6839",fullscreenCanvas:"\u5168\u5C4F",fullscreenPage:"\u5168\u9875",exitFullscreen:"\u9000\u51FA",search:"\u641C\u7D22",showMenu:"\u663E\u793A\u83DC\u5355",expandCollapse:"\u5C55\u5F00/\u6536\u8D77",copyNodeText:"\u590D\u5236\u6587\u672C",readonly:"\u53EA\u8BFB"},WF={undo:"\u21B6",redo:"\u21B7",editNode:"T",insertSiblingAfter:"\u21B5",insertChild:"+",deleteNode:"\xD7",tag:"#",hyperlink:"\u2301",note:"N",image:"\u25A1",icon:"\u2606",summary:"{",associativeLine:"\u2307",formula:"fx",painter:"\u25D0",import:"\u21E7",export:"\u21E9",centerRoot:"\u25CE",zoomOut:"-",zoomIn:"+",search:"\u2315",fullscreenCanvas:"\u26F6",fullscreenPage:"\u25A3",exitFullscreen:"\u2921",showMenu:"\u2630",expandCollapse:"\u2922",copyNodeText:"C",readonly:"\u9501"},YF={undo:"\u21B6",redo:"\u21B7",type:"T",sibling:"\u21B5",trash:"\xD7",child:"+",tag:"#",link:"\u2301",note:"N",image:"\u25A1",smile:"\u2606",summary:"{",route:"\u2307",formula:"fx",paintbrush:"\u25D0",import:"\u21E7",export:"\u21E9",target:"\u25CE",fullscreen:"\u26F6","fullscreen-page":"\u25A3","exit-fullscreen":"\u2921",menu:"\u2630","zoom-out":"-","zoom-in":"+",search:"\u2315",lock:"\u9501"},XF=i=>{let e=[...Xp];if(i===null||!Number.isFinite(i))return{availableWidth:null,visibleActionIds:e,overflowActionIds:[]};let t=118,r=96,n=50,s=Math.max(0,i-t-r),a=Math.max(2,Math.min(e.length,Math.floor(s/n)));if(a>=e.length)return{availableWidth:i,visibleActionIds:e,overflowActionIds:[]};let o=Math.max(1,a-1);return{availableWidth:i,visibleActionIds:e.slice(0,o),overflowActionIds:e.slice(o)}};function KF(i){return({node:e,getPos:t})=>{let r=e,n=0,s=null,a=null,o=null,l=null,h=null,d=null,c=d0(),f=null,m=!1,g=Sc.sidebarPanels[0]?.id??"nodeStyle",x=!0,y=!0,b=!1,S=300,A=null,C=null,I=!1,O=!1,q=null,P=!1,W=null,ne=null,re=null,oe=null,ke=null,Q=null,Y="visible",le=!1,ce=!1,at="canvas",It=null,bt="canvas",qi=0,zt=0,di=null,nn=null,Hi=null,co=0,Mr=0,tr=!1,sn=null,bi=null,Ui=!1,wi=DF(window.location.href),Je=document.createElement("div");Je.className="mnote-mindmap-placeholder",Je.dataset.mnoteBlockType="mindmap",Je.dataset.testid="mnote-mindmap-placeholder",Je.setAttribute("contenteditable","false");let ve=document.createElement("div");ve.className="mnote-mindmap-editor-root",ve.dataset.testid="mnote-mindmap-editor-root",ve.dataset.mnoteSceneQuery="mindmap.simple_mind_map_scene.get";let an=()=>FF(ve),R=document.createElement("div");R.dataset.testid="leptos-mindmap-island",R.dataset.runtime="simple-mind-map",R.dataset.stage="loading",R.dataset.debugChrome=wi?"true":"false",R.dataset.suppressRuntimeDiffState="idle",R.className="mnote-leptos-mindmap-shell";let Wn=()=>{R.dataset.suppressRuntimeDiffState=c.status,R.dataset.suppressRuntimeDiffReason=c.status==="armed"?c.reason:"",R.dataset.suppressRuntimeDiffSince=c.status==="armed"?String(c.armedAt):""},Bs=M=>{c=xb(M,Date.now()),Wn()},$i=()=>{c=yb(),Wn()},Yn=document.createElement("div");Yn.className="mnote-mindmap-workspace",Yn.dataset.debugChrome=wi?"true":"false",Yn.dataset.layout=wi?"debug-grid":"floating-overlay";let c0=document.createElement("div");c0.className="mnote-mindmap-canvas-layer",c0.dataset.testid="mindmap-canvas-layer";let Xn=document.createElement("div");Xn.className="mnote-mindmap-overlay-layer",Xn.dataset.testid="mindmap-overlay-layer";let mt=document.createElement("div");mt.className="mnote-leptos-mindmap-runtime",mt.dataset.testid="simple-mind-map-runtime",mt.dataset.runtime="simple-mind-map",mt.dataset.runtimeEngine="pending";let ir=document.createElement("div");ir.className="mnote-mindmap-rust-shell-mount",ir.dataset.testid="mindmap-rust-shell-mount",ir.dataset.uiShellSource="leptos-rust-shell";let Kn=document.createElement("div");Kn.className="mnote-mindmap-command-toolbar",Kn.dataset.testid="mindmap-command-toolbar";let Zn=document.createElement("aside");Zn.className="mnote-mindmap-side-panel",Zn.dataset.testid="mindmap-sidebar";let Qn=document.createElement("div");Qn.className="mnote-mindmap-bottom-bar",Qn.dataset.testid="mindmap-bottom-bar";let et=()=>{let M=typeof r.attrs.mindmapId=="string"&&r.attrs.mindmapId.length>0?r.attrs.mindmapId:"mindmap",L=typeof r.attrs.rootNodeId=="string"&&r.attrs.rootNodeId.length>0?r.attrs.rootNodeId:"root";return{mindmapId:M,rootNodeId:L}},Kp=M=>JSON.stringify({mnoteBlockType:M.attrs.mnoteBlockType??null,mindmapId:M.attrs.mindmapId??null,rootNodeId:M.attrs.rootNodeId??null,projectionVersion:M.attrs.projectionVersion??null}),u0=()=>{let{mindmapId:M,rootNodeId:L}=et();Je.dataset.mnoteMindmapId=M,Je.dataset.mnoteRootNodeId=L,Je.dataset.mnoteProjectionVersion=String(r.attrs.projectionVersion??1),ve.dataset.mnoteMindmapId=M,ve.dataset.mnoteRootNodeId=L,ve.dataset.mnoteProjectionVersion=String(r.attrs.projectionVersion??1),ve.dataset.mnoteSceneQuery="mindmap.simple_mind_map_scene.get"},f0=()=>{if(!(nn===null||!Hi)){try{Hi.unmount(nn)}catch{}nn=null,Hi=null,ir.replaceChildren()}},Zp=()=>{let M=s?.getSnapshot(),L=Lt(M)&&"root"in M?M.root:M??a?.root,D=0,F=0,K=te=>{if(!Lt(te))return;D+=1;let we=Lt(te.data)&&typeof te.data.text=="string"?te.data.text:"";F+=we.trim().length,(Array.isArray(te.children)?te.children:[]).forEach(K)};K(L);let ee=a?.view,ue=Lt(M)&&Lt(M.view)&&Lt(M.view.state)&&typeof M.view.state.scale=="number"?M.view.state.scale:Lt(ee)&&Lt(ee.state)&&typeof ee.state.scale=="number"?ee.state.scale:1;return{nodeCount:D,wordCount:F,zoomPercent:Math.round(ue*100)}},Sb=()=>a?{...vi(a.root),layout:a.layout,theme:a.theme,themeConfig:a.themeConfig,view:a.view,config:a.config,compatPayload:a.compatPayload}:null,Ab=(M,L)=>{a&&(a.root=Os(vi(M)),"layout"in M&&(a.layout=M.layout),"theme"in M&&(a.theme=M.theme),"themeConfig"in M&&(a.themeConfig=M.themeConfig),"view"in M&&(a.view=M.view),"config"in M&&(a.config=M.config),"compatPayload"in M&&(a.compatPayload=M.compatPayload),typeof L=="number"&&Number.isFinite(L)&&(a.kernelRevision=L),o=Os(a.root))},kb=()=>{let M=s?.getSnapshot(),L=Lt(M)&&"root"in M?M.root:M??a?.root;return VF(L)},m0=()=>{P&&(P=!1,Le())},Qp=(M,L)=>{let D=ve.getBoundingClientRect();qi=Math.round(M-D.left),zt=Math.round(L-D.top)},p0=()=>{!ce&&!R.dataset.contextMenuKind||(ce=!1,at="canvas",It=null,bt="canvas",delete R.dataset.contextMenuKind,delete R.dataset.contextMenuTargetNodeId,delete R.dataset.contextMenuTargetNodeKind,b0())},g0=M=>Ds(M),Cb=()=>{let M=s?.instance.renderer;return M?.activeNodeList?.[0]??M?.lastActiveNodeList?.[0]??M?.root??M?.renderTree?._node??null},x0=(M,L,D)=>{let F=Ds(L);return F&&$F(M.root,F)?F:Tb(M.root)??Mb(M,D)},kc=M=>{let L=Ds(M);return L||(g0(h)??g0(Cb())??null)},_b=M=>{if(!M||typeof M!="object")return"node";let L=M;return L.isRoot===!0?"root":L.isGeneralization===!0?"generalization":"node"},Lb=M=>{let L=s?.instance.renderer;if(L)try{L.clearActiveNodeList?.(),L.addNodeToActiveList?.(M,!0),L.emitNodeActiveEvent?.(M,[M])}catch{}},uo=M=>{let L=Ds(M),D=s?.instance.renderer,F=L?D?.findNodeByUid?.(L)??null:D?.activeNodeList?.[0]??D?.lastActiveNodeList?.[0]??D?.root??D?.renderTree?._node;if(F)try{D?.clearActiveNodeList?.(),D?.addNodeToActiveList?.(F,!0),D&&(D.lastActiveNodeList=[F]),D?.emitNodeActiveEvent?.(F,[F]),s?.instance.execCommand?.("SET_NODE_ACTIVE",F,!0),h=F;let ee=L??g0(F)??l??et().rootNodeId;return l=ee,d=ee,R.dataset.lastRuntimeSelectionSync=ee,!0}catch{}let K=s?.instance.execCommand;if(typeof K!="function"||!L)return!1;try{return K.call(s?.instance,"GO_TARGET_NODE",L),l=L,d=L,R.dataset.lastRuntimeSelectionSync=L,!0}catch{return!1}},Jp=M=>{let L=Ds(M),D=s?.instance.renderer;return{wantedNodeId:L,runtimeNode:L?D?.findNodeByUid?.(L)??null:D?.activeNodeList?.[0]??D?.lastActiveNodeList?.[0]??D?.root??D?.renderTree?._node}},Ib=async(M,L=3)=>{let D=Jp(M);for(let F=1;!D.runtimeNode&&Fwindow.requestAnimationFrame(()=>K())),D=Jp(M);return D},zb=async(M,L,D)=>{let F=M==="DELETE_NODE"?"REMOVE_NODE":M,K=s?.instance;if(!K||typeof K.execCommand!="function")return{ok:!1,command:F,error:"runtime_unavailable",message:"runtime_instance_missing"};let{wantedNodeId:ee,runtimeNode:ue}=await Ib(L);if(!ue)return{ok:!1,command:F,error:"command_failed",message:`runtime_node_missing:${ee??"unknown"}`};try{if(K.renderer?.clearActiveNodeList?.(),K.renderer?.addNodeToActiveList?.(ue,!0),K.renderer&&(K.renderer.lastActiveNodeList=[ue]),K.renderer?.emitNodeActiveEvent?.(ue,[ue]),K.execCommand("SET_NODE_ACTIVE",ue,!0),F==="REMOVE_NODE")return{ok:!0,command:F,result:K.execCommand("REMOVE_NODE",[ue])};if(F==="INSERT_CHILD_NODE"||F==="INSERT_NODE")return{ok:!0,command:F,result:K.execCommand(F,!1,[ue],D?{uid:D.uid,text:D.text}:null)}}catch{return{ok:!1,command:F,error:"command_failed",message:"runtime_command_throw"}}return s?.execCommand(F)??{ok:!1,command:F,error:"unsupported_command",message:"runtime_command_unsupported"}},Rb=(M,L,D)=>{Qp(M,L),at="node",It=g0(D),bt=_b(D),ce=!0,R.dataset.contextMenuKind="node",R.dataset.contextMenuTargetNodeId=It??"",R.dataset.contextMenuTargetNodeKind=bt,Lb(D),It&&(l=It,d=It),b0()},Db=(M,L)=>{Qp(M,L),at="canvas",It=null,bt="canvas",ce=!0,R.dataset.contextMenuKind="canvas",R.dataset.contextMenuTargetNodeId="",R.dataset.contextMenuTargetNodeKind="canvas",b0()},y0=M=>{Y!==M&&(Y=M,R.dataset.chromeVisibility=M,Le())},e3=()=>{le=!0,R.dataset.keyboardShortcutArmed="true"},Ob=()=>{le=!1,R.dataset.keyboardShortcutArmed="false"},v0=()=>document.fullscreenElement===ve||ve.contains(document.fullscreenElement),Bb=()=>{let M=s?.instance;try{typeof M?.resize=="function"?M.resize():typeof M?.view?.resize=="function"&&M.view.resize(),M?.renderer?.setRootNodeCenter?.()}catch{R.dataset.fullscreenResizeStatus="failed";return}R.dataset.fullscreenResizeStatus="success"},Cc=()=>{let M=v0();R.dataset.fullscreenActive=M?"true":"false",ve.dataset.fullscreenActive=M?"true":"false",window.setTimeout(()=>{Bb(),Le()},80)},_c=()=>{s?.instance.setMode?.(m?"readonly":"edit"),R.dataset.readonly=m?"true":"false"},t3=M=>{R.dataset.searchStatus="ready",R.dataset.lastSearchQuery=M,O=!0,Le()},Pb=M=>{let L=typeof M=="number"?M:Number(String(M??"").replace("%","").trim());if(!Number.isFinite(L)){R.dataset.zoomInputStatus="invalid",Le();return}let D=Math.max(10,Math.min(500,Math.round(L))),F=s?.instance.view;if(!F?.setScale){R.dataset.zoomInputStatus="unsupported",Le();return}F.setScale(D/100,ve.clientWidth/2,ve.clientHeight/2),R.dataset.zoomInputStatus="success",qs(s?.getSnapshot()),Le()},i3=async M=>{if(M==="exitFullscreen"){document.fullscreenElement&&await document.exitFullscreen(),Cc();return}if(!(M!=="fullscreenCanvas"&&M!=="fullscreenPage")){if(typeof ve.requestFullscreen!="function"){R.dataset.commandStatus="fullscreen-unavailable",R.dataset.disabledReason="fullscreen-unavailable";return}await ve.requestFullscreen(),Cc()}},Lc=(M,L)=>{let D=Sc.toolbarActionMeta[M],F=D?.shortLabel??Eb[M]??M,K=D?.longLabel??Eb[M]??M,ee=D?.iconKey??M;return{id:M,label:F,longLabel:K,icon:YF[ee]??WF[M]??F.slice(0,1),iconKey:ee,priority:D?.priority??999,overflowGroup:D?.overflowGroup??"view",cluster:D?.cluster??"main",disabled:L.disabledActions[M]??!0}},Fb=()=>{let M=window.__MNOTE_MINDMAP_RUST_SHELL__;if(!M){f0(),R.dataset.uiShell="typescript-nodeview-dom",ir.dataset.uiShellSource="missing-rust-shell",ir.innerHTML='
    Leptos/Rust mindmap shell \u672A\u52A0\u8F7D
    ';return}let L=XF(q??ve.getBoundingClientRect().width),D=Vp({activeNodeId:l,readonly:m,runtimeCapabilities:["runtimeCommand","kernelCommand","compatPatch","localView"],shell:{chromeVisibility:Y,toolbarOverflow:{availableWidth:L.availableWidth,visibleActionIds:L.visibleActionIds,overflowActionIds:L.overflowActionIds,moreOpen:P&&L.overflowActionIds.length>0},fullscreen:{mode:v0()?"canvas":"none",isFullscreen:v0(),target:"mindmap-root",apiAvailable:typeof ve.requestFullscreen=="function"},sidebar:{activePanelId:g,panelOpen:x,triggerVisible:y,drawerWidth:S,collapsedByToggle:b},navigator:{searchOpen:O,minimapOpen:I,readonly:m,zoomPercent:Zp().zoomPercent}}}),{mindmapId:F}=et(),K=Zp(),ee=new Set(D.shell.toolbarOverflow.visibleActionIds),ue=new Set(D.shell.toolbarOverflow.overflowActionIds),te={mindmapId:F,toolbarGroups:[{id:"main",label:"\u4E3B\u5DE5\u5177",actions:Xp.filter(we=>ee.size===0||ee.has(we)).map(we=>Lc(we,D))},{id:"overflow",label:"\u66F4\u591A",actions:Xp.filter(we=>ue.has(we)).map(we=>Lc(we,D))},{id:"file",label:"\u6587\u4EF6",actions:OF.map(we=>Lc(we,D))}],sidebarPanels:Sc.sidebarPanels.map(we=>({id:we.id,kind:we.kind,label:we.label,icon:we.icon,active:we.id===g,bodyTitle:we.label,bodyCaption:we.runtimeCapability,options:we.options.map(Ge=>({id:Ge.id,label:Ge.label,actionId:Ge.actionId,value:Ge.value,controlType:Ge.controlType,preview:Ge.preview,description:Ge.description,readonly:Ge.readonly??!1,compatPath:Ge.compatPath})),bodyItems:we.id==="outline"?kb():[]})),navigator:{wordCount:K.wordCount,nodeCount:K.nodeCount,zoomPercent:K.zoomPercent,readonly:m,minimapOpen:I},shell:D.shell};f0(),ir.dataset.uiShellSource="leptos-rust-shell",Hi=M,nn=M.mount(ir,te),R.dataset.uiShell="leptos-rust-shell"},Le=()=>{if(u0(),!wi){Fb(),b0();return}f0(),R.dataset.uiShell="debug-fallback",Kn.dataset.testid="mindmap-command-toolbar",Kn.className="mnote-mindmap-command-toolbar",delete Kn.dataset.schemaSource,Zn.dataset.testid="mindmap-sidebar",Zn.className="mnote-mindmap-side-panel",delete Zn.dataset.schemaSource,Qn.dataset.testid="mindmap-bottom-bar",Qn.className="mnote-mindmap-bottom-bar",delete Qn.dataset.schemaSource;let M=a?Yp(a):"",L=f??M,F=s!==null?"":" disabled",K=["\u8282\u70B9\u6837\u5F0F","\u5BFC\u56FE\u6837\u5F0F","\u4E3B\u9898","\u7ED3\u6784","\u5927\u7EB2"].map((ee,ue)=>``).join("");Kn.innerHTML=`
    `,Zn.innerHTML=`${K}
    \u7ECF\u5178\u4E3B\u9898 \xB7 \u903B\u8F91\u7ED3\u6784
    `,Qn.innerHTML=`runtime \u8282\u70B9100%`},b0=()=>{let M=Xn.querySelector('[data-testid="mindmap-schema-context-menu"]');if(!ce){M?.remove();return}M||(M=document.createElement("div"),M.className="mnote-mindmap-context-menu",M.dataset.testid="mindmap-schema-context-menu",M.dataset.uiSource="typescript-nodeview-bridge",Xn.append(M)),M.dataset.contextMenuKind=at,M.dataset.contextMenuTargetNodeKind=bt;let D=Vp({activeNodeId:at==="node"?It??l:l,readonly:m,runtimeCapabilities:["runtimeCommand","kernelCommand","compatPatch","localView"]}),F=Sc.contextMenuItems.filter(te=>te.requiresNode===(at==="node")),K=te=>D.disabledActions[te]?!0:at!=="node"?!1:bt==="root"&&(te==="insertSiblingAfter"||te==="deleteNode")||bt==="generalization"&&(te==="insertChild"||te==="insertSiblingAfter"||te==="deleteNode"||te==="summary"||te==="associativeLine"||te==="expandCollapse");M.innerHTML=F.map(te=>{let we=K(te.actionId);return``}).join("");let ee=Math.min(Math.max(8,qi),Math.max(8,ve.clientWidth-M.offsetWidth-8)),ue=Math.min(Math.max(8,zt),Math.max(8,ve.clientHeight-M.offsetHeight-8));M.style.left=`${ee}px`,M.style.top=`${ue}px`},r3=()=>{u0(),R.dataset.stage="loading",mt.dataset.runtimeEngine="loading",mt.replaceChildren(),Le()},n3=M=>M==="mnote-web-tree-live"||M==="externalTreeLive"||M==="nodeViewUpdate",qb=M=>M==="mnote-web-tree-live"||M==="externalTreeLive",Hb=()=>R.dataset.stage==="ready"&&mt.dataset.runtimeReady==="true"&&a!==null,Ub=()=>{if(s?.instance.renderer?.textEdit?.isShowTextEdit?.())return!0;let L=document.activeElement;if(L instanceof HTMLElement&&L.closest(".smm-node-edit-wrap"))return!0;let D=document.querySelector('.smm-node-edit-wrap[contenteditable="true"]');return D instanceof HTMLElement&&D.style.display!=="none"&&D.getClientRects().length>0},s3=M=>{let L=s?.instance.renderer?.textEdit;if(!L?.isShowTextEdit?.())return!1;let D=Ds(L.getCurrentEditNode?.())??l??d,F=L.getEditText?.();if(R.dataset.lastRuntimeTextEditFlushReason=M,D&&typeof F=="string"&&F.length>0){R.dataset.lastRuntimeTextEditFlushNodeId=D,R.dataset.lastRuntimeTextEditFlushText=F;let{mindmapId:K}=et(),ee=an();ee&&a&&Gp({documentId:ee,mindmapId:K,commands:[{type:"updateText",mindmapId:K,nodeId:D,text:F}],projectionRevision:a.kernelRevision}).catch(te=>{R.dataset.lastRuntimeTextEditFlushError=te instanceof Error?te.message:String(te)})}return L.hideEditTextBox?.(),!0},a3=(M,L="text_edit")=>{R.dataset.runtimeProjectionDeferred=L,R.dataset.lastRuntimeProjectionDeferReason=M,R.dataset.runtimeProjectionDeferredAt=String(Date.now())},o3=()=>{R.dataset.runtimeProjectionDeferred="",R.dataset.lastRuntimeProjectionDeferReason="",R.dataset.runtimeProjectionDeferredAt=""},Ps=(M,L="")=>{R.dataset.backgroundProjectionRefresh=M,R.dataset.backgroundProjectionRefreshMessage=L,Le()},on=(M,L)=>{let{mindmapId:D}=et();u0(),R.dataset.stage=M,mt.dataset.runtimeEngine="error",mt.innerHTML=`
    ${Rs(M)}${Rs(D)}${Rs(L)}
    `,Le()},Fs=async(M,L)=>{if(R.dataset.commandCount=String(M.length),R.dataset.commandHasProjection=a?"true":"false",R.dataset.commandFailedAction="",R.dataset.commandFailedMessage="",M.length===0||!a){R.dataset.commandStatus="skipped";return}let{mindmapId:D}=et(),F=an();if(!F){on("command_failed",`mindmapId=${D}; documentId_missing`);return}R.dataset.commandStatus="pending";try{let K=await AF({documentId:F,mindmapId:D,commands:M,projectionRevision:a.kernelRevision});if(Ui)return;let ee=L?x0(K,L,et().rootNodeId):void 0;await d3(K,"commandRefresh",ee),f=null,R.dataset.commandStatus="success"}catch(K){let ee=K instanceof Error?K.message:String(K);R.dataset.commandStatus="failed",R.dataset.commandFailedAction=R.dataset.lastSchemaAction??"",R.dataset.commandFailedMessage=ee,on("command_failed",`mindmapId=${D}; ${ee}`)}},l3=async(M,L)=>{if(R.dataset.commandCount=String(M.length),R.dataset.commandHasProjection=a?"true":"false",R.dataset.commandFailedAction="",R.dataset.commandFailedMessage="",R.dataset.commandApplyMode="",R.dataset.commandLocalApplyErrors="",M.length===0||!a){R.dataset.commandStatus="skipped",R.dataset.commandApplyMode="skipped",$i();return}if(!EF(M)){R.dataset.commandApplyMode="refresh:unsupported",$i(),await Fs(M,L);return}let D=Sb();if(!D){R.dataset.commandApplyMode="refresh:no_blob",$i(),await Fs(M,L);return}let F=NF(D,M);if(F.errors.length>0){R.dataset.commandApplyMode="refresh:local_errors",R.dataset.commandLocalApplyErrors=F.errors.join("|"),$i(),await Fs(M,L);return}let{mindmapId:K}=et(),ee=an();if(!ee){on("command_failed",`mindmapId=${K}; documentId_missing`);return}R.dataset.commandStatus="pending",R.dataset.commandApplyMode="local";try{let ue=await Gp({documentId:ee,mindmapId:K,commands:M,projectionRevision:a.kernelRevision});if(Ui)return;if(Ab(F.data,ue.kernelRevision),L){let te=x0(a,L,et().rootNodeId);l=te,d=te,window.setTimeout(()=>{uo(te)},0)}f=null,R.dataset.commandStatus="success",Le()}catch(ue){let te=ue instanceof Error?ue.message:String(ue);R.dataset.commandStatus="failed",R.dataset.commandFailedAction=R.dataset.lastSchemaAction??"",R.dataset.commandFailedMessage=te,$i(),on("command_failed",`mindmapId=${K}; ${te}`)}},$b=async M=>{if(!a)return;let{mindmapId:L}=et(),D=an();if(!D){on("command_failed",`mindmapId=${L}; documentId_missing`);return}R.dataset.viewCommandStatus="pending";try{if(await Gp({documentId:D,mindmapId:L,commands:[CF(L,M)],projectionRevision:a.kernelRevision}),Ui)return;a.view=M,R.dataset.viewCommandStatus="success"}catch(F){on("command_failed",`mindmapId=${L}; ${F instanceof Error?F.message:String(F)}`)}},qs=M=>{let L=HF(M);L&&(A=L,R.dataset.lastViewPatch=JSON.stringify(L),C!==null&&window.clearTimeout(C),C=window.setTimeout(()=>{C=null;let D=A;A=null,D&&$b(D)},350))},jb=M=>{if(s){if(M==="editNode"){let L=s.instance.renderer,D=s.instance.keyCommand,F=L?.textEdit,K=h??L?.activeNodeList?.[0]??null;if(K&&typeof F?.show=="function")F.show({node:K,isFromKeyDown:!1}),R.dataset.editNodeStatus="opened";else if(typeof D?.getShortcutFn=="function"){let ee=D.getShortcutFn("F2")[0];typeof ee=="function"?(ee(),R.dataset.editNodeStatus="opened"):R.dataset.editNodeStatus="unsupported"}else R.dataset.editNodeStatus="unsupported";return}if(M==="centerRoot"&&s.instance.renderer?.setRootNodeCenter?.(),M==="zoomOut"&&s.instance.view?.narrow?.(),M==="zoomIn"&&s.instance.view?.enlarge?.(),M==="fitView"&&s.instance.view?.reset?.(),M==="fullscreenCanvas"||M==="fullscreenPage"||M==="exitFullscreen"){i3(M);return}if(M==="search"){O?(O=!1,R.dataset.lastSearchQuery="",R.dataset.searchStatus="closed",Le()):t3("");return}if(M==="showMenu"){y0("visible"),p0();return}if(M==="expandCollapse"){s.instance.renderer?.toggleActiveExpand?.(),R.dataset.contextMenuActionStatus="success",R.dataset.lastContextMenuAction=M,Le();return}if(M==="copyNodeText"){let L=It??l,D=a?GF(a.root,L):"";R.dataset.copiedNodeText=D,R.dataset.contextMenuActionStatus="success",R.dataset.lastContextMenuAction=M;return}M==="readonly"&&(m=!m,_c()),qs(s.getSnapshot()),Le()}},Gb=(M,L,D)=>{if(!s)return;let F=Nb(M,D);if(!F)return;let K=Lt(s.instance.getThemeConfig?.())?s.instance.getThemeConfig?.():{};if(D&&F.startsWith(`nodes.${D}.data.`)&&h){let ee=F.split(".data.")[1]??"";if(ee==="shape"){h.setShape?.(L);return}if(ee){let ue=ee==="fontWeight"&&L===!0?"bold":ee==="fontStyle"&&L===!0?"italic":L;h.setData?.({[ee]:ue});return}}if(F.startsWith("style.map.")){let ee=F.slice(10);if(!ee)return;s.instance.setThemeConfig?.({...K,[ee]:L},!1)}},Vb=(M,L,D)=>{if(!(!s||L.source!=="sidebar")){if(M==="setLayout"){s.instance.setLayout?.(L.value);return}if(M==="setTheme"){s.instance.setTheme?.(L.value);return}M==="painter"&&typeof L.compatPath=="string"&&Gb(L.compatPath,L.value,D)}},w0=async(M,L={})=>{let D=wb(M);if(!D||Vp({activeNodeId:at==="node"?It??l:l,readonly:m,runtimeCapabilities:["runtimeCommand","kernelCommand","compatPatch","localView"]}).disabledActions[M])return;if(R.dataset.lastSchemaAction=M,D.target==="localView"){jb(M);return}let K=UF(M),ee=at==="node"?It??d??l:d??l;if(D.requiresActiveNode){let Ge=kc(ee);Ge&&(ee=Ge,l=Ge,d=Ge)}R.dataset.lastSchemaActiveNodeId=ee??"";let ue=et().mindmapId;Vb(M,L,ee);let te=IF({actionId:M,mindmapId:ue,activeNodeId:ee,node:K,value:L.value??(M==="editNode"?Yp(a):!0),source:L.source,runtimeRevision:a?.kernelRevision??null,kernelRevision:a?.kernelRevision??null}),we=Nb(L.compatPath,ee);if(we&&(te={command:_F({mindmapId:ue,path:we,value:L.value??!0,source:L.source??"sidebar",actionId:M,runtimeRevision:a?.kernelRevision??null,kernelRevision:a?.kernelRevision??null})}),R.dataset.lastSchemaCommand=te?.command?JSON.stringify(te.command):"",R.dataset.lastSchemaRuntimeCommand=te?.runtimeCommand??"",!!te){if(D.target==="runtimeCommand"&&te.runtimeCommand){if(te.command){Bs(te.runtimeCommand),D.requiresActiveNode&&uo(ee);let Ge=await zb(te.runtimeCommand,ee,K),u3=K?.uid??(M==="deleteNode"?et().rootNodeId:ee);if(Ge?.ok){K?.uid&&(te.runtimeCommand==="INSERT_CHILD_NODE"||te.runtimeCommand==="INSERT_NODE")&&window.setTimeout(()=>{uo(K.uid)},0),l3([te.command],u3);return}R.dataset.commandApplyMode="refresh:runtime_failed",R.dataset.commandRuntimeError=Ge?.error??"unknown",R.dataset.commandRuntimeMessage=Ge&&"message"in Ge?Ge.message??"":"",$i(),Fs([te.command],u3);return}s?.execCommand(te.runtimeCommand);return}te.command&&Fs([te.command])}},Wb=M=>{let L=M.target instanceof Node&&ve.contains(M.target);return RF({debugChromeEnabled:wi,bridgeReady:!!s,readonly:m,isComposing:M.isComposing,isEditableTarget:qF(M.target),targetInsideRoot:L,keyboardShortcutArmed:le})},Yb=M=>{if(!Wb(M))return;let L=zF(M);if(!L)return;M.preventDefault(),M.stopPropagation(),M.stopImmediatePropagation(),R.dataset.lastShortcutKey=M.key,R.dataset.lastShortcutAction=L;let D=a?Mb(a,et().rootNodeId):et().rootNodeId;if(wb(L)?.requiresActiveNode){let K=kc(d??l);if(K===D&&(L==="insertSiblingAfter"||L==="deleteNode")){let ee=kc(null);ee&&ee!==D?K=ee:a&&(K=Tb(a.root)??K)}if(K)l=K,d=K;else{R.dataset.lastShortcutActionBlocked=L;return}}if((d??l)===D&&(L==="insertSiblingAfter"||L==="deleteNode")){R.dataset.lastShortcutActionBlocked=L;return}w0(L,{source:"toolbar"})},Xb=M=>{if(M.type==="node_active"){let K=l;l=Ds(M.args[0])??l,h=typeof M.args[0]=="object"&&M.args[0]!==null?M.args[0]:null,(!d||d===K)&&(d=l),Le();return}if(M.type==="view_data_change"||M.type==="scale"||M.type==="translate"){R.dataset.lastViewEvent=M.type,qs(M.snapshot??s?.getSnapshot());return}if(M.type!=="data_change"||!a||!M.snapshot)return;let L=Os(M.snapshot);R.dataset.lastDataChangeSeenAt=String(Date.now());let D=vb(c,Date.now());if(c=D.state,Wn(),R.dataset.lastDataChangeSuppressed=D.suppressed?"true":"false",R.dataset.lastDataChangeSuppressionExpired=D.expired?"true":"false",D.suppressed){o=L;return}let F=LF({mindmapId:et().mindmapId,previous:o??a.root,next:L});if(o=L,R.dataset.lastDataChangeDiffCommandCount=String(F.commands.length),R.dataset.lastDataChangeCompatPatchCount=String(F.compatPatches.length),F.commands.length===0&&F.compatPatches.length===0){R.dataset.lastDataChangeRefreshTriggered="false";return}R.dataset.lastDataChangeRefreshTriggered="false",l3([...F.commands,...F.compatPatches])},h3=M=>typeof M=="string"&&M.trim().length>0?M.trim():Lt(M)&&typeof M.template=="string"&&M.template.trim().length>0?M.template.trim():null,Kb=(M,L,D)=>{if(!s)return!1;let{mindmapId:F,rootNodeId:K}=et();if(R.dataset.mountedMindmapId&&R.dataset.mountedMindmapId!==F)return!1;let ee=vi(M.root);if(typeof s.instance.setData!="function")return!1;a={...M,root:Os(ee)},l=x0(M,D??l,K),h=null,d=l,o=Os(a.root);let ue=h3(M.layout),te=h3(M.theme),we=Lt(M.themeConfig)?M.themeConfig:null;return ue&&s.instance.setLayout?.(ue,!0),te&&s.instance.setTheme?.(te,!0),we&&s.instance.setThemeConfig?.(we,!0),s.instance.setData(ee),Mr+=1,R.dataset.runtimeProjectionApplyCount=String(Mr),R.dataset.lastRuntimeProjectionApplyReason=L,mt.dataset.runtimeEngine="simple-mind-map",mt.dataset.runtimeReady="true",R.dataset.stage="ready",Ac(F,s),_c(),window.setTimeout(()=>{uo(l)},0),Le(),!0},d3=async(M,L,D)=>{let{mindmapId:F,rootNodeId:K}=et();if(di&&(s?.instance.off?.("node_contextmenu",di),di=null),s?.destroy(),Ac(F,null),a={...M,root:Os(M.root)},l=x0(M,D,K),h=null,d=l,o=Os(a.root),co+=1,R.dataset.runtimeMountCount=String(co),R.dataset.lastRuntimeMountReason=L,R.dataset.mountedMindmapId=F,R.dataset.mountedRootNodeId=K,mt.dataset.runtimeEngine="simple-mind-map",mt.dataset.runtimeReady="false",mt.replaceChildren(),Le(),s=await SF({el:mt,projection:M,mode:m?"readonly":"edit",runtimeOptions:{fit:!0,mousewheelAction:"zoom",enableFreeDrag:!0,enableCtrlKeyNodeSelection:!0,useLeftKeySelectionRightKeyDrag:!0,createNewNodeBehavior:"activeOnly"},onEvent:Xb}),Ui){s.destroy();return}s.refreshProjection=c3,di=(...ee)=>{let[ue,te]=ee;ue instanceof MouseEvent&&(ue.preventDefault(),ue.stopPropagation(),Rb(ue.clientX,ue.clientY,te))},s.instance.on?.("node_contextmenu",di),mt.dataset.runtimeReady="true",R.dataset.stage="ready",Ac(F,s),_c(),window.setTimeout(()=>{uo(l)},0),Le()},M0=async(M="fetchScene",L=0)=>{let D=++n,{mindmapId:F,rootNodeId:K}=et();bi!==null&&(window.clearTimeout(bi),bi=null);let ee=an();if(!ee){if(R.dataset.documentIdResolveStatus="missing",R.dataset.documentIdResolveRetryCount=String(L),L<30){n3(M)||r3(),bi=window.setTimeout(()=>{bi=null,Ui||M0(M,L+1)},100);return}on("projection_load_failed",`mindmapId=${F}; documentId_missing`);return}R.dataset.documentIdResolveStatus="ready",R.dataset.lastFetchSceneReason=M;let ue=n3(M)&&Hb();if(ue&&qb(M)){a3(M,"live_signal"),Ps("deferred","usability_first");return}ue?Ps("pending"):r3();try{if(D!==n)return;let te=await kF({documentId:ee,mindmapId:F,endpoint:`/api/mindmap/${encodeURIComponent(ee)}/${encodeURIComponent(F)}?view=simple_mind_map_scene&queryName=mindmap.simple_mind_map_scene.get&rootNodeId=${encodeURIComponent(K)}`});if(D!==n||Ui)return;try{let we=bb({documentId:ee,mindmapId:F,data:vi(te.root)}),Ge=await fetch(we.endpoint,we.init);R.dataset.initialCreateOnlyStatus=Ge.ok?"success":"failed"}catch{R.dataset.initialCreateOnlyStatus="failed"}if(ue&&Ub()){a3(M),Ps("deferred","text_edit");return}if(ue&&Kb(te,M)){o3(),Ps("success");return}await d3(te,M),o3(),Ps("success")}catch(te){if(ue){Ps("failed",te instanceof Error?te.message:String(te));return}on("projection_load_failed",`mindmapId=${F}; ${te instanceof Error?te.message:String(te)}`)}},c3=async(M="externalTreeLive")=>{if(!Ui){if(tr){sn=M;return}tr=!0;try{await M0(M)}finally{tr=!1;let L=sn;sn=null,L&&!Ui&&c3(L)}}};return Je.addEventListener("pointerdown",M=>M.stopPropagation()),Je.addEventListener("mousedown",M=>M.stopPropagation()),ve.addEventListener("pointerleave",()=>{m0(),!ce&&Y==="visible"&&y0(Ec(Y,"pointerLeave"))}),ve.addEventListener("pointerenter",()=>{Y=Ec(Y,"pointerEnter"),R.dataset.chromeVisibility=Y}),ve.addEventListener("pointerdown",()=>{e3(),Y!=="visible"&&y0(Ec(Y,"restoreClick"))},!0),ve.addEventListener("click",()=>{e3(),Y!=="visible"&&y0(Ec(Y,"restoreClick"))}),Je.addEventListener("mnote:mindmap-shell:action",M=>{let L=M.detail,D=L?.actionId;if(D==="search"){if(typeof L?.value=="string"){t3(L.value);return}if(L?.value===!1){O=!1,R.dataset.lastSearchQuery="",R.dataset.searchStatus="closed",Le();return}}D&&(m0(),w0(D,L))}),Je.addEventListener("mnote:mindmap-shell:zoom",M=>{let L=M.detail;Pb(L?.percent)}),Je.addEventListener("mnote:mindmap-shell:minimap",M=>{let L=M.detail;I=typeof L?.open=="boolean"?L.open:!I,R.dataset.minimapOpen=I?"true":"false",Le()}),Je.addEventListener("mnote:mindmap-shell:toolbar-overflow",M=>{let L=M.detail;P=typeof L?.moreOpen=="boolean"?L.moreOpen:!P,Le()}),Je.addEventListener("mnote:mindmap-shell:panel",M=>{let L=M.detail,D=L?.event??"togglePanel";if(D==="restoreTrigger"){y=!0,x=!0,b=!1,Le();return}if(D==="hideTrigger"){y=!1,x=!1,b=!0,Le();return}if(D==="closeDrawer"){x=!1,b=!1,Le();return}L?.panelId&&(L.panelId===g?x=!x:(g=L.panelId,x=!0),y=!0,b=!1,Le())}),ke=()=>Cc(),document.addEventListener("fullscreenchange",ke),Q=()=>{s3("page_lifecycle")},window.addEventListener("pagehide",Q),window.addEventListener("beforeunload",Q),Je.addEventListener("input",M=>{if(!wi)return;let L=M.target;L instanceof HTMLInputElement&&L.dataset.testid==="mindmap-command-text-input"&&(f=L.value)}),typeof ResizeObserver<"u"&&(W=new ResizeObserver(M=>{let L=M[0]?.contentRect.width??null;L===null||Math.abs(L-(q??0))<1||(q=L,wi||Le())}),W.observe(ve)),ne=M=>{let L=M.target;if((!(L instanceof Node)||!ve.contains(L))&&Ob(),ce){if(L instanceof Node){let D=Xn.querySelector('[data-testid="mindmap-schema-context-menu"]');if(D instanceof HTMLElement&&D.contains(L))return}p0()}P&&(L instanceof Node&&ir.contains(L)||m0())},re=M=>{if(M.key==="Escape"){if(ce){M.preventDefault(),p0();return}if(v0()){M.preventDefault(),i3("exitFullscreen");return}P&&(M.preventDefault(),m0())}},oe=M=>Yb(M),document.addEventListener("pointerdown",ne),document.addEventListener("keydown",re),document.addEventListener("keydown",oe,!0),Je.addEventListener("click",M=>{M.stopPropagation();let L=M.target;if(!(L instanceof HTMLElement))return;let D=L.closest("button");if(D instanceof HTMLButtonElement){if(!wi){M.preventDefault();let F=D.dataset.mindmapContextActionId;if(F){if(D.disabled||D.dataset.disabled==="true")return;p0(),w0(F);return}if(D.closest('[data-testid="mindmap-rust-shell"]'))return;let K=D.dataset.mindmapActionId;K&&w0(K);let ee=D.dataset.mindmapSidebarPanelId;ee&&(g=ee,Le());return}if(D.dataset.testid==="mindmap-command-update-text"){M.preventDefault();let F=ve.querySelector('[data-testid="mindmap-command-text-input"]'),K=f??(F instanceof HTMLInputElement?F.value:Yp(a));R.dataset.lastCommandText=K,Fs([{type:"updateText",mindmapId:et().mindmapId,nodeId:l??et().rootNodeId,text:K}])}D.dataset.testid==="mindmap-command-add-child"&&(M.preventDefault(),s?.execCommand("INSERT_CHILD_NODE")),D.dataset.testid==="mindmap-command-add-sibling-after"&&(M.preventDefault(),s?.execCommand("INSERT_NODE")),D.dataset.testid==="mindmap-command-delete-node"&&(M.preventDefault(),s?.execCommand("REMOVE_NODE")),D.dataset.testid==="mindmap-toolbar-undo"&&(M.preventDefault(),s?.execCommand("BACK")),D.dataset.testid==="mindmap-toolbar-redo"&&(M.preventDefault(),s?.execCommand("FORWARD")),D.dataset.testid==="mindmap-toolbar-summary"&&(M.preventDefault(),s?.execCommand("ADD_GENERALIZATION")),D.dataset.testid==="mindmap-bottom-center-root"&&(M.preventDefault(),s?.instance.renderer?.setRootNodeCenter?.(),qs(s?.getSnapshot())),D.dataset.testid==="mindmap-bottom-zoom-out"&&(M.preventDefault(),s?.instance.view?.narrow?.(),qs(s?.getSnapshot())),D.dataset.testid==="mindmap-bottom-zoom-in"&&(M.preventDefault(),s?.instance.view?.enlarge?.(),qs(s?.getSnapshot()))}}),Je.addEventListener("contextmenu",M=>{if(wi)return;let L=M.target;if(L instanceof Element&&(L.closest(".smm-node")||L.closest('[class*="generalization_"]'))){M.preventDefault();return}M.preventDefault(),M.stopPropagation(),Db(M.clientX,M.clientY)},!0),c0.append(mt),wi?(Yn.append(mt,Zn),R.append(Kn,Yn,Qn)):(Xn.append(ir),Yn.append(c0,Xn),R.append(Yn)),ve.append(R),Je.append(ve),M0("initial"),{dom:Je,update(M){if(M.type.name!==r.type.name)return!1;let L=Kp(r);return r=M,r.attrs.mnoteBlockType!=="mindmap"?!1:(u0(),Kp(r)!==L&&M0("nodeViewUpdate"),!0)},stopEvent:()=>!0,ignoreMutation:()=>!0,destroy(){s3("node_view_destroy"),Ui=!0,bi!==null&&window.clearTimeout(bi),C!==null&&window.clearTimeout(C),W?.disconnect(),ne&&document.removeEventListener("pointerdown",ne),re&&document.removeEventListener("keydown",re),oe&&document.removeEventListener("keydown",oe,!0),ke&&document.removeEventListener("fullscreenchange",ke),Q&&(window.removeEventListener("pagehide",Q),window.removeEventListener("beforeunload",Q)),di&&s?.instance.off?.("node_contextmenu",di);let{mindmapId:M}=et();Ac(M,null),s?.destroy(),f0(),s=null}}}}function ZF(){return({node:i})=>{let e=i,t=document.createElement("p"),r=()=>{let n=e.attrs.blockId;typeof n=="string"&&n.length>0?(t.dataset.blockId=n,t.id=n):(t.removeAttribute("data-block-id"),t.removeAttribute("id"))};return r(),{dom:t,contentDOM:t,update(n){return n.type.name!==e.type.name||n.attrs.mnoteBlockType==="mindmap"?!1:(e=n,r(),!0)}}}}var QF=p3.extend({addAttributes(){return{...this.parent?.(),blockId:{default:null,parseHTML:i=>i.getAttribute("data-block-id"),renderHTML:i=>BF(i.blockId)},mnoteBlockType:{default:null,parseHTML:i=>i.getAttribute("data-mnote-block-type"),renderHTML:i=>PF(i)},mindmapId:{default:null,parseHTML:i=>i.getAttribute("data-mnote-mindmap-id"),renderHTML:()=>({})},rootNodeId:{default:null,parseHTML:i=>i.getAttribute("data-mnote-root-node-id"),renderHTML:()=>({})},projectionVersion:{default:null,parseHTML:i=>Number(i.getAttribute("data-mnote-projection-version")??1),renderHTML:()=>({})}}},addNodeView(){let i=KF(this.editor),e=ZF();return({node:t,getPos:r})=>t.attrs.mnoteBlockType==="mindmap"?i({node:t,getPos:r}):e({node:t})}}),JF={name:"paragraph",create:()=>QF,commands:{set_paragraph:i=>i.chain().focus().setParagraph().run()},selection_keys:["paragraph"],selection_state:i=>({paragraph:i.isActive("paragraph")})};function Jne(){y3(JF)}export{Jne as register_paragraph,FF as resolveCurrentDocumentId}; + `,minExportImgCanvasScale:2,addContentToHeader:null,addContentToFooter:null,handleBeingExportSvg:null,maxCanvasSize:16384,defaultAssociativeLineText:"\u5173\u8054",associativeLineIsAlwaysAboveNode:!0,associativeLineInitPointsPosition:{from:"",to:""},enableAdjustAssociativeLinePoints:!0,beforeAssociativeLineConnection:null,disableTouchZoom:!1,minTouchZoomScale:20,maxTouchZoomScale:-1,isLimitMindMapInCanvasWhenHasScrollbar:!0,isOnlySearchCurrentRenderNodes:!1,beforeCooperateUpdate:null,rainbowLinesConfig:{open:!1,colorsList:[]},demonstrateConfig:null,enableEditFormulaInRichTextEdit:!0,katexFontPath:"https://unpkg.com/katex@0.16.11/dist/",getKatexOutputType:null,transformRichTextOnEnterEdit:null,beforeHideRichTextEdit:null,outerFramePaddingX:10,outerFramePaddingY:10,defaultOuterFrameText:"\u5916\u6846",onlyPainterNodeCustomStyles:!1,beforeDeleteNodeImg:null,imgResizeBtnSize:25,minImgResizeWidth:50,minImgResizeHeight:50,maxImgResizeWidthInheritTheme:!1,maxImgResizeWidth:1/0,maxImgResizeHeight:1/0,customDeleteBtnInnerHTML:"",customResizeBtnInnerHTML:""}});var gb={};tt(gb,{default:()=>cF});var qp,It,Hp,cF,xb=T(()=>{Bv();Uv();sb();qp=yt(j0());ab();ih();lb();ub();mb();Ge();wt();pe();qo();pb();It=class i{constructor(e={}){if(i.instanceCount++,this.opt=this.handleOpt((0,qp.default)(Fp,e)),this.opt.data=this.handleData(this.opt.data),this.el=this.opt.el,!this.el)throw new Error("\u7F3A\u5C11\u5BB9\u5668\u5143\u7D20el");this.getElRectInfo(),this.initWidth=this.width,this.initHeight=this.height,this.cssEl=null,this.cssTextMap={},this.nodeInnerPrefixList=[],this.nodeInnerPostfixList=[],this.editNodeClassList=[],this.extendShapeList=[],this.initContainer(),this.initTheme(),this.initCache(),i.pluginList.filter(t=>t.preload).forEach(t=>{this.initPlugin(t)}),this.event=new Hv({mindMap:this}),this.keyCommand=new p0({mindMap:this}),this.command=new cb({mindMap:this}),this.renderer=new nb({mindMap:this}),this.view=new Ov({mindMap:this}),this.batchExecution=new fb,i.pluginList.filter(t=>!t.preload).forEach(t=>{this.initPlugin(t)}),this.addCss(),this.render(this.opt.fit?()=>this.view.fit():()=>{}),this.opt.addHistoryOnInit&&this.opt.data&&this.command.addHistory()}handleOpt(e){return eu.includes(e.layout)||(e.layout=C.LAYOUT.LOGICAL_STRUCTURE),e.theme=e.theme&&$n[e.theme]?e.theme:"default",e}handleData(e){return Kt(e)||Object.keys(e).length<=0?null:(e=Qt(e||{}),e.data&&!e.data.expand&&(e.data.expand=!0),cs([e],!1,null,!0),e)}initContainer(){let{associativeLineIsAlwaysAboveNode:e}=this.opt;this.el.classList.add("smm-mind-map-container");let t=()=>{this.associativeLineDraw=this.draw.group(),this.associativeLineDraw.addClass("smm-associative-line-container")};this.svg=ke().addTo(this.el).size(this.width,this.height),this.draw=this.svg.group(),this.draw.addClass("smm-container"),this.lineDraw=this.draw.group(),this.lineDraw.addClass("smm-line-container"),e||t(),this.nodeDraw=this.draw.group(),this.nodeDraw.addClass("smm-node-container"),e&&t(),this.otherDraw=this.draw.group(),this.otherDraw.addClass("smm-other-container")}clearDraw(){this.lineDraw.clear(),this.associativeLineDraw.clear(),this.nodeDraw.clear(),this.otherDraw.clear()}appendCss(e,t){this.cssTextMap[e]=t,this.removeCss(),this.addCss()}removeAppendCss(e){this.cssTextMap[e]&&(delete this.cssTextMap[e],this.removeCss(),this.addCss())}joinCss(){return $3+Object.keys(this.cssTextMap).map(e=>this.cssTextMap[e]).join(` +`)}addCss(){this.cssEl=document.createElement("style"),this.cssEl.type="text/css",this.cssEl.innerHTML=this.joinCss(),document.head.appendChild(this.cssEl)}removeCss(){this.cssEl&&document.head.removeChild(this.cssEl)}checkEditNodeClassIndex(e){return this.editNodeClassList.findIndex(t=>t===e)}addEditNodeClass(e){this.checkEditNodeClassIndex(e)===-1&&this.editNodeClassList.push(e)}deleteEditNodeClass(e){let t=this.checkEditNodeClassIndex(e);t!==-1&&this.editNodeClassList.splice(t,1)}render(e,t=""){this.initTheme(),this.renderer.render(e,t)}reRender(e,t=""){this.renderer.reRender=!0,this.renderer.clearCache(),this.clearDraw(),this.render(e,t)}getElRectInfo(){if(this.elRect=this.el.getBoundingClientRect(),this.width=this.elRect.width,this.height=this.elRect.height,this.width<=0||this.height<=0)throw new Error("\u5BB9\u5668\u5143\u7D20el\u7684\u5BBD\u9AD8\u4E0D\u80FD\u4E3A0")}resize(){let e=this.width,t=this.height;this.getElRectInfo(),this.svg.size(this.width,this.height),(e!==this.width||t!==this.height)&&(this.demonstrate?this.demonstrate.isInDemonstrate||this.render():this.render()),this.emit("resize")}on(e,t){this.event.on(e,t)}emit(e,...t){this.event.emit(e,...t)}off(e,t){this.event.off(e,t)}initCache(){this.commonCaches={measureCustomNodeContentSizeEl:null,measureRichtextNodeTextSizeEl:null}}initTheme(){this.themeConfig=Iu($n[this.opt.theme]||$n.default,this.opt.themeConfig),Wo.setBackgroundStyle(this.el,this.themeConfig)}setTheme(e,t=!1){this.execCommand("CLEAR_ACTIVE_NODE"),this.opt.theme=e,t||this.render(null,C.CHANGE_THEME),this.emit("view_theme_change",e)}getTheme(){return this.opt.theme}setThemeConfig(e,t=!1){let r=P2(this.themeConfig,e);if(this.opt.themeConfig=e,!t){let n=S2(r);this.render(null,n?"":C.CHANGE_THEME)}}getCustomThemeConfig(){return this.opt.themeConfig}getThemeConfig(e){return e===void 0?this.themeConfig:this.themeConfig[e]}getConfig(e){return e===void 0?this.opt:this.opt[e]}updateConfig(e={}){this.emit("before_update_config",this.opt);let t={...this.opt};this.opt=this.handleOpt(qp.default.all([Fp,this.opt,e])),this.emit("after_update_config",this.opt,t)}getLayout(){return this.opt.layout}setLayout(e,t=!1){eu.includes(e)||(e=C.LAYOUT.LOGICAL_STRUCTURE),this.opt.layout=e,this.view.reset(),this.renderer.setLayout(),t||this.render(null,C.CHANGE_LAYOUT),this.emit("layout_change",e)}execCommand(...e){this.command.exec(...e)}updateData(e){e=this.handleData(e),this.emit("before_update_data",e),this.renderer.setData(e),this.render(),this.command.addHistory(),this.emit("update_data",e)}setData(e){e=this.handleData(e),this.emit("before_set_data",e),this.opt.data=e,this.execCommand("CLEAR_ACTIVE_NODE"),this.command.clearHistory(),this.command.addHistory(),this.renderer.setData(e),this.reRender(),this.emit("set_data",e)}setFullData(e){e.root&&this.setData(e.root),e.layout&&this.setLayout(e.layout),e.theme&&(e.theme.template&&this.setTheme(e.theme.template),e.theme.config&&this.setThemeConfig(e.theme.config)),e.view&&this.view.setTransformData(e.view)}getData(e){let t=this.command.getCopyData(),r={};return e?r={layout:this.getLayout(),root:t,theme:{template:this.getTheme(),config:this.getCustomThemeConfig()},view:this.view.getTransformData()}:r=t,Qt(r)}async export(...e){try{if(!this.doExport)throw new Error("\u8BF7\u6CE8\u518CExport\u63D2\u4EF6\uFF01");return await this.doExport.export(...e)}catch(t){this.opt.errorHandler(Ti.EXPORT_ERROR,t)}}toPos(e,t){return{x:e-this.elRect.left,y:t-this.elRect.top}}setMode(e){if(![C.MODE.READONLY,C.MODE.EDIT].includes(e))return;let t=e===C.MODE.READONLY;t!==this.opt.readonly&&(t&&(this.renderer.textEdit.isShowTextEdit()&&(this.renderer.textEdit.hideEditTextBox(),this.command.originAddHistory()),this.execCommand("CLEAR_ACTIVE_NODE")),this.opt.readonly=t,!t&&this.command.history.length<=0&&this.command.originAddHistory(),this.emit("mode_change",e))}getSvgData({paddingX:e=0,paddingY:t=0,ignoreWatermark:r=!1,addContentToHeader:n,addContentToFooter:s,node:a}={}){let{watermarkConfig:o,openPerformance:l}=this.opt;l&&this.renderer.forceLoadNode(a);let{cssTextList:h,header:d,headerHeight:c,footer:f,footerHeight:m}=Y2({addContentToHeader:n,addContentToFooter:s}),g=this.svg,x=this.draw,y=g.width(),b=g.height(),E=x.transform(),k=this.elRect;x.scale(1/E.scaleX,1/E.scaleY);let L=x.rbox(),I=null;a&&(I=Lu(a,L.x,L.y,e,t));let O=0;L.width+=e*2,L.height+=t*2+O+c+m,x.translate(e,t),g.size(L.width,L.height),x.translate(-L.x+k.left,-L.y+k.top);let q=g.clone(),F=this.watermark&&this.watermark.hasWatermark();if(!r&&F){this.watermark.isInExport=!0;let{onlyExport:re}=o;L.width>y||L.height>b?(this.width=L.width,this.height=L.height,this.watermark.onResize(),q=g.clone(),this.width=y,this.height=b,this.watermark.onResize()):re&&(this.watermark.onResize(),q=g.clone()),re&&this.watermark.clear(),this.watermark.isInExport=!1}[this.joinCss(),...h].forEach(re=>{q.add(ke(``))}),d&&c>0&&(q.findOne(".smm-container").translate(0,c),d.width(L.width),d.y(t),q.add(d,0)),f&&m>0&&(f.width(L.width),f.y(L.height-t-m),q.add(f));let W=g.find("defs"),ne=q.find("defs");return W.forEach((re,le)=>{let Ce=ne[le];if(!Ce)return;let te=re.children(),Q=Ce.children();for(let oe=0;oer.name===e.name)||this.extendShapeList.push(e)}removeShape(e){let t=this.extendShapeList.findIndex(r=>r.name===e);t!==-1&&this.extendShapeList.splice(t,1)}getSvgObjects(){return{SVG:ke,G:Le,Rect:Ve}}addPlugin(e,t){i.hasPlugin(e)===-1&&i.usePlugin(e,t),this.initPlugin(e)}removePlugin(e){let t=i.hasPlugin(e);t!==-1&&(i.pluginList.splice(t,1),this[e.instanceName]&&(this[e.instanceName].beforePluginRemove&&this[e.instanceName].beforePluginRemove(),delete this[e.instanceName]))}initPlugin(e){this[e.instanceName]||(this[e.instanceName]=new e({mindMap:this,pluginOpt:e.pluginOpt}))}destroy(){this.emit("beforeDestroy"),this.renderer.textEdit.hideEditTextBox(),this.renderer.textEdit.removeTextEditEl(),[...i.pluginList].forEach(e=>{this[e.instanceName]&&this[e.instanceName].beforePluginDestroy&&this[e.instanceName].beforePluginDestroy(),this[e.instanceName]=null}),this.event.unbind(),this.svg.remove(),Wo.removeBackgroundStyle(this.el),this.el.classList.remove("smm-mind-map-container"),this.el.innerHTML="",this.el=null,this.removeCss(),i.instanceCount--}},Hp=[];It.extendNodeDataNoStylePropList=(i=[])=>{Hp.push(...i),js.push(...i)};It.resetNodeDataNoStylePropList=()=>{Hp.forEach(i=>{let e=js.findIndex(t=>t===i);e!==-1&&js.splice(e,1)}),Hp=[]};It.pluginList=[];It.usePlugin=(i,e={})=>(It.hasPlugin(i)!==-1||(i.pluginOpt=e,It.pluginList.push(i)),It);It.hasPlugin=i=>It.pluginList.findIndex(e=>e===i);It.instanceCount=0;It.defineTheme=(i,e={})=>{if($n[i])return new Error("\u8BE5\u4E3B\u9898\u540D\u79F0\u5DF2\u5B58\u5728");$n[i]=Iu(G0,e)};It.removeTheme=i=>{$n[i]&&($n[i]=null)};cF=It});var $c=globalThis.__LEPTOS_TIPTAP_BRIDGE__;if($c==null||$c.modules==null)throw new Error("leptos-tiptap bridge bindings are unavailable");var j=$c.modules["@tiptap/core"];if(j==null)throw new Error('leptos-tiptap bridge module "@tiptap/core" is unavailable');var Aq=j.CommandManager,kq=j.Editor,Cq=j.Extension,_q=j.InputRule,Lq=j.Mark,A3=j.Node,Iq=j.NodePos,zq=j.NodeView,Rq=j.PasteRule,Dq=j.Tracker,Oq=j.callOrReturn,Bq=j.canInsertNode,Pq=j.combineTransactionSteps,Fq=j.createChainableState,qq=j.createDocument,Hq=j.createNodeFromContent,Uq=j.createStyleTag,$q=j.defaultBlockAt,jq=j.deleteProps,Gq=j.elementFromString,Wq=j.escapeForRegEx,Vq=j.extensions,Yq=j.findChildren,Xq=j.findChildrenInRange,Kq=j.findDuplicates,Zq=j.findParentNode,Qq=j.findParentNodeClosestToPos,Jq=j.fromString,eH=j.generateHTML,tH=j.generateJSON,iH=j.generateText,rH=j.getAttributes,nH=j.getAttributesFromExtensions,sH=j.getChangedRanges,aH=j.getDebugJSON,oH=j.getExtensionField,lH=j.getHTMLFromFragment,hH=j.getMarkAttributes,dH=j.getMarkRange,cH=j.getMarkType,uH=j.getMarksBetween,fH=j.getNodeAtPosition,mH=j.getNodeAttributes,pH=j.getNodeType,gH=j.getRenderedAttributes,xH=j.getSchema,yH=j.getSchemaByResolvedExtensions,vH=j.getSchemaTypeByName,bH=j.getSchemaTypeNameByName,wH=j.getSplittedAttributes,MH=j.getText,TH=j.getTextBetween,NH=j.getTextContentFromNodes,EH=j.getTextSerializersFromSchema,SH=j.injectExtensionAttributesToParseRule,AH=j.inputRulesPlugin,kH=j.isActive,CH=j.isAtEndOfNode,_H=j.isAtStartOfNode,LH=j.isEmptyObject,IH=j.isExtensionRulesEnabled,zH=j.isFunction,RH=j.isList,DH=j.isMacOS,OH=j.isMarkActive,BH=j.isNodeActive,PH=j.isNodeEmpty,FH=j.isNodeSelection,qH=j.isNumber,HH=j.isPlainObject,UH=j.isRegExp,$H=j.isSafari,jH=j.isString,GH=j.isTextSelection,WH=j.isiOS,VH=j.markInputRule,YH=j.markPasteRule,k3=j.mergeAttributes,XH=j.mergeDeep,KH=j.minMax,ZH=j.nodeInputRule,QH=j.nodePasteRule,JH=j.objectIncludes,eU=j.pasteRulesPlugin,tU=j.posToDOMRect,iU=j.removeDuplicates,rU=j.resolveFocusPosition,nU=j.rewriteUnknownContent,sU=j.selectionToInsertionEnd,aU=j.splitExtensions,oU=j.textInputRule,lU=j.textPasteRule,hU=j.textblockTypeInputRule,dU=j.wrappingInputRule;var C3=A3.create({name:"paragraph",priority:1e3,addOptions(){return{HTMLAttributes:{}}},group:"block",content:"inline*",parseHTML(){return[{tag:"p"}]},renderHTML({HTMLAttributes:i}){return["p",k3(this.options.HTMLAttributes,i),0]},addCommands(){return{setParagraph:()=>({commands:i})=>i.setNode(this.name)}},addKeyboardShortcuts(){return{"Mod-Alt-0":()=>this.editor.commands.setParagraph()}}});var _3="__LEPTOS_TIPTAP_BRIDGE__";function kw(){let i=globalThis,e=i[_3];if(e!=null)return e;let t={modules:{},registerExtension:()=>{throw new Error("leptos-tiptap bridge runtime is not initialized")}};return i[_3]=t,t}function L3(){return kw()}function I3(i){L3().registerExtension(i)}var Wc={};tt(Wc,{applyMetadataOnlyMindmapCommands:()=>O3,applyMindmapCommandsLocally:()=>zw,isLocallyApplicableMindmapCommandSet:()=>Iw,isMetadataOnlyMindmapCommandSet:()=>Lw});var Cw={data:{text:"\u4E2D\u5FC3\u4E3B\u9898"},children:[]},vt=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),ir=i=>JSON.parse(JSON.stringify(i)),R3=i=>{let e=vt(i)?ir(i):ir(Cw);return vt(e.data)||(e.data={text:"\u4E2D\u5FC3\u4E3B\u9898"}),Array.isArray(e.children)||(e.children=[]),e},D3=(i,e)=>{if(!vt(i)||!vt(e))return ir(e);let t={...i};return Object.entries(e).forEach(([r,n])=>{if(n===null){delete t[r];return}t[r]=vt(t[r])&&vt(n)?D3(t[r],n):ir(n)}),t},jc=(i,e,t)=>{let r=e.split(".").filter(Boolean);if(r.length===0)return!1;let n=i;for(let s of r.slice(0,-1))vt(n[s])||(n[s]={}),n=n[s];return n[r[r.length-1]]=ir(t),!0},I0=(i,e)=>{if(!vt(i))return null;let t=vt(i.data)?i.data:null;if(typeof t?.uid=="string"&&t.uid===e)return i;let r=Array.isArray(i.children)?i.children:[];for(let n of r){let s=I0(n,e);if(s)return s}return null},Gc=(i,e)=>{for(let t=0;t({data:{uid:i.uid??`node_${Date.now().toString(36)}`,text:i.text,...typeof i.hyperlink=="string"?{hyperlink:i.hyperlink}:{},...typeof i.note=="string"?{note:i.note}:{},...Array.isArray(i.refs)?{refs:ir(i.refs)}:{}},children:[]}),_w=(i,e)=>{let t=String(e.path||"").trim();if(!t)return!1;if(t.startsWith("root.data.")){let n=t.slice(10),s=vt(i.data)?i.data:i.data={};return jc(s,n,e.value)}if(t.startsWith("nodes.")){let[,n,s,...a]=t.split(".");if(!n||s!=="data"||a.length===0)return!1;let o=I0(i,n);if(!o)return!1;let l=vt(o.data)?o.data:o.data={};return jc(l,a.join("."),e.value)}let r=vt(i.compatPayload)?i.compatPayload:i.compatPayload={};return jc(r,t,e.value)},Lw=i=>Array.isArray(i)&&i.every(e=>!vt(e)||typeof e.type!="string"?!1:["setLayout","setTheme","patchView","compatPayloadPatch"].includes(e.type)),Iw=i=>Array.isArray(i)&&i.every(e=>!vt(e)||typeof e.type!="string"?!1:["updateText","insertChild","insertSiblingAfter","deleteNode","setLayout","setTheme","patchView","compatPayloadPatch"].includes(e.type)),O3=(i,e)=>{let t=R3(i),r=[],n=0;return e.forEach(s=>{if(s.type==="setLayout"){t.layout=ir(s.layout),n+=1;return}if(s.type==="setTheme"){t.theme=ir(s.theme),s.themeConfig!==void 0&&s.themeConfig!==null&&(t.themeConfig=ir(s.themeConfig)),n+=1;return}if(s.type==="patchView"){t.view=D3(t.view??{},s.patch),n+=1;return}s.type==="compatPayloadPatch"&&(_w(t,s)?n+=1:r.push(`\u65E0\u6CD5\u5E94\u7528 compatPayloadPatch:${s.path}`))}),{applied:n,data:t,errors:r}},zw=(i,e)=>{let t=R3(i),r=[],n=0;return e.forEach(s=>{if(s.type==="setLayout"||s.type==="setTheme"||s.type==="patchView"||s.type==="compatPayloadPatch"){let a=O3(t,[s]);Object.assign(t,a.data),n+=a.applied,r.push(...a.errors);return}if(s.type==="updateText"){let a=I0(t,s.nodeId);if(!a){r.push(`\u672A\u627E\u5230\u8282\u70B9:${s.nodeId}`);return}let o=vt(a.data)?a.data:a.data={};o.text=s.text,n+=1;return}if(s.type==="insertChild"){let a=I0(t,s.parentNodeId);if(!a){r.push(`\u672A\u627E\u5230\u7236\u8282\u70B9:${s.parentNodeId}`);return}(Array.isArray(a.children)?a.children:a.children=[]).push(z3(s.node)),n+=1;return}if(s.type==="insertSiblingAfter"){let a=Gc([t],s.targetNodeId);if(!a){r.push(`\u672A\u627E\u5230\u540C\u7EA7\u8282\u70B9:${s.targetNodeId}`);return}if(a.parentChildren[a.index]===t){r.push("\u6839\u8282\u70B9\u4E0D\u652F\u6301\u63D2\u5165\u540C\u7EA7\u8282\u70B9");return}a.parentChildren.splice(a.index+1,0,z3(s.node)),n+=1;return}if(s.type==="deleteNode"){let a=Gc([t],s.nodeId);if(!a){r.push(`\u672A\u627E\u5230\u5220\u9664\u8282\u70B9:${s.nodeId}`);return}if(a.parentChildren[a.index]===t){r.push("\u6839\u8282\u70B9\u4E0D\u652F\u6301\u5220\u9664");return}a.parentChildren.splice(a.index,1),n+=1}}),{applied:n,data:t,errors:r}};var Wp={};tt(Wp,{MindmapCommandBridgeError:()=>ho,createLeptosMindmapAdapter:()=>AF,createMindmapAdapterProjectionEndpoint:()=>Mb,createMindmapCommandApplyEndpoint:()=>jp,executeMindmapCommandApply:()=>Tb,executeMindmapCommandApplyAndRefreshProjection:()=>EF,executeMindmapDataPutAndRefreshProjection:()=>SF,isMindmapAdapterProjection:()=>$p,requestMindmapAdapterProjection:()=>Gp});var Xc={};tt(Xc,{DEFAULT_MINDMAP_LAYOUT:()=>z0,DEFAULT_MINDMAP_THEME:()=>R0,buildMindmapEditorScene:()=>Dw,buildMindmapProjection:()=>Rw,buildMindmapSimpleMindMapScene:()=>Ow,canonicalizeMindmapData:()=>fi,createDefaultMindmapThemeConfig:()=>D0,defaultMindmapData:()=>$s,extractMindmapTitle:()=>Yc,normalizeMindmapData:()=>Vc,summarizeMindmapProjectionNodes:()=>B3});var $s={data:{text:"\u4E2D\u5FC3\u4E3B\u9898"},children:[]},z0="logicalStructure",R0="default",D0=()=>({lineColor:"#7aa2ff",lineStyle:"curve",rootLineKeepSameInCurve:!0,rootLineStartPositionKeepSameInCurve:!0,generalizationLineColor:"#ef6a5b",backgroundColor:"#f6f8fc",root:{fillColor:"#e25563",color:"#ffffff",fontWeight:"bold",borderColor:"transparent",borderWidth:0,borderRadius:8},second:{fillColor:"#4f7df3",color:"#ffffff",borderColor:"transparent",borderWidth:0,borderRadius:8},node:{fillColor:"transparent",color:"#315aa9",borderColor:"transparent",borderWidth:0},generalization:{fillColor:"#ffffff",color:"#ef6a5b",borderColor:"#ef6a5b",borderWidth:1,borderRadius:8}}),Us=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),Vc=i=>{if(!i||typeof i!="object")return $s;let e=i.root??i,t=r=>{if(!Us(r))return;let n=Us(r.data)?r.data:{};r.data=n;let s=n.text;n.text=typeof s=="string"?s:String(s??"");let a=n.generalization,o=l=>{if(!Us(l))return;let h=l.text;l.text=typeof h=="string"?h:String(h??"")};Array.isArray(a)?a.forEach(o):o(a),Array.isArray(r.children)&&r.children.forEach(t)};return t(e),i},fi=i=>{let e=Vc(i)??$s,t=e&&typeof e=="object"&&"root"in e?e.root:e;return Vc(t)??$s},Yc=i=>{let t=fi(i)?.data?.text;return typeof t=="string"&&t.trim()?t.trim():"\u672A\u547D\u540D\u5BFC\u56FE"},B3=i=>{let t=[{node:fi(i),depth:0}],r=[];for(;t.length>0;){let n=t.shift();if(!n)break;let s=n.node?.data?.uid,a=n.node?.data?.text,o=Array.isArray(n.node?.children)?n.node.children:[];r.push({uid:typeof s=="string"&&s.trim()?s:`depth:${n.depth}:index:${r.length}`,text:typeof a=="string"&&a.trim()?a.trim():"\u672A\u547D\u540D\u8282\u70B9",depth:n.depth,childCount:o.length}),o.forEach(l=>{t.push({node:fi(l),depth:n.depth+1})})}return r},Rw=i=>{let e=fi(i.data),t=B3(e),r=Us(i.meta)?i.meta:null,n=t[0]?.uid??null;return{schema:"mnote.mindmap_projection.v1",projectionId:`mindmap_projection:${i.documentId}:${i.mindmapId}`,projection:"mindmap_subtree",source:i.source??"rust-kernel",owner:i.owner??"rust-kernel",documentId:i.documentId,mindmapId:i.mindmapId,rootNodeId:n,title:Yc(e),nodeCount:t.length,nodes:t,data:e,meta:r}},Dw=i=>{let e=fi(i.data),t=Us(i.meta)?i.meta:null,r=[],n=[],s=(l,h,d)=>{let c=l?.data?.uid,f=l?.data?.text,m=typeof c=="string"&&c.trim()?c.trim():`depth:${h}:index:${r.length}`,g=Array.isArray(l?.children)?l.children:[];r.push({id:m,uid:m,text:typeof f=="string"&&f.trim()?f.trim():"\u672A\u547D\u540D\u8282\u70B9",depth:h,childCount:g.length,parentId:d}),d&&n.push({id:`${d}->${m}`,source:d,target:m}),g.forEach(x=>{s(fi(x),h+1,m)})};s(e,0,null);let a=r[0]?.id??null,o=i.rootNodeId&&i.rootNodeId.trim()?i.rootNodeId.trim():a;return{schema:"mnote.mindmap_editor_scene.v1",source:i.source??"rust-kernel",owner:i.owner??"rust-kernel",documentId:i.documentId,mindmapId:i.mindmapId,rootNodeId:o,title:Yc(e),nodes:r,edges:n,capabilities:{canEditText:!0,canAddChild:!0,canAddSiblingAfter:!0,canDeleteNode:!0},data:e,meta:t}},Ow=i=>{let e=fi(i.data),t=Us(i.meta)?i.meta:null,r=e?.data?.uid,n=typeof r=="string"&&r.trim()?r.trim():"root";return e.data.uid||(e.data.uid=n),{schema:"mnote.mindmap.simple_mind_map_scene.v1",runtime:"simple-mind-map",documentId:i.documentId,mindmapId:i.mindmapId,rootNodeId:n,root:e,layout:z0,theme:R0,themeConfig:D0(),view:{x:0,y:0,scale:1},config:{},compatPayload:{},kernelRevision:1,source:i.source??"rust-kernel",owner:i.owner??"rust-kernel",meta:t}};var uF=["Drag","KeyboardNavigation","Export","Select","AssociativeLine","Search","OuterFrame"],fF=["Scrollbar","MiniMap","Painter","Formula"],mF=["data_change","view_data_change","node_active","back_forward","scale","translate"],pF=new Set(["BACK","FORWARD","INSERT_NODE","INSERT_CHILD_NODE","REMOVE_NODE","DELETE_NODE","ADD_GENERALIZATION","ADD_OUTER_FRAME","SET_NOTATION"]),oo=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),yb=(i,e)=>{if(typeof i=="string"&&i.trim())return i.trim();if(oo(i)){let t=i.template;if(typeof t=="string"&&t.trim())return t.trim()}return e},vb=i=>oo(i)?i:{},bb=i=>!oo(i)||Object.keys(i).length===0?D0():i,gF=i=>!oo(i)||!oo(i.state)?null:{...i,state:i.state,transform:oo(i.transform)?i.transform:{}},xF=i=>{let e=fi(i.projection.root??$s),t=vb(i.projection.config),r=vb(i.runtimeOptions);return{...t,...r,el:i.el,data:e,fit:typeof r.fit=="boolean"?r.fit:typeof t.fit=="boolean"?t.fit:!0,layout:yb(i.projection.layout,z0),theme:yb(i.projection.theme,R0),themeConfig:bb(i.projection.themeConfig),viewData:gF(i.projection.view),initRootNodePosition:["center","center"]}},yF={Drag:()=>Promise.resolve().then(()=>(f6(),u6)),KeyboardNavigation:()=>Promise.resolve().then(()=>(p6(),m6)),Export:()=>Promise.resolve().then(()=>(N6(),T6)),Select:()=>Promise.resolve().then(()=>(S6(),E6)),AssociativeLine:()=>Promise.resolve().then(()=>(R6(),z6)),Search:()=>Promise.resolve().then(()=>(O6(),D6)),OuterFrame:()=>Promise.resolve().then(()=>(U6(),H6)),Scrollbar:()=>Promise.resolve().then(()=>(j6(),$6)),MiniMap:()=>Promise.resolve().then(()=>(W6(),G6)),Painter:()=>Promise.resolve().then(()=>(Y6(),V6)),Formula:()=>Promise.resolve().then(()=>(Dv(),Rv))},vF=()=>[...uF,...fF],bF=async(i=vF())=>{if(typeof window>"u"||typeof document>"u")throw new Error("simple_mind_map_browser_required");let[{default:e},t]=await Promise.all([Promise.resolve().then(()=>(xb(),gb)),Promise.all(i.map(async s=>[s,(await yF[s]()).default]))]),r=e,n=[];return t.forEach(([s,a])=>{if(!a||typeof r.usePlugin!="function")return;typeof r.hasPlugin=="function"&&r.hasPlugin(a)!==-1||r.usePlugin(a),n.push(s)}),{MindMap:r,registeredPlugins:n}},wF=i=>(e,...t)=>{if(!pF.has(e))return{ok:!1,command:e,error:"unsupported_command"};if(typeof i.execCommand!="function")return{ok:!1,command:e,error:"runtime_unavailable"};try{return{ok:!0,command:e,result:i.execCommand(e,...t)}}catch{return{ok:!1,command:e,error:"command_failed"}}},MF=i=>{let e=[];return mF.forEach(t=>{let r=(...n)=>{let s=t==="data_change"?i.instance.getData?.():t==="view_data_change"?i.instance.getData?.(!0)??i.instance.getData?.():void 0;i.onEvent?.({type:t,args:n,snapshot:s,kernelRevision:i.projection.kernelRevision})};i.instance.on?.(t,r),e.push(()=>i.instance.off?.(t,r))}),()=>e.splice(0).forEach(t=>t())},wb=async i=>{let e=await bF(i.pluginNames),t=bb(i.projection.themeConfig),r=xF({el:i.el,projection:i.projection,runtimeOptions:i.runtimeOptions}),n=new e.MindMap(r);n.setThemeConfig?.(t),i.mode&&n.setMode?.(i.mode);let s=MF({instance:n,projection:i.projection,onEvent:i.onEvent}),a=wF(n);return{instance:n,execCommand:a,getSnapshot:()=>n.getData?.(!0)??n.getData?.(),destroy:()=>{n.renderer?.textEdit?.hideEditTextBox?.(),s(),n.destroy?.()}}};var ho=class extends Error{constructor(e,t=null){super(e),this.name="MindmapCommandBridgeError",this.code="command_failed",this.status=t}},lo=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),Up=i=>{if(!lo(i))return"";let e=[i.error,i.message,i.details].map(t=>typeof t=="string"?t.trim():Array.isArray(t)&&t.length>0?t.join("|"):"").find(Boolean);return e?`:${e}`:""},TF=async i=>{if(typeof i.text!="function"){let t=await i.json().catch(()=>null);return Up(t)}let e=await i.text().catch(()=>"");if(!e.trim())return"";try{return Up(JSON.parse(e))||`:${e.trim()}`}catch{return`:${e.trim()}`}},$p=i=>lo(i)?i.schema==="mnote.mindmap.simple_mind_map_scene.v1"&&i.runtime==="simple-mind-map"&&"root"in i&&typeof i.kernelRevision=="number":!1,Mb=(i,e)=>{let t=encodeURIComponent(i),r=encodeURIComponent(e);return`/api/mindmap/${t}/${r}?view=simple_mind_map_scene&queryName=mindmap.simple_mind_map_scene.get`},jp=(i,e)=>{let t=encodeURIComponent(i),r=encodeURIComponent(e);return`/api/mindmap/${t}/${r}`},Gp=async i=>{let e=i.fetcher??fetch,t=i.endpoint??Mb(i.documentId,i.mindmapId),r=await e(t,{headers:{Accept:"application/json"}});if(!r.ok)throw new Error(`projection_load_failed:${r.status}`);let n=await r.json(),s=lo(n)&&"result"in n?n.result:n;if(!$p(s))throw new Error("projection_load_failed:invalid_adapter_projection");return s},NF=i=>{if(!lo(i))return null;let e=[i.kernelRevision,i.projectionRevision,lo(i.result)?i.result.kernelRevision:null,lo(i.result)?i.result.projectionRevision:null];for(let t of e)if(typeof t=="number"&&Number.isFinite(t))return t;return null},Tb=async i=>{if(i.commands.length===0)throw new ho("command_failed:empty_commands");let e=i.fetcher??fetch,t=i.endpoint??jp(i.documentId,i.mindmapId),r=JSON.stringify({commandName:"mindmap.command.apply",documentId:i.documentId,mindmapId:i.mindmapId,commands:i.commands,projectionRevision:i.projectionRevision??null}),n=await e(t,{method:"POST",keepalive:r.length<=6e4,headers:{Accept:"application/json","Content-Type":"application/json"},body:r}),s=await n.json().catch(()=>null);if(!n.ok){let a=Up(s);throw new ho(`command_failed:${n.status}${a}`,n.status)}return{ok:!0,kernelRevision:NF(s),raw:s}},EF=async i=>(await Tb(i),Gp({documentId:i.documentId,mindmapId:i.mindmapId,endpoint:i.projectionEndpoint,fetcher:i.fetcher})),SF=async i=>{let e=i.fetcher??fetch,t=i.endpoint??jp(i.documentId,i.mindmapId),r=await e(t,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({data:i.data,createOnly:!1})});if(!r.ok){let n=await TF(r);throw new ho(`command_failed:${r.status}${n}`,r.status)}return await r.json().catch(()=>null),Gp({documentId:i.documentId,mindmapId:i.mindmapId,endpoint:i.projectionEndpoint,fetcher:e})},AF=async i=>{if(!$p(i.projection))throw new Error("adapter_init_failed:invalid_projection");return wb(i)};var Kp={};tt(Kp,{createCompatPayloadPatch:()=>Lc,createLayoutCommand:()=>Xp,createThemeCommand:()=>Yp,createToolbarKernelCommand:()=>Vp,createViewPatchCommand:()=>kF,diffMindmapRuntimeDataToKernelCommands:()=>zF});var Nb=i=>({text:i?.text?.trim()||"\u65B0\u8282\u70B9",...i?.uid?{uid:i.uid}:{},...i?.hyperlink?{hyperlink:i.hyperlink}:{},...i?.note?{note:i.note}:{},...i?.refs?{refs:i.refs}:{}}),Vp=i=>{let e=i.activeNodeId?.trim();switch(i.runtimeCommand){case"INSERT_CHILD_NODE":return e?{type:"insertChild",mindmapId:i.mindmapId,parentNodeId:e,node:Nb(i.newNode)}:null;case"INSERT_NODE":return e?{type:"insertSiblingAfter",mindmapId:i.mindmapId,targetNodeId:e,node:Nb(i.newNode)}:null;case"REMOVE_NODE":case"DELETE_NODE":return e?{type:"deleteNode",mindmapId:i.mindmapId,nodeId:e}:null;case"ADD_GENERALIZATION":return{type:"compatPayloadPatch",mindmapId:i.mindmapId,path:"root.data.generalization",value:{text:"\u6982\u8981"},source:"toolbar"};case"ADD_OUTER_FRAME":return{type:"compatPayloadPatch",mindmapId:i.mindmapId,path:"outerFrame",value:{enabled:!0,activeNodeId:e},source:"toolbar"};default:return null}},kF=(i,e)=>({type:"patchView",mindmapId:i,patch:e}),Yp=(i,e,t)=>({type:"setTheme",mindmapId:i,theme:e,themeConfig:t}),Xp=(i,e)=>({type:"setLayout",mindmapId:i,layout:e}),Lc=i=>({type:"compatPayloadPatch",mindmapId:i.mindmapId,path:i.path,value:i.value,source:i.source,...i.actionId?{actionId:i.actionId}:{},...typeof i.runtimeRevision=="number"?{runtimeRevision:i.runtimeRevision}:{},...typeof i.kernelRevision=="number"?{kernelRevision:i.kernelRevision}:{}}),CF=i=>typeof i=="object"&&i!==null&&!Array.isArray(i),_F=(i,e)=>{let t=i.data?.uid;return typeof t=="string"&&t.trim()?t.trim():e},LF=i=>{let e=i.data?.text;return typeof e=="string"?e:String(e??"")},Eb=i=>{let e=fi(i),t=new Map,r=(n,s,a,o)=>{let l=_F(n,o),h=CF(n.data)?n.data:{};t.set(l,{uid:l,parentUid:s,order:a,text:LF(n),data:h,node:n}),(Array.isArray(n.children)?n.children:[]).forEach((c,f)=>{r(fi(c),l,f,`${o}.${f}`)})};return r(e,null,0,"root"),t},Sb=i=>JSON.stringify(i??null),Ab=i=>({uid:i.uid,text:i.text||"\u65B0\u8282\u70B9",...typeof i.data.hyperlink=="string"?{hyperlink:i.data.hyperlink}:{},...typeof i.data.note=="string"?{note:i.data.note}:{},...Array.isArray(i.data.refs)?{refs:i.data.refs}:{}}),IF=(i,e,t,r)=>{let n=new Set(["uid","text","refs"]);new Set([...Object.keys(e.data),...Object.keys(t.data)]).forEach(a=>{n.has(a)||Sb(e.data[a])!==Sb(t.data[a])&&r.push(Lc({mindmapId:i,path:`nodes.${t.uid}.data.${a}`,value:t.data[a],source:"adapter-diff"}))})},zF=i=>{let e=Eb(i.previous),t=Eb(i.next),r=[],n=[];return t.forEach((s,a)=>{let o=e.get(a);if(!o){let l=Array.from(t.values()).find(h=>h.parentUid===s.parentUid&&h.order===s.order-1&&e.has(h.uid));l?r.push({type:"insertSiblingAfter",mindmapId:i.mindmapId,targetNodeId:l.uid,node:Ab(s)}):s.parentUid&&r.push({type:"insertChild",mindmapId:i.mindmapId,parentNodeId:s.parentUid,node:Ab(s)});return}o.text!==s.text&&r.push({type:"updateText",mindmapId:i.mindmapId,nodeId:a,text:s.text}),(o.parentUid!==s.parentUid||o.order!==s.order)&&r.push({type:"moveNode",mindmapId:i.mindmapId,nodeId:a,newParentNodeId:s.parentUid??"",order:s.order}),IF(i.mindmapId,o,s,n)}),e.forEach((s,a)=>{!t.has(a)&&s.parentUid&&r.push({type:"deleteNode",mindmapId:i.mindmapId,nodeId:a})}),{commands:r,compatPatches:n}};var Qp={};tt(Qp,{getMindmapActionMapping:()=>Ic,listMindmapActionMappings:()=>Zp,mapMindmapActionToCommand:()=>DF});var zs=i=>e=>e?`nodes.${e}.data.${i}`:null,kb=[{actionId:"undo",target:"runtimeCommand",runtimeCommand:"BACK",requiresActiveNode:!1,readonlyAllowed:!1},{actionId:"redo",target:"runtimeCommand",runtimeCommand:"FORWARD",requiresActiveNode:!1,readonlyAllowed:!1},{actionId:"editNode",target:"localView",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"insertSiblingAfter",target:"runtimeCommand",runtimeCommand:"INSERT_NODE",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"insertChild",target:"runtimeCommand",runtimeCommand:"INSERT_CHILD_NODE",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"deleteNode",target:"runtimeCommand",runtimeCommand:"REMOVE_NODE",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"summary",target:"runtimeCommand",runtimeCommand:"ADD_GENERALIZATION",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"associativeLine",target:"compatPatch",runtimeCommand:"ADD_ASSOCIATIVE_LINE",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"setTheme",target:"kernelCommand",requiresActiveNode:!1,readonlyAllowed:!1},{actionId:"setLayout",target:"kernelCommand",requiresActiveNode:!1,readonlyAllowed:!1},{actionId:"tag",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("tag")},{actionId:"hyperlink",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("hyperlink")},{actionId:"note",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("note")},{actionId:"image",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("image")},{actionId:"icon",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("icon")},{actionId:"formula",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("formula")},{actionId:"painter",target:"compatPatch",requiresActiveNode:!0,readonlyAllowed:!1,compatPath:zs("style")},{actionId:"import",target:"compatPatch",requiresActiveNode:!1,readonlyAllowed:!1,compatPath:()=>"import"},{actionId:"export",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"centerRoot",target:"localView",runtimeMethod:"centerRoot",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"zoomIn",target:"localView",runtimeMethod:"zoomIn",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"zoomOut",target:"localView",runtimeMethod:"zoomOut",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"fitView",target:"localView",runtimeMethod:"fitView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"fullscreenCanvas",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"fullscreenPage",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"exitFullscreen",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"search",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"showMenu",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0},{actionId:"expandCollapse",target:"localView",requiresActiveNode:!0,readonlyAllowed:!1},{actionId:"copyNodeText",target:"localView",requiresActiveNode:!0,readonlyAllowed:!0},{actionId:"readonly",target:"localView",requiresActiveNode:!1,readonlyAllowed:!0}],Zp=()=>kb,Ic=i=>kb.find(e=>e.actionId===i)??null,RF=i=>{let e=i?.trim();return e||null},DF=i=>{let e=Ic(i.actionId);if(!e)return null;let t=RF(i.activeNodeId);if(e.requiresActiveNode&&!t)return null;if(i.actionId==="setTheme")return{command:Yp(i.mindmapId,i.value,i.themeConfig)};if(i.actionId==="setLayout")return{command:Xp(i.mindmapId,i.value)};if(i.actionId==="editNode")return{command:{type:"updateText",mindmapId:i.mindmapId,nodeId:t??"root",text:typeof i.value=="string"?i.value:""}};if(e.runtimeCommand&&["INSERT_CHILD_NODE","INSERT_NODE","DELETE_NODE","REMOVE_NODE"].includes(e.runtimeCommand)){let r=Vp({mindmapId:i.mindmapId,runtimeCommand:e.runtimeCommand,activeNodeId:t,newNode:i.node});return r?{runtimeCommand:e.runtimeCommand,command:r}:{runtimeCommand:e.runtimeCommand}}if(e.compatPath){let r=e.compatPath(t);return r?{command:Lc({mindmapId:i.mindmapId,path:r,value:i.value??!0,source:i.source??"toolbar",actionId:i.actionId,runtimeRevision:i.runtimeRevision,kernelRevision:i.kernelRevision})}:null}return e.runtimeCommand?{runtimeCommand:e.runtimeCommand}:{}};var e3={};tt(e3,{createDefaultMindmapShellInteractionState:()=>_b,deriveMindmapUiState:()=>GF,reduceMindmapChromeVisibility:()=>jF});var Jp={};tt(Jp,{MINDMAP_DEBUG_CHROME_QUERY_PARAM:()=>Cb,MINDMAP_TOOLBAR_MORE_ACTION_ID:()=>OF,isMindmapDebugChromeEnabled:()=>FF,mindmapDefaultUiSchema:()=>co,mindmapToolbarFileActionOrder:()=>PF,mindmapToolbarPrimaryActionOrder:()=>BF});var Cb="mnoteMindmapDebugChrome",OF="more",BF=["undo","redo","editNode","insertSiblingAfter","deleteNode","insertChild","tag","hyperlink","note","image","icon","summary","associativeLine","formula"],PF=["import","export"],Se=(i,e,t,r,n,s,a)=>({id:i,iconKey:e,shortLabel:t,longLabel:r,priority:n,overflowGroup:s,cluster:a}),co={toolbarGroups:[{id:"history",label:"\u5386\u53F2",actions:["undo","redo"],collapsePriority:4},{id:"node",label:"\u8282\u70B9",actions:["editNode","insertSiblingAfter","deleteNode","insertChild"],collapsePriority:1},{id:"insert",label:"\u63D2\u5165",actions:["tag","hyperlink","note","image","icon","summary","associativeLine","formula"],collapsePriority:2},{id:"file",label:"\u6587\u4EF6",actions:["import","export"],collapsePriority:5},{id:"view",label:"\u89C6\u56FE",actions:["painter","centerRoot","zoomOut","zoomIn","search","readonly"],collapsePriority:3}],toolbarActionMeta:{undo:Se("undo","undo","\u64A4\u9500","\u64A4\u9500",10,"history","main"),redo:Se("redo","redo","\u91CD\u505A","\u91CD\u505A",20,"history","main"),editNode:Se("editNode","type","\u7F16\u8F91","\u7F16\u8F91\u8282\u70B9",30,"node","main"),insertSiblingAfter:Se("insertSiblingAfter","sibling","\u540C\u7EA7","\u63D2\u5165\u540C\u7EA7\u8282\u70B9",40,"node","main"),deleteNode:Se("deleteNode","trash","\u5220\u9664","\u5220\u9664\u8282\u70B9",50,"node","main"),insertChild:Se("insertChild","child","\u5B50\u7EA7","\u63D2\u5165\u5B50\u8282\u70B9",60,"node","main"),tag:Se("tag","tag","\u6807\u7B7E","\u6807\u7B7E",70,"insert","main"),hyperlink:Se("hyperlink","link","\u94FE\u63A5","\u8D85\u94FE\u63A5",80,"insert","main"),note:Se("note","note","\u5907\u6CE8","\u5907\u6CE8",90,"insert","main"),image:Se("image","image","\u56FE\u7247","\u56FE\u7247",100,"insert","main"),icon:Se("icon","smile","\u56FE\u6807","\u56FE\u6807",110,"insert","main"),summary:Se("summary","summary","\u6982\u8981","\u6982\u8981",120,"insert","main"),associativeLine:Se("associativeLine","route","\u5173\u8054","\u5173\u8054\u7EBF",130,"insert","main"),formula:Se("formula","formula","\u516C\u5F0F","\u516C\u5F0F",140,"insert","main"),painter:Se("painter","paintbrush","\u683C\u5F0F","\u683C\u5F0F\u5237",210,"view","view"),import:Se("import","import","\u5BFC\u5165","\u5BFC\u5165",310,"file","file"),export:Se("export","export","\u5BFC\u51FA","\u5BFC\u51FA",320,"file","file"),setTheme:Se("setTheme","palette","\u4E3B\u9898","\u4E3B\u9898",410,"view","view"),setLayout:Se("setLayout","layout","\u7ED3\u6784","\u7ED3\u6784",420,"view","view"),zoomIn:Se("zoomIn","zoom-in","\u653E\u5927","\u653E\u5927",510,"view","view"),zoomOut:Se("zoomOut","zoom-out","\u7F29\u5C0F","\u7F29\u5C0F",500,"view","view"),fitView:Se("fitView","fit","\u9002\u5E94","\u9002\u5E94\u753B\u5E03",520,"view","view"),centerRoot:Se("centerRoot","target","\u56DE\u6839","\u56DE\u5230\u6839\u8282\u70B9",490,"view","view"),fullscreenCanvas:Se("fullscreenCanvas","fullscreen","\u5168\u5C4F","\u5168\u5C4F\u67E5\u770B",550,"view","view"),fullscreenPage:Se("fullscreenPage","fullscreen-page","\u5168\u9875","\u5168\u5C4F\u7F16\u8F91",560,"view","view"),exitFullscreen:Se("exitFullscreen","exit-fullscreen","\u9000\u51FA","\u9000\u51FA\u5168\u5C4F",570,"view","view"),search:Se("search","search","\u641C\u7D22","\u641C\u7D22",530,"view","view"),showMenu:Se("showMenu","menu","\u83DC\u5355","\u663E\u793A\u83DC\u5355",580,"view","view"),expandCollapse:Se("expandCollapse","expand","\u5C55\u5F00","\u5C55\u5F00/\u6536\u8D77",610,"view","view"),copyNodeText:Se("copyNodeText","copy","\u590D\u5236","\u590D\u5236\u6587\u672C",620,"view","view"),readonly:Se("readonly","lock","\u53EA\u8BFB","\u53EA\u8BFB",540,"view","view")},sidebarPanels:[{id:"nodeStyle",kind:"nodeStyle",label:"\u8282\u70B9\u6837\u5F0F",icon:"palette",runtimeCapability:"node-style",phase:1,options:[{id:"node-fill-blue",label:"\u6D77\u84DD",actionId:"painter",value:"#dbeafe",controlType:"swatch",preview:"#dbeafe",compatPath:"nodes.$active.data.fillColor"},{id:"node-fill-green",label:"\u8584\u8377",actionId:"painter",value:"#dcfce7",controlType:"swatch",preview:"#dcfce7",compatPath:"nodes.$active.data.fillColor"},{id:"node-fill-amber",label:"\u6696\u9EC4",actionId:"painter",value:"#fef3c7",controlType:"swatch",preview:"#fef3c7",compatPath:"nodes.$active.data.fillColor"},{id:"node-text-dark",label:"\u6DF1\u8272\u6587\u5B57",actionId:"painter",value:"#0f172a",controlType:"swatch",preview:"#0f172a",compatPath:"nodes.$active.data.color"},{id:"node-text-blue",label:"\u84DD\u8272\u6587\u5B57",actionId:"painter",value:"#1d4ed8",controlType:"swatch",preview:"#1d4ed8",compatPath:"nodes.$active.data.color"},{id:"node-font-14",label:"14 px",actionId:"painter",value:14,controlType:"segmented",compatPath:"nodes.$active.data.fontSize"},{id:"node-font-18",label:"18 px",actionId:"painter",value:18,controlType:"segmented",compatPath:"nodes.$active.data.fontSize"},{id:"node-font-bold",label:"\u52A0\u7C97",actionId:"painter",value:!0,controlType:"toggle",compatPath:"nodes.$active.data.fontWeight"},{id:"node-font-italic",label:"\u659C\u4F53",actionId:"painter",value:!0,controlType:"toggle",compatPath:"nodes.$active.data.fontStyle"},{id:"node-shape-round",label:"\u5706\u89D2\u77E9\u5F62",actionId:"painter",value:"roundedRectangle",controlType:"button",compatPath:"nodes.$active.data.shape"},{id:"node-shape-rect",label:"\u77E9\u5F62",actionId:"painter",value:"rectangle",controlType:"button",compatPath:"nodes.$active.data.shape"},{id:"node-border-blue",label:"\u84DD\u8272\u8FB9\u6846",actionId:"painter",value:"#60a5fa",controlType:"swatch",preview:"#60a5fa",compatPath:"nodes.$active.data.borderColor"},{id:"node-line-teal",label:"\u9752\u8272\u5206\u652F\u7EBF",actionId:"painter",value:"#14b8a6",controlType:"swatch",preview:"#14b8a6",compatPath:"nodes.$active.data.lineColor"},{id:"node-line-width-2",label:"\u8FB9\u7EBF 2",actionId:"painter",value:2,controlType:"segmented",compatPath:"nodes.$active.data.lineWidth"}]},{id:"baseStyle",kind:"baseStyle",label:"\u5BFC\u56FE\u6837\u5F0F",icon:"sliders",runtimeCapability:"base-style",phase:1,options:[{id:"base-curve-line",label:"\u66F2\u7EBF",actionId:"painter",value:"curve",controlType:"segmented",compatPath:"style.map.lineStyle"},{id:"base-direct-line",label:"\u76F4\u7EBF",actionId:"painter",value:"straight",controlType:"segmented",compatPath:"style.map.lineStyle"},{id:"base-rainbow-lines",label:"\u5F69\u8679\u7EBF\u6761",actionId:"painter",value:{enabled:!0},controlType:"toggle",compatPath:"style.map.rainbowLines"},{id:"base-line-width-2",label:"\u7EBF\u5BBD 2",actionId:"painter",value:2,controlType:"segmented",compatPath:"style.map.lineWidth"},{id:"base-line-width-4",label:"\u7EBF\u5BBD 4",actionId:"painter",value:4,controlType:"segmented",compatPath:"style.map.lineWidth"},{id:"base-background-light",label:"\u6D45\u8272\u80CC\u666F",actionId:"painter",value:"#f8fafc",controlType:"swatch",preview:"#f8fafc",compatPath:"style.map.backgroundColor"},{id:"base-node-spacing-36",label:"\u8282\u70B9\u95F4\u8DDD 36",actionId:"painter",value:36,controlType:"numberInput",compatPath:"style.map.nodeSpacing"},{id:"base-summary-bracket",label:"\u62EC\u53F7\u6982\u8981",actionId:"painter",value:"bracket",controlType:"select",compatPath:"style.map.summaryStyle"}]},{id:"theme",kind:"theme",label:"\u4E3B\u9898",icon:"swatch",runtimeCapability:"theme",phase:1,options:[{id:"theme-classic",label:"Classic",actionId:"setTheme",value:"classic",controlType:"swatch",preview:"#60a5fa",description:"\u9ED8\u8BA4\u4E3B\u9898"},{id:"theme-classic4",label:"KMind",actionId:"setTheme",value:"classic4",controlType:"swatch",preview:"#22c55e",description:"KMind-like"},{id:"theme-simple",label:"Simple",actionId:"setTheme",value:"simple",controlType:"swatch",preview:"#f59e0b",description:"\u8F7B\u91CF\u4E3B\u9898"},{id:"theme-dark",label:"Dark",actionId:"setTheme",value:"dark",controlType:"swatch",preview:"#334155",description:"\u6DF1\u8272\u4E3B\u9898"}]},{id:"structure",kind:"structure",label:"\u7ED3\u6784",icon:"layout",runtimeCapability:"layout",phase:1,options:[{id:"layout-logical",label:"\u903B\u8F91\u7ED3\u6784\u56FE",actionId:"setLayout",value:"logicalStructure",controlType:"layoutCard",preview:"logicalStructure"},{id:"layout-mind-map",label:"\u601D\u7EF4\u5BFC\u56FE",actionId:"setLayout",value:"mindMap",controlType:"layoutCard",preview:"mindMap"},{id:"layout-organization",label:"\u7EC4\u7EC7\u7ED3\u6784\u56FE",actionId:"setLayout",value:"organizationStructure",controlType:"layoutCard",preview:"organizationStructure"},{id:"layout-catalog",label:"\u76EE\u5F55\u7EC4\u7EC7\u56FE",actionId:"setLayout",value:"catalogOrganization",controlType:"layoutCard",preview:"catalogOrganization"},{id:"layout-timeline",label:"\u65F6\u95F4\u8F74",actionId:"setLayout",value:"timeline",controlType:"layoutCard",preview:"timeline"},{id:"layout-fishbone",label:"\u9C7C\u9AA8\u56FE",actionId:"setLayout",value:"fishbone",controlType:"layoutCard",preview:"fishbone"}]},{id:"outline",kind:"outline",label:"\u5927\u7EB2",icon:"list-tree",runtimeCapability:"outline",phase:1,options:[]},{id:"shortcutKey",kind:"shortcutKey",label:"\u5FEB\u6377\u952E",icon:"sparkles",runtimeCapability:"shortcut-key",phase:1,options:[{id:"shortcut-insert-child",label:"Tab",description:"\u63D2\u5165\u5B50\u8282\u70B9",actionId:null,value:null,controlType:"treeItem",readonly:!0},{id:"shortcut-insert-sibling",label:"Enter",description:"\u63D2\u5165\u540C\u7EA7\u8282\u70B9",actionId:null,value:null,controlType:"treeItem",readonly:!0},{id:"shortcut-delete",label:"Delete",description:"\u5220\u9664\u8282\u70B9",actionId:null,value:null,controlType:"treeItem",readonly:!0}]},{id:"settings",kind:"settings",label:"\u8BBE\u7F6E",icon:"hexagon",runtimeCapability:"settings",phase:1,options:[{id:"settings-readonly-hint",label:"\u53EA\u8BFB\u6A21\u5F0F",description:"\u5BFC\u822A\u680F\u5207\u6362",actionId:null,value:null,controlType:"toggle",readonly:!0},{id:"settings-mouse",label:"\u9F20\u6807\u884C\u4E3A",description:"\u5DE6\u952E\u9009\u4E2D\uFF0C\u53F3\u952E\u62D6\u62FD",actionId:null,value:"leftSelectRightDrag",controlType:"select",readonly:!0}]}],navigatorItems:[{id:"stats",label:"\u7EDF\u8BA1",actionId:null,readOnly:!0,displayMode:"text"},{id:"centerRoot",label:"\u56DE\u6839\u8282\u70B9",actionId:"centerRoot",readOnly:!0,displayMode:"button"},{id:"search",label:"\u641C\u7D22",actionId:"search",readOnly:!0,displayMode:"button"},{id:"zoomOut",label:"\u7F29\u5C0F",actionId:"zoomOut",readOnly:!0,displayMode:"button"},{id:"zoom",label:"\u7F29\u653E",actionId:null,readOnly:!0,displayMode:"input"},{id:"zoomIn",label:"\u653E\u5927",actionId:"zoomIn",readOnly:!0,displayMode:"button"},{id:"fullscreen",label:"\u5168\u5C4F",actionId:"fullscreenCanvas",readOnly:!0,displayMode:"button"},{id:"readonly",label:"\u53EA\u8BFB",actionId:"readonly",readOnly:!0,displayMode:"button"}],contextMenuItems:[{id:"insertChild",label:"\u63D2\u5165\u5B50\u8282\u70B9",actionId:"insertChild",requiresNode:!0,phase:1},{id:"insertSiblingAfter",label:"\u63D2\u5165\u540C\u7EA7\u8282\u70B9",actionId:"insertSiblingAfter",requiresNode:!0,phase:1},{id:"deleteNode",label:"\u5220\u9664\u8282\u70B9",actionId:"deleteNode",requiresNode:!0,phase:1},{id:"summary",label:"\u6982\u8981",actionId:"summary",requiresNode:!0,phase:1},{id:"associativeLine",label:"\u5173\u8054\u7EBF",actionId:"associativeLine",requiresNode:!0,phase:1},{id:"expandCollapse",label:"\u5C55\u5F00/\u6536\u8D77",actionId:"expandCollapse",requiresNode:!0,phase:1},{id:"copyNodeText",label:"\u590D\u5236\u6587\u672C",actionId:"copyNodeText",requiresNode:!0,phase:1},{id:"centerRoot",label:"\u56DE\u6839\u8282\u70B9",actionId:"centerRoot",requiresNode:!1,phase:1},{id:"fitView",label:"\u9002\u5E94\u753B\u5E03",actionId:"fitView",requiresNode:!1,phase:1},{id:"search",label:"\u641C\u7D22",actionId:"search",requiresNode:!1,phase:1},{id:"readonly",label:"\u53EA\u8BFB\u5207\u6362",actionId:"readonly",requiresNode:!1,phase:1},{id:"showMenu",label:"\u663E\u793A\u83DC\u5355",actionId:"showMenu",requiresNode:!1,phase:1}]},FF=i=>{try{let e=new URL(i).searchParams.get(Cb);return e==="1"||e==="true"}catch{return!1}};var qF=()=>{let i=[...co.toolbarGroups.flatMap(e=>e.actions),...co.navigatorItems.flatMap(e=>e.actionId?[e.actionId]:[]),...co.contextMenuItems.map(e=>e.actionId),...Zp().map(e=>e.actionId)];return[...new Set(i)]},HF=i=>{let e=i?.trim();return e||null},UF=i=>typeof i!="number"||!Number.isFinite(i)?100:Math.max(10,Math.min(500,Math.round(i))),$F=new Set(["tag","hyperlink","note","image","icon","associativeLine","formula","import","export"]),_b=(i={})=>({chromeVisibility:i.chromeVisibility??"visible",toolbarOverflow:{availableWidth:i.toolbarOverflow?.availableWidth??null,visibleActionIds:i.toolbarOverflow?.visibleActionIds??[],overflowActionIds:i.toolbarOverflow?.overflowActionIds??[],moreOpen:i.toolbarOverflow?.moreOpen??!1},fullscreen:{mode:i.fullscreen?.mode??"none",isFullscreen:i.fullscreen?.isFullscreen??!1,target:i.fullscreen?.target??"mindmap-root",apiAvailable:i.fullscreen?.apiAvailable??!0},sidebar:{triggerVisible:i.sidebar?.triggerVisible??!0,panelOpen:i.sidebar?.panelOpen??!0,activePanelId:i.sidebar?.activePanelId??co.sidebarPanels[0]?.id??null,drawerWidth:i.sidebar?.drawerWidth??300,collapsedByToggle:i.sidebar?.collapsedByToggle??!1},navigator:{searchOpen:i.navigator?.searchOpen??!1,minimapOpen:i.navigator?.minimapOpen??!1,readonly:i.navigator?.readonly??!1,zoomPercent:UF(i.navigator?.zoomPercent),mouseBehavior:i.navigator?.mouseBehavior??"leftSelectRightDrag"}}),jF=(i,e)=>e==="pointerLeave"?"hiddenByPointerLeave":e==="pointerEnter"?i:e==="restoreClick"?"visible":e==="hideToggle"?"hiddenByToggle":e==="showToggle"?"visible":e==="enterFullscreen"?i==="visible"?"visible":"hiddenByFullscreen":e==="exitFullscreen"&&i==="hiddenByFullscreen"?"visible":i,GF=i=>{let e=HF(i.activeNodeId),t=i.runtimeCapabilities?new Set(i.runtimeCapabilities):null,r={},n=_b({...i.shell,navigator:{...i.shell?.navigator,readonly:i.readonly}});return qF().forEach(s=>{let a=Ic(s);if(!a){r[s]=!0;return}if(a.requiresActiveNode&&!e){r[s]=!0;return}if(i.readonly&&!a.readonlyAllowed){r[s]=!0;return}if(t&&!t.has(a.target)){r[s]=!0;return}if($F.has(s)){r[s]=!0;return}r[s]=!1}),{activeNodeId:e,readonly:i.readonly,disabledActions:r,shell:n}};var t3={};tt(t3,{resolveMindmapShortcutAction:()=>WF,shouldInterceptMindmapShortcut:()=>VF});var WF=i=>i.ctrlKey||i.metaKey||i.altKey||i.shiftKey?null:i.key==="Enter"?"insertSiblingAfter":i.key==="Tab"||i.key==="Insert"?"insertChild":i.key==="Delete"||i.key==="Backspace"?"deleteNode":i.key==="F2"?"editNode":null,VF=i=>i.debugChromeEnabled||!i.bridgeReady||i.readonly||i.isComposing||i.isEditableTarget?!1:i.targetInsideRoot?!0:i.keyboardShortcutArmed;function g0(){return{status:"idle"}}function Lb(i,e){return{status:"armed",armedAt:e,reason:i}}function Ib(){return g0()}function zb(i,e,t=1500){return i.status!=="armed"?{state:i,suppressed:!1,expired:!1}:e-i.armedAt>t?{state:g0(),suppressed:!1,expired:!0}:{state:g0(),suppressed:!0,expired:!1}}function Rb(i){return{endpoint:`/api/mindmap/${encodeURIComponent(i.documentId)}/${encodeURIComponent(i.mindmapId)}`,init:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({data:i.data,createOnly:!0})}}}function Gn(i){let e=i;return e.default&&typeof e.default=="object"?e.default:i}var{applyMindmapCommandsLocally:YF,isLocallyApplicableMindmapCommandSet:XF}=Gn(Wc),{createLeptosMindmapAdapter:KF,executeMindmapCommandApply:i3,executeMindmapCommandApplyAndRefreshProjection:ZF,requestMindmapAdapterProjection:QF}=Gn(Wp),{createViewPatchCommand:JF,createCompatPayloadPatch:eq,diffMindmapRuntimeDataToKernelCommands:tq}=Gn(Kp),{getMindmapActionMapping:Db,mapMindmapActionToCommand:iq}=Gn(Qp),{canonicalizeMindmapData:wi}=Gn(Xc),{deriveMindmapUiState:r3,reduceMindmapChromeVisibility:zc}=Gn(e3),{resolveMindmapShortcutAction:rq,shouldInterceptMindmapShortcut:nq}=Gn(t3),{isMindmapDebugChromeEnabled:sq,mindmapDefaultUiSchema:Dc,mindmapToolbarFileActionOrder:aq,mindmapToolbarPrimaryActionOrder:a3}=Gn(Jp),uo=900,o3=240,Ob=1280,oq=300;function lq(){return{activePanelId:Dc.sidebarPanels[0]?.id??"nodeStyle",panelOpen:!1,triggerVisible:!0,collapsedByToggle:!1,drawerWidth:oq}}function hq(i){return typeof i=="string"&&i.length>0?{"data-block-id":i,id:i}:{}}function fo(i,e){let t=typeof i=="number"?i:typeof i=="string"?Number(i.trim().replace(/px$/i,"")):NaN;if(!Number.isFinite(t))return null;let r=Math.round(t);return r>=(e==="width"?uo:o3)?r:null}function dq(i){if(i.mnoteBlockType!=="mindmap")return{};let e={"data-mnote-block-type":"mindmap"};typeof i.mindmapId=="string"&&(e["data-mnote-mindmap-id"]=i.mindmapId),typeof i.rootNodeId=="string"&&(e["data-mnote-root-node-id"]=i.rootNodeId),typeof i.projectionVersion=="number"&&(e["data-mnote-projection-version"]=String(i.projectionVersion));let t=fo(i.mindmapWidth,"width"),r=fo(i.mindmapHeight,"height");return t!==null&&(e["data-mnote-mindmap-width"]=String(t)),r!==null&&(e["data-mnote-mindmap-height"]=String(r)),e}function Rs(i){return String(i??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function n3(i){if(typeof i!="string")return null;let e=i.trim();return e.length>0?e:null}function cq(i){let e=n3(i?.closest("[data-document-id]")?.getAttribute("data-document-id"));if(e)return e;if(i&&"isConnected"in i&&i.isConnected===!1)return null;let t=window.location.pathname.match(/\/documents\/([^/?#]+)/);if(t?.[1])return decodeURIComponent(t[1]);let r=window.location.pathname.match(/\/mindmap\/([^/?#]+)\/([^/?#]+)/);if(r?.[1])return decodeURIComponent(r[1]);let n=n3(document.body?.getAttribute("data-document-id"));if(n)return n;let s=n3(document.querySelector(".document-shell[data-document-id], [data-pane-document-id], [data-document-id]")?.getAttribute("data-document-id")??document.querySelector("[data-pane-document-id]")?.getAttribute("data-pane-document-id"));return s||null}function Bb(i,e){let r=wi(i.root).data?.uid;return typeof r=="string"&&r.trim().length>0?r.trim():e}function s3(i){let t=wi(i.root).data?.text;return typeof t=="string"&&t.trim().length>0?t.trim():"\u672A\u547D\u540D\u8282\u70B9"}function Ds(i){if(typeof i=="string"&&i.trim().length>0)return i.trim();if(!i||typeof i!="object")return null;let e=i;if(typeof e.uid=="string"&&e.uid.trim().length>0)return e.uid.trim();let t=e.data&&typeof e.data=="object"&&!Array.isArray(e.data)?e.data:{};if(typeof t.uid=="string"&&t.uid.trim().length>0)return t.uid.trim();let r=e.getData;if(typeof r=="function")try{let n=r.call(e,"uid");if(typeof n=="string"&&n.trim().length>0)return n.trim()}catch{return null}return null}function Rc(i,e){let t=window,r=t.__MNOTE_LEPTOS_MINDMAP_BRIDGES__??{};t.__MNOTE_LEPTOS_MINDMAP_BRIDGES__=r,e?r[i]=e:delete r[i]}function zt(i){return typeof i=="object"&&i!==null&&!Array.isArray(i)}function Os(i){return JSON.parse(JSON.stringify(i))}function uq(i){if(!(i instanceof HTMLElement))return!1;let e=i.tagName;return e==="INPUT"||e==="TEXTAREA"||e==="SELECT"?!0:i.isContentEditable?!i.matches(".ProseMirror"):!1}function fq(i){if(!zt(i))return null;let e=i.view;return zt(e)&&zt(e.state)?e:null}function mq(i){if(!(i!=="insertChild"&&i!=="insertSiblingAfter"))return{uid:`node_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`,text:"\u65B0\u8282\u70B9"}}function pq(i,e){let t=e?.trim();if(!t)return!1;let r=wi(i),n=!1,s=a=>{if(n)return;let o=wi(a);if(o.data?.uid===t){n=!0;return}(Array.isArray(o.children)?o.children:[]).forEach(s)};return s(r),n}function Pb(i){let e=wi(i),t=Array.isArray(e.children)?[...e.children]:[];for(;t.length>0;){let r=wi(t.shift()),n=r.data?.uid;if(typeof n=="string"&&n.trim().length>0)return n.trim();Array.isArray(r.children)&&t.push(...r.children)}return null}function gq(i,e){let t=e?.trim();if(!t)return null;let r=null,n=s=>{if(r)return;let a=wi(s);if(a.data?.uid===t){r=a;return}(Array.isArray(a.children)?a.children:[]).forEach(n)};return n(i),r}function xq(i,e){let t=gq(i,e),r=t&&zt(t.data)?t.data.text:null;return typeof r=="string"?r:""}function yq(i,e=10){let t=wi(i),r=[],n=(s,a)=>{if(r.length>=e)return;let o=wi(s),l=typeof o.data?.text=="string"&&o.data.text.trim().length>0?o.data.text.trim():"\u672A\u547D\u540D\u8282\u70B9";r.push(`${" ".repeat(Math.max(a-1,0))}${l}`),(Array.isArray(o.children)?o.children:[]).forEach(d=>n(d,a+1))};return n(t,1),r}function Fb(i,e){let t=i?.trim();return t?t.includes("$active")?e?t.replaceAll("$active",e):null:t:null}var qb={undo:"\u64A4\u9500",redo:"\u91CD\u505A",editNode:"\u7F16\u8F91\u8282\u70B9",insertSiblingAfter:"\u540C\u7EA7\u8282\u70B9",insertChild:"\u5B50\u8282\u70B9",deleteNode:"\u5220\u9664",tag:"\u6807\u7B7E",hyperlink:"\u94FE\u63A5",note:"\u5907\u6CE8",image:"\u56FE\u7247",icon:"\u56FE\u6807",summary:"\u6982\u8981",associativeLine:"\u5173\u8054\u7EBF",formula:"\u516C\u5F0F",painter:"\u683C\u5F0F\u5237",import:"\u5BFC\u5165",export:"\u5BFC\u51FA",setTheme:"\u4E3B\u9898",setLayout:"\u7ED3\u6784",zoomIn:"\u653E\u5927",zoomOut:"\u7F29\u5C0F",fitView:"\u9002\u5E94",centerRoot:"\u56DE\u6839",fullscreenCanvas:"\u5168\u5C4F",fullscreenPage:"\u5168\u9875",exitFullscreen:"\u9000\u51FA",search:"\u641C\u7D22",showMenu:"\u663E\u793A\u83DC\u5355",expandCollapse:"\u5C55\u5F00/\u6536\u8D77",copyNodeText:"\u590D\u5236\u6587\u672C",readonly:"\u53EA\u8BFB"},vq={undo:"\u21B6",redo:"\u21B7",editNode:"T",insertSiblingAfter:"\u21B5",insertChild:"+",deleteNode:"\xD7",tag:"#",hyperlink:"\u2301",note:"N",image:"\u25A1",icon:"\u2606",summary:"{",associativeLine:"\u2307",formula:"fx",painter:"\u25D0",import:"\u21E7",export:"\u21E9",centerRoot:"\u25CE",zoomOut:"-",zoomIn:"+",search:"\u2315",fullscreenCanvas:"\u26F6",fullscreenPage:"\u25A3",exitFullscreen:"\u2921",showMenu:"\u2630",expandCollapse:"\u2922",copyNodeText:"C",readonly:"\u9501"},bq={undo:"\u21B6",redo:"\u21B7",type:"T",sibling:"\u21B5",trash:"\xD7",child:"+",tag:"#",link:"\u2301",note:"N",image:"\u25A1",smile:"\u2606",summary:"{",route:"\u2307",formula:"fx",paintbrush:"\u25D0",import:"\u21E7",export:"\u21E9",target:"\u25CE",fullscreen:"\u26F6","fullscreen-page":"\u25A3","exit-fullscreen":"\u2921",menu:"\u2630","zoom-out":"-","zoom-in":"+",search:"\u2315",lock:"\u9501"},wq=i=>{let e=[...a3];if(i===null||!Number.isFinite(i))return{availableWidth:null,visibleActionIds:e,overflowActionIds:[]};let t=118,r=96,n=50,s=Math.max(0,i-t-r),a=Math.max(2,Math.min(e.length,Math.floor(s/n)));if(a>=e.length)return{availableWidth:i,visibleActionIds:e,overflowActionIds:[]};let o=Math.max(1,a-1);return{availableWidth:i,visibleActionIds:e.slice(0,o),overflowActionIds:e.slice(o)}};function Mq(i){return({node:e,getPos:t})=>{let r=e,n=0,s=null,a=null,o=null,l=null,h=null,d=null,c=g0(),f=null,m=!1,g=lq(),x=g.activePanelId,y=g.panelOpen,b=g.triggerVisible,E=g.collapsedByToggle,k=g.drawerWidth,L=null,I=null,O=!1,q=!1,F=null,W=!1,ne=null,re=null,le=null,Ce=null,te=null,Q=null,oe=null,he="visible",ui=!1,Mi=!1,ut="canvas",at=null,ot="canvas",nn=0,Bs=0,Gt=null,Wn=null,Hi=null,br=0,Vn=0,sn=!1,mo=null,wr=null,Ui=!1,Wt=sq(window.location.href),ge=document.createElement("div");ge.className="mnote-mindmap-placeholder",ge.dataset.mnoteBlockType="mindmap",ge.dataset.testid="mnote-mindmap-placeholder",ge.setAttribute("contenteditable","false");let ye=document.createElement("div");ye.className="mnote-mindmap-editor-root",ye.dataset.testid="mnote-mindmap-editor-root",ye.dataset.mnoteSceneQuery="mindmap.simple_mind_map_scene.get";let Mr=()=>cq(ye),D=document.createElement("div");D.dataset.testid="leptos-mindmap-island",D.dataset.runtime="simple-mind-map",D.dataset.stage="loading",D.dataset.debugChrome=Wt?"true":"false",D.dataset.suppressRuntimeDiffState="idle",D.className="mnote-leptos-mindmap-shell";let ft=document.createElement("div");ft.className="mnote-mindmap-resize-preview",ft.dataset.testid="mindmap-resize-preview",ft.hidden=!0;let l3=()=>({width:fo(r.attrs.mindmapWidth,"width"),height:fo(r.attrs.mindmapHeight,"height")}),Hb=()=>{let w=window.getComputedStyle(ge).maxWidth,A=Number(String(w||"").replace(/px$/i,""));if(Number.isFinite(A)&&A>0)return A;let R=window.innerWidth>0?Math.min(Ob,Math.max(uo,window.innerWidth-340)):Ob;return Math.max(uo,R)},Oc=(w=!1)=>{window.requestAnimationFrame(()=>{let A=s?.instance;A?.resize?.(),w&&(typeof A?.view?.fit=="function"?A.view.fit():A?.renderer?.setRootNodeCenter?.())})},Ub=()=>{let{width:w,height:A}=l3();ge.dataset.mnoteMindmapResized=w!==null||A!==null?"true":"false",w!==null?(ge.dataset.mnoteMindmapWidth=String(w),ge.style.width=`${w}px`):(delete ge.dataset.mnoteMindmapWidth,ge.style.removeProperty("width")),A!==null?(ge.dataset.mnoteMindmapHeight=String(A),ye.dataset.mnoteMindmapHeight=String(A),D.dataset.mnoteMindmapHeight=String(A),D.style.height=`${A}px`):(delete ge.dataset.mnoteMindmapHeight,delete ye.dataset.mnoteMindmapHeight,delete D.dataset.mnoteMindmapHeight,D.style.removeProperty("height")),Oc()},$b=(w,A)=>{ge.dataset.mnoteMindmapResized="true",ge.dataset.mnoteMindmapWidth=String(w),ge.dataset.mnoteMindmapHeight=String(A),ye.dataset.mnoteMindmapHeight=String(A),D.dataset.mnoteMindmapHeight=String(A),ge.style.width=`${w}px`,D.style.height=`${A}px`},h3=(w,A)=>{ge.dataset.mnoteMindmapResizing="true",ft.hidden=!1,ft.dataset.mnoteMindmapWidth=String(w),ft.dataset.mnoteMindmapHeight=String(A),ft.style.width=`${w}px`,ft.style.height=`${A}px`},jb=()=>{ge.dataset.mnoteMindmapResizing="false",ft.hidden=!0,delete ft.dataset.mnoteMindmapWidth,delete ft.dataset.mnoteMindmapHeight,ft.style.removeProperty("width"),ft.style.removeProperty("height")},Gb=(w,A)=>{let R=t();if(typeof R!="number")return;let P={...r.attrs,mindmapWidth:w,mindmapHeight:A};i.view.dispatch(i.state.tr.setNodeMarkup(R,void 0,P)),Oc(!0)},Wb=()=>{let w=t();if(typeof w!="number")return;let{width:A}=l3();if(A===null){Oc(!0);return}let R={...r.attrs,mindmapWidth:null};i.view.dispatch(i.state.tr.setNodeMarkup(w,void 0,R))},x0=w=>{let A=document.createElement("button");return A.type="button",A.className=`mnote-mindmap-resize-handle mnote-mindmap-resize-handle-${w}`,A.dataset.testid=`mindmap-resize-handle-${w}`,A.dataset.mnoteMindmapResizeHandle=w,A.setAttribute("aria-label","\u8C03\u6574\u601D\u7EF4\u5BFC\u56FE\u5927\u5C0F"),A.addEventListener("pointerdown",R=>{R.preventDefault(),R.stopPropagation();let P=ge.getBoundingClientRect(),Y=Math.max(uo,P.width),K=Math.max(o3,P.height),ce=R.clientX,J=R.clientY,ve=w.endsWith("e")?1:-1,qe=w.startsWith("s")?1:-1,C0=Math.max(uo,Hb()),xo=Math.round(Y),yo=Math.round(K);h3(xo,yo),A.setPointerCapture(R.pointerId);let S3=Uc=>{Uc.preventDefault();let vw=(Uc.clientX-ce)*ve,bw=(Uc.clientY-J)*qe;xo=Math.round(Math.max(uo,Math.min(C0,Y+vw))),yo=Math.round(Math.max(o3,K+bw)),h3(xo,yo)},_0=()=>{jb(),A.releasePointerCapture(R.pointerId),A.removeEventListener("pointermove",S3),A.removeEventListener("pointerup",_0),A.removeEventListener("pointercancel",_0),$b(xo,yo),Gb(xo,yo)};A.addEventListener("pointermove",S3),A.addEventListener("pointerup",_0),A.addEventListener("pointercancel",_0)}),A},Bc=()=>{D.dataset.suppressRuntimeDiffState=c.status,D.dataset.suppressRuntimeDiffReason=c.status==="armed"?c.reason:"",D.dataset.suppressRuntimeDiffSince=c.status==="armed"?String(c.armedAt):""},Vb=w=>{c=Lb(w,Date.now()),Bc()},Ps=()=>{c=Ib(),Bc()},Yn=document.createElement("div");Yn.className="mnote-mindmap-workspace",Yn.dataset.debugChrome=Wt?"true":"false",Yn.dataset.layout=Wt?"debug-grid":"floating-overlay";let y0=document.createElement("div");y0.className="mnote-mindmap-canvas-layer",y0.dataset.testid="mindmap-canvas-layer";let Xn=document.createElement("div");Xn.className="mnote-mindmap-overlay-layer",Xn.dataset.testid="mindmap-overlay-layer";let xt=document.createElement("div");xt.className="mnote-leptos-mindmap-runtime",xt.dataset.testid="simple-mind-map-runtime",xt.dataset.runtime="simple-mind-map",xt.dataset.runtimeEngine="pending";let er=document.createElement("div");er.className="mnote-mindmap-rust-shell-mount",er.dataset.testid="mindmap-rust-shell-mount",er.dataset.uiShellSource="leptos-rust-shell";let Kn=document.createElement("div");Kn.className="mnote-mindmap-command-toolbar",Kn.dataset.testid="mindmap-command-toolbar";let Zn=document.createElement("aside");Zn.className="mnote-mindmap-side-panel",Zn.dataset.testid="mindmap-sidebar";let Qn=document.createElement("div");Qn.className="mnote-mindmap-bottom-bar",Qn.dataset.testid="mindmap-bottom-bar";let et=()=>{let w=typeof r.attrs.mindmapId=="string"&&r.attrs.mindmapId.length>0?r.attrs.mindmapId:"mindmap",A=typeof r.attrs.rootNodeId=="string"&&r.attrs.rootNodeId.length>0?r.attrs.rootNodeId:"root";return{mindmapId:w,rootNodeId:A}},d3=w=>JSON.stringify({mnoteBlockType:w.attrs.mnoteBlockType??null,mindmapId:w.attrs.mindmapId??null,rootNodeId:w.attrs.rootNodeId??null,projectionVersion:w.attrs.projectionVersion??null,mindmapWidth:w.attrs.mindmapWidth??null,mindmapHeight:w.attrs.mindmapHeight??null}),po=()=>{let{mindmapId:w,rootNodeId:A}=et();ge.dataset.mnoteMindmapId=w,ge.dataset.mnoteRootNodeId=A,ge.dataset.mnoteProjectionVersion=String(r.attrs.projectionVersion??1),ye.dataset.mnoteMindmapId=w,ye.dataset.mnoteRootNodeId=A,ye.dataset.mnoteProjectionVersion=String(r.attrs.projectionVersion??1),ye.dataset.mnoteSceneQuery="mindmap.simple_mind_map_scene.get",Ub()},v0=()=>{if(!(Wn===null||!Hi)){try{Hi.unmount(Wn)}catch{}Wn=null,Hi=null,er.replaceChildren()}},c3=()=>{let w=s?.getSnapshot(),A=zt(w)&&"root"in w?w.root:w??a?.root,R=0,P=0,Y=J=>{if(!zt(J))return;R+=1;let ve=zt(J.data)&&typeof J.data.text=="string"?J.data.text:"";P+=ve.trim().length,(Array.isArray(J.children)?J.children:[]).forEach(Y)};Y(A);let K=a?.view,ce=zt(w)&&zt(w.view)&&zt(w.view.state)&&typeof w.view.state.scale=="number"?w.view.state.scale:zt(K)&&zt(K.state)&&typeof K.state.scale=="number"?K.state.scale:1;return{nodeCount:R,wordCount:P,zoomPercent:Math.round(ce*100)}},Yb=()=>a?{...wi(a.root),layout:a.layout,theme:a.theme,themeConfig:a.themeConfig,view:a.view,config:a.config,compatPayload:a.compatPayload}:null,Xb=(w,A)=>{a&&(a.root=Os(wi(w)),"layout"in w&&(a.layout=w.layout),"theme"in w&&(a.theme=w.theme),"themeConfig"in w&&(a.themeConfig=w.themeConfig),"view"in w&&(a.view=w.view),"config"in w&&(a.config=w.config),"compatPayload"in w&&(a.compatPayload=w.compatPayload),typeof A=="number"&&Number.isFinite(A)&&(a.kernelRevision=A),o=Os(a.root))},Kb=()=>{let w=s?.getSnapshot(),A=zt(w)&&"root"in w?w.root:w??a?.root;return yq(A)},b0=()=>{W&&(W=!1,Ie())},u3=(w,A)=>{let R=ye.getBoundingClientRect();nn=Math.round(w-R.left),Bs=Math.round(A-R.top)},w0=()=>{!Mi&&!D.dataset.contextMenuKind||(Mi=!1,ut="canvas",at=null,ot="canvas",delete D.dataset.contextMenuKind,delete D.dataset.contextMenuTargetNodeId,delete D.dataset.contextMenuTargetNodeKind,S0())},M0=w=>Ds(w),Zb=()=>{let w=s?.instance.renderer;return w?.activeNodeList?.[0]??w?.lastActiveNodeList?.[0]??w?.root??w?.renderTree?._node??null},T0=(w,A,R)=>{let P=Ds(A);return P&&pq(w.root,P)?P:Pb(w.root)??Bb(w,R)},Pc=w=>{let A=Ds(w);return A||(M0(h)??M0(Zb())??null)},Qb=w=>{if(!w||typeof w!="object")return"node";let A=w;return A.isRoot===!0?"root":A.isGeneralization===!0?"generalization":"node"},Jb=w=>{let A=s?.instance.renderer;if(A)try{A.clearActiveNodeList?.(),A.addNodeToActiveList?.(w,!0),A.emitNodeActiveEvent?.(w,[w])}catch{}},go=w=>{let A=Ds(w),R=s?.instance.renderer,P=A?R?.findNodeByUid?.(A)??null:R?.activeNodeList?.[0]??R?.lastActiveNodeList?.[0]??R?.root??R?.renderTree?._node;if(P)try{R?.clearActiveNodeList?.(),R?.addNodeToActiveList?.(P,!0),R&&(R.lastActiveNodeList=[P]),R?.emitNodeActiveEvent?.(P,[P]),s?.instance.execCommand?.("SET_NODE_ACTIVE",P,!0),h=P;let K=A??M0(P)??l??et().rootNodeId;return l=K,d=K,D.dataset.lastRuntimeSelectionSync=K,!0}catch{}let Y=s?.instance.execCommand;if(typeof Y!="function"||!A)return!1;try{return Y.call(s?.instance,"GO_TARGET_NODE",A),l=A,d=A,D.dataset.lastRuntimeSelectionSync=A,!0}catch{return!1}},f3=w=>{let A=Ds(w),R=s?.instance.renderer;return{wantedNodeId:A,runtimeNode:A?R?.findNodeByUid?.(A)??null:R?.activeNodeList?.[0]??R?.lastActiveNodeList?.[0]??R?.root??R?.renderTree?._node}},ew=async(w,A=3)=>{let R=f3(w);for(let P=1;!R.runtimeNode&&Pwindow.requestAnimationFrame(()=>Y())),R=f3(w);return R},tw=async(w,A,R)=>{let P=w==="DELETE_NODE"?"REMOVE_NODE":w,Y=s?.instance;if(!Y||typeof Y.execCommand!="function")return{ok:!1,command:P,error:"runtime_unavailable",message:"runtime_instance_missing"};let{wantedNodeId:K,runtimeNode:ce}=await ew(A);if(!ce)return{ok:!1,command:P,error:"command_failed",message:`runtime_node_missing:${K??"unknown"}`};try{if(Y.renderer?.clearActiveNodeList?.(),Y.renderer?.addNodeToActiveList?.(ce,!0),Y.renderer&&(Y.renderer.lastActiveNodeList=[ce]),Y.renderer?.emitNodeActiveEvent?.(ce,[ce]),Y.execCommand("SET_NODE_ACTIVE",ce,!0),P==="REMOVE_NODE")return{ok:!0,command:P,result:Y.execCommand("REMOVE_NODE",[ce])};if(P==="INSERT_CHILD_NODE"||P==="INSERT_NODE")return{ok:!0,command:P,result:Y.execCommand(P,!1,[ce],R?{uid:R.uid,text:R.text}:null)}}catch{return{ok:!1,command:P,error:"command_failed",message:"runtime_command_throw"}}return s?.execCommand(P)??{ok:!1,command:P,error:"unsupported_command",message:"runtime_command_unsupported"}},iw=(w,A,R)=>{u3(w,A),ut="node",at=M0(R),ot=Qb(R),Mi=!0,D.dataset.contextMenuKind="node",D.dataset.contextMenuTargetNodeId=at??"",D.dataset.contextMenuTargetNodeKind=ot,Jb(R),at&&(l=at,d=at),S0()},rw=(w,A)=>{u3(w,A),ut="canvas",at=null,ot="canvas",Mi=!0,D.dataset.contextMenuKind="canvas",D.dataset.contextMenuTargetNodeId="",D.dataset.contextMenuTargetNodeKind="canvas",S0()},N0=w=>{he!==w&&(he=w,D.dataset.chromeVisibility=w,Ie())},m3=()=>{ui=!0,D.dataset.keyboardShortcutArmed="true"},nw=()=>{ui=!1,D.dataset.keyboardShortcutArmed="false"},E0=()=>document.fullscreenElement===ye||ye.contains(document.fullscreenElement),sw=()=>{let w=s?.instance;try{typeof w?.resize=="function"?w.resize():typeof w?.view?.resize=="function"&&w.view.resize(),w?.renderer?.setRootNodeCenter?.()}catch{D.dataset.fullscreenResizeStatus="failed";return}D.dataset.fullscreenResizeStatus="success"},Fc=()=>{let w=E0();D.dataset.fullscreenActive=w?"true":"false",ye.dataset.fullscreenActive=w?"true":"false",window.setTimeout(()=>{sw(),Ie()},80)},qc=()=>{s?.instance.setMode?.(m?"readonly":"edit"),D.dataset.readonly=m?"true":"false"},p3=w=>{D.dataset.searchStatus="ready",D.dataset.lastSearchQuery=w,q=!0,Ie()},aw=w=>{let A=typeof w=="number"?w:Number(String(w??"").replace("%","").trim());if(!Number.isFinite(A)){D.dataset.zoomInputStatus="invalid",Ie();return}let R=Math.max(10,Math.min(500,Math.round(A))),P=s?.instance.view;if(!P?.setScale){D.dataset.zoomInputStatus="unsupported",Ie();return}P.setScale(R/100,ye.clientWidth/2,ye.clientHeight/2),D.dataset.zoomInputStatus="success",Hs(s?.getSnapshot()),Ie()},g3=async w=>{if(w==="exitFullscreen"){document.fullscreenElement&&await document.exitFullscreen(),Fc();return}if(!(w!=="fullscreenCanvas"&&w!=="fullscreenPage")){if(typeof ye.requestFullscreen!="function"){D.dataset.commandStatus="fullscreen-unavailable",D.dataset.disabledReason="fullscreen-unavailable";return}await ye.requestFullscreen(),Fc()}},Hc=(w,A)=>{let R=Dc.toolbarActionMeta[w],P=R?.shortLabel??qb[w]??w,Y=R?.longLabel??qb[w]??w,K=R?.iconKey??w;return{id:w,label:P,longLabel:Y,icon:bq[K]??vq[w]??P.slice(0,1),iconKey:K,priority:R?.priority??999,overflowGroup:R?.overflowGroup??"view",cluster:R?.cluster??"main",disabled:A.disabledActions[w]??!0}},ow=()=>{let w=window.__MNOTE_MINDMAP_RUST_SHELL__;if(!w){v0(),D.dataset.uiShell="typescript-nodeview-dom",er.dataset.uiShellSource="missing-rust-shell",er.innerHTML='
    Leptos/Rust mindmap shell \u672A\u52A0\u8F7D
    ';return}let A=wq(F??ye.getBoundingClientRect().width),R=r3({activeNodeId:l,readonly:m,runtimeCapabilities:["runtimeCommand","kernelCommand","compatPatch","localView"],shell:{chromeVisibility:he,toolbarOverflow:{availableWidth:A.availableWidth,visibleActionIds:A.visibleActionIds,overflowActionIds:A.overflowActionIds,moreOpen:W&&A.overflowActionIds.length>0},fullscreen:{mode:E0()?"canvas":"none",isFullscreen:E0(),target:"mindmap-root",apiAvailable:typeof ye.requestFullscreen=="function"},sidebar:{activePanelId:x,panelOpen:y,triggerVisible:b,drawerWidth:k,collapsedByToggle:E},navigator:{searchOpen:q,minimapOpen:O,readonly:m,zoomPercent:c3().zoomPercent}}}),{mindmapId:P}=et(),Y=c3(),K=new Set(R.shell.toolbarOverflow.visibleActionIds),ce=new Set(R.shell.toolbarOverflow.overflowActionIds),J={mindmapId:P,toolbarGroups:[{id:"main",label:"\u4E3B\u5DE5\u5177",actions:a3.filter(ve=>K.size===0||K.has(ve)).map(ve=>Hc(ve,R))},{id:"overflow",label:"\u66F4\u591A",actions:a3.filter(ve=>ce.has(ve)).map(ve=>Hc(ve,R))},{id:"file",label:"\u6587\u4EF6",actions:aq.map(ve=>Hc(ve,R))}],sidebarPanels:Dc.sidebarPanels.map(ve=>({id:ve.id,kind:ve.kind,label:ve.label,icon:ve.icon,active:y&&ve.id===x,bodyTitle:ve.label,bodyCaption:ve.runtimeCapability,options:ve.options.map(qe=>({id:qe.id,label:qe.label,actionId:qe.actionId,value:qe.value,controlType:qe.controlType,preview:qe.preview,description:qe.description,readonly:qe.readonly??!1,compatPath:qe.compatPath})),bodyItems:ve.id==="outline"?Kb():[]})),navigator:{wordCount:Y.wordCount,nodeCount:Y.nodeCount,zoomPercent:Y.zoomPercent,readonly:m,minimapOpen:O},shell:R.shell};v0(),er.dataset.uiShellSource="leptos-rust-shell",Hi=w,Wn=w.mount(er,J),D.dataset.uiShell="leptos-rust-shell"},Ie=()=>{if(po(),!Wt){ow(),S0();return}v0(),D.dataset.uiShell="debug-fallback",Kn.dataset.testid="mindmap-command-toolbar",Kn.className="mnote-mindmap-command-toolbar",delete Kn.dataset.schemaSource,Zn.dataset.testid="mindmap-sidebar",Zn.className="mnote-mindmap-side-panel",delete Zn.dataset.schemaSource,Qn.dataset.testid="mindmap-bottom-bar",Qn.className="mnote-mindmap-bottom-bar",delete Qn.dataset.schemaSource;let w=a?s3(a):"",A=f??w,P=s!==null?"":" disabled",Y=["\u8282\u70B9\u6837\u5F0F","\u5BFC\u56FE\u6837\u5F0F","\u4E3B\u9898","\u7ED3\u6784","\u5927\u7EB2"].map((K,ce)=>``).join("");Kn.innerHTML=`
    `,Zn.innerHTML=`${Y}
    \u7ECF\u5178\u4E3B\u9898 \xB7 \u903B\u8F91\u7ED3\u6784
    `,Qn.innerHTML=`runtime \u8282\u70B9100%`},S0=()=>{let w=Xn.querySelector('[data-testid="mindmap-schema-context-menu"]');if(!Mi){w?.remove();return}w||(w=document.createElement("div"),w.className="mnote-mindmap-context-menu",w.dataset.testid="mindmap-schema-context-menu",w.dataset.uiSource="typescript-nodeview-bridge",Xn.append(w)),w.dataset.contextMenuKind=ut,w.dataset.contextMenuTargetNodeKind=ot;let R=r3({activeNodeId:ut==="node"?at??l:l,readonly:m,runtimeCapabilities:["runtimeCommand","kernelCommand","compatPatch","localView"]}),P=Dc.contextMenuItems.filter(J=>J.requiresNode===(ut==="node")),Y=J=>R.disabledActions[J]?!0:ut!=="node"?!1:ot==="root"&&(J==="insertSiblingAfter"||J==="deleteNode")||ot==="generalization"&&(J==="insertChild"||J==="insertSiblingAfter"||J==="deleteNode"||J==="summary"||J==="associativeLine"||J==="expandCollapse");w.innerHTML=P.map(J=>{let ve=Y(J.actionId);return``}).join("");let K=Math.min(Math.max(8,nn),Math.max(8,ye.clientWidth-w.offsetWidth-8)),ce=Math.min(Math.max(8,Bs),Math.max(8,ye.clientHeight-w.offsetHeight-8));w.style.left=`${K}px`,w.style.top=`${ce}px`},x3=()=>{po(),D.dataset.stage="loading",xt.dataset.runtimeEngine="loading",xt.replaceChildren(),Ie()},y3=w=>w==="mnote-web-tree-live"||w==="externalTreeLive"||w==="nodeViewUpdate",lw=w=>w==="mnote-web-tree-live"||w==="externalTreeLive",hw=()=>D.dataset.stage==="ready"&&xt.dataset.runtimeReady==="true"&&a!==null,dw=()=>{if(s?.instance.renderer?.textEdit?.isShowTextEdit?.())return!0;let A=document.activeElement;if(A instanceof HTMLElement&&A.closest(".smm-node-edit-wrap"))return!0;let R=document.querySelector('.smm-node-edit-wrap[contenteditable="true"]');return R instanceof HTMLElement&&R.style.display!=="none"&&R.getClientRects().length>0},v3=w=>{let A=s?.instance.renderer?.textEdit;if(!A?.isShowTextEdit?.())return!1;let R=Ds(A.getCurrentEditNode?.())??l??d,P=A.getEditText?.();if(D.dataset.lastRuntimeTextEditFlushReason=w,R&&typeof P=="string"&&P.length>0){D.dataset.lastRuntimeTextEditFlushNodeId=R,D.dataset.lastRuntimeTextEditFlushText=P;let{mindmapId:Y}=et(),K=Mr();K&&a&&i3({documentId:K,mindmapId:Y,commands:[{type:"updateText",mindmapId:Y,nodeId:R,text:P}],projectionRevision:a.kernelRevision}).catch(J=>{D.dataset.lastRuntimeTextEditFlushError=J instanceof Error?J.message:String(J)})}return A.hideEditTextBox?.(),!0},b3=(w,A="text_edit")=>{D.dataset.runtimeProjectionDeferred=A,D.dataset.lastRuntimeProjectionDeferReason=w,D.dataset.runtimeProjectionDeferredAt=String(Date.now())},w3=()=>{D.dataset.runtimeProjectionDeferred="",D.dataset.lastRuntimeProjectionDeferReason="",D.dataset.runtimeProjectionDeferredAt=""},Fs=(w,A="")=>{D.dataset.backgroundProjectionRefresh=w,D.dataset.backgroundProjectionRefreshMessage=A,Ie()},an=(w,A)=>{let{mindmapId:R}=et();po(),D.dataset.stage=w,xt.dataset.runtimeEngine="error",xt.innerHTML=`
    ${Rs(w)}${Rs(R)}${Rs(A)}
    `,Ie()},qs=async(w,A)=>{if(D.dataset.commandCount=String(w.length),D.dataset.commandHasProjection=a?"true":"false",D.dataset.commandFailedAction="",D.dataset.commandFailedMessage="",w.length===0||!a){D.dataset.commandStatus="skipped";return}let{mindmapId:R}=et(),P=Mr();if(!P){an("command_failed",`mindmapId=${R}; documentId_missing`);return}D.dataset.commandStatus="pending";try{let Y=await ZF({documentId:P,mindmapId:R,commands:w,projectionRevision:a.kernelRevision});if(Ui)return;let K=A?T0(Y,A,et().rootNodeId):void 0;await N3(Y,"commandRefresh",K),f=null,D.dataset.commandStatus="success"}catch(Y){let K=Y instanceof Error?Y.message:String(Y);D.dataset.commandStatus="failed",D.dataset.commandFailedAction=D.dataset.lastSchemaAction??"",D.dataset.commandFailedMessage=K,an("command_failed",`mindmapId=${R}; ${K}`)}},M3=async(w,A)=>{if(D.dataset.commandCount=String(w.length),D.dataset.commandHasProjection=a?"true":"false",D.dataset.commandFailedAction="",D.dataset.commandFailedMessage="",D.dataset.commandApplyMode="",D.dataset.commandLocalApplyErrors="",w.length===0||!a){D.dataset.commandStatus="skipped",D.dataset.commandApplyMode="skipped",Ps();return}if(!XF(w)){D.dataset.commandApplyMode="refresh:unsupported",Ps(),await qs(w,A);return}let R=Yb();if(!R){D.dataset.commandApplyMode="refresh:no_blob",Ps(),await qs(w,A);return}let P=YF(R,w);if(P.errors.length>0){D.dataset.commandApplyMode="refresh:local_errors",D.dataset.commandLocalApplyErrors=P.errors.join("|"),Ps(),await qs(w,A);return}let{mindmapId:Y}=et(),K=Mr();if(!K){an("command_failed",`mindmapId=${Y}; documentId_missing`);return}D.dataset.commandStatus="pending",D.dataset.commandApplyMode="local";try{let ce=await i3({documentId:K,mindmapId:Y,commands:w,projectionRevision:a.kernelRevision});if(Ui)return;if(Xb(P.data,ce.kernelRevision),A){let J=T0(a,A,et().rootNodeId);l=J,d=J,window.setTimeout(()=>{go(J)},0)}f=null,D.dataset.commandStatus="success",Ie()}catch(ce){let J=ce instanceof Error?ce.message:String(ce);D.dataset.commandStatus="failed",D.dataset.commandFailedAction=D.dataset.lastSchemaAction??"",D.dataset.commandFailedMessage=J,Ps(),an("command_failed",`mindmapId=${Y}; ${J}`)}},cw=async w=>{if(!a)return;let{mindmapId:A}=et(),R=Mr();if(!R){an("command_failed",`mindmapId=${A}; documentId_missing`);return}D.dataset.viewCommandStatus="pending";try{if(await i3({documentId:R,mindmapId:A,commands:[JF(A,w)],projectionRevision:a.kernelRevision}),Ui)return;a.view=w,D.dataset.viewCommandStatus="success"}catch(P){an("command_failed",`mindmapId=${A}; ${P instanceof Error?P.message:String(P)}`)}},Hs=w=>{let A=fq(w);A&&(L=A,D.dataset.lastViewPatch=JSON.stringify(A),I!==null&&window.clearTimeout(I),I=window.setTimeout(()=>{I=null;let R=L;L=null,R&&cw(R)},350))},uw=w=>{if(s){if(w==="editNode"){let A=s.instance.renderer,R=s.instance.keyCommand,P=A?.textEdit,Y=h??A?.activeNodeList?.[0]??null;if(Y&&typeof P?.show=="function")P.show({node:Y,isFromKeyDown:!1}),D.dataset.editNodeStatus="opened";else if(typeof R?.getShortcutFn=="function"){let K=R.getShortcutFn("F2")[0];typeof K=="function"?(K(),D.dataset.editNodeStatus="opened"):D.dataset.editNodeStatus="unsupported"}else D.dataset.editNodeStatus="unsupported";return}if(w==="centerRoot"&&s.instance.renderer?.setRootNodeCenter?.(),w==="zoomOut"&&s.instance.view?.narrow?.(),w==="zoomIn"&&s.instance.view?.enlarge?.(),w==="fitView"&&s.instance.view?.reset?.(),w==="fullscreenCanvas"||w==="fullscreenPage"||w==="exitFullscreen"){g3(w);return}if(w==="search"){q?(q=!1,D.dataset.lastSearchQuery="",D.dataset.searchStatus="closed",Ie()):p3("");return}if(w==="showMenu"){N0("visible"),w0();return}if(w==="expandCollapse"){s.instance.renderer?.toggleActiveExpand?.(),D.dataset.contextMenuActionStatus="success",D.dataset.lastContextMenuAction=w,Ie();return}if(w==="copyNodeText"){let A=at??l,R=a?xq(a.root,A):"";D.dataset.copiedNodeText=R,D.dataset.contextMenuActionStatus="success",D.dataset.lastContextMenuAction=w;return}w==="readonly"&&(m=!m,qc()),Hs(s.getSnapshot()),Ie()}},fw=(w,A,R)=>{if(!s)return;let P=Fb(w,R);if(!P)return;let Y=zt(s.instance.getThemeConfig?.())?s.instance.getThemeConfig?.():{};if(R&&P.startsWith(`nodes.${R}.data.`)&&h){let K=P.split(".data.")[1]??"";if(K==="shape"){h.setShape?.(A);return}if(K){let ce=K==="fontWeight"&&A===!0?"bold":K==="fontStyle"&&A===!0?"italic":A;h.setData?.({[K]:ce});return}}if(P.startsWith("style.map.")){let K=P.slice(10);if(!K)return;s.instance.setThemeConfig?.({...Y,[K]:A},!1)}},mw=(w,A,R)=>{if(!(!s||A.source!=="sidebar")){if(w==="setLayout"){s.instance.setLayout?.(A.value);return}if(w==="setTheme"){s.instance.setTheme?.(A.value);return}w==="painter"&&typeof A.compatPath=="string"&&fw(A.compatPath,A.value,R)}},A0=async(w,A={})=>{let R=Db(w);if(!R||r3({activeNodeId:ut==="node"?at??l:l,readonly:m,runtimeCapabilities:["runtimeCommand","kernelCommand","compatPatch","localView"]}).disabledActions[w])return;if(D.dataset.lastSchemaAction=w,R.target==="localView"){uw(w);return}let Y=mq(w),K=ut==="node"?at??d??l:d??l;if(R.requiresActiveNode){let qe=Pc(K);qe&&(K=qe,l=qe,d=qe)}D.dataset.lastSchemaActiveNodeId=K??"";let ce=et().mindmapId;mw(w,A,K);let J=iq({actionId:w,mindmapId:ce,activeNodeId:K,node:Y,value:A.value??(w==="editNode"?s3(a):!0),source:A.source,runtimeRevision:a?.kernelRevision??null,kernelRevision:a?.kernelRevision??null}),ve=Fb(A.compatPath,K);if(ve&&(J={command:eq({mindmapId:ce,path:ve,value:A.value??!0,source:A.source??"sidebar",actionId:w,runtimeRevision:a?.kernelRevision??null,kernelRevision:a?.kernelRevision??null})}),D.dataset.lastSchemaCommand=J?.command?JSON.stringify(J.command):"",D.dataset.lastSchemaRuntimeCommand=J?.runtimeCommand??"",!!J){if(R.target==="runtimeCommand"&&J.runtimeCommand){if(J.command){Vb(J.runtimeCommand),R.requiresActiveNode&&go(K);let qe=await tw(J.runtimeCommand,K,Y),C0=Y?.uid??(w==="deleteNode"?et().rootNodeId:K);if(qe?.ok){Y?.uid&&(J.runtimeCommand==="INSERT_CHILD_NODE"||J.runtimeCommand==="INSERT_NODE")&&window.setTimeout(()=>{go(Y.uid)},0),M3([J.command],C0);return}D.dataset.commandApplyMode="refresh:runtime_failed",D.dataset.commandRuntimeError=qe?.error??"unknown",D.dataset.commandRuntimeMessage=qe&&"message"in qe?qe.message??"":"",Ps(),qs([J.command],C0);return}s?.execCommand(J.runtimeCommand);return}J.command&&qs([J.command])}},pw=w=>{let A=w.target instanceof Node&&ye.contains(w.target);return nq({debugChromeEnabled:Wt,bridgeReady:!!s,readonly:m,isComposing:w.isComposing,isEditableTarget:uq(w.target),targetInsideRoot:A,keyboardShortcutArmed:ui})},gw=w=>{if(!pw(w))return;let A=rq(w);if(!A)return;w.preventDefault(),w.stopPropagation(),w.stopImmediatePropagation(),D.dataset.lastShortcutKey=w.key,D.dataset.lastShortcutAction=A;let R=a?Bb(a,et().rootNodeId):et().rootNodeId;if(Db(A)?.requiresActiveNode){let Y=Pc(d??l);if(Y===R&&(A==="insertSiblingAfter"||A==="deleteNode")){let K=Pc(null);K&&K!==R?Y=K:a&&(Y=Pb(a.root)??Y)}if(Y)l=Y,d=Y;else{D.dataset.lastShortcutActionBlocked=A;return}}if((d??l)===R&&(A==="insertSiblingAfter"||A==="deleteNode")){D.dataset.lastShortcutActionBlocked=A;return}A0(A,{source:"toolbar"})},xw=w=>{if(w.type==="node_active"){let Y=l;l=Ds(w.args[0])??l,h=typeof w.args[0]=="object"&&w.args[0]!==null?w.args[0]:null,(!d||d===Y)&&(d=l),Ie();return}if(w.type==="view_data_change"||w.type==="scale"||w.type==="translate"){D.dataset.lastViewEvent=w.type,Hs(w.snapshot??s?.getSnapshot());return}if(w.type!=="data_change"||!a||!w.snapshot)return;let A=Os(w.snapshot);D.dataset.lastDataChangeSeenAt=String(Date.now());let R=zb(c,Date.now());if(c=R.state,Bc(),D.dataset.lastDataChangeSuppressed=R.suppressed?"true":"false",D.dataset.lastDataChangeSuppressionExpired=R.expired?"true":"false",R.suppressed){o=A;return}let P=tq({mindmapId:et().mindmapId,previous:o??a.root,next:A});if(o=A,D.dataset.lastDataChangeDiffCommandCount=String(P.commands.length),D.dataset.lastDataChangeCompatPatchCount=String(P.compatPatches.length),P.commands.length===0&&P.compatPatches.length===0){D.dataset.lastDataChangeRefreshTriggered="false";return}D.dataset.lastDataChangeRefreshTriggered="false",M3([...P.commands,...P.compatPatches])},T3=w=>typeof w=="string"&&w.trim().length>0?w.trim():zt(w)&&typeof w.template=="string"&&w.template.trim().length>0?w.template.trim():null,yw=(w,A,R)=>{if(!s)return!1;let{mindmapId:P,rootNodeId:Y}=et();if(D.dataset.mountedMindmapId&&D.dataset.mountedMindmapId!==P)return!1;let K=wi(w.root);if(typeof s.instance.setData!="function")return!1;a={...w,root:Os(K)},l=T0(w,R??l,Y),h=null,d=l,o=Os(a.root);let ce=T3(w.layout),J=T3(w.theme),ve=zt(w.themeConfig)?w.themeConfig:null;return ce&&s.instance.setLayout?.(ce,!0),J&&s.instance.setTheme?.(J,!0),ve&&s.instance.setThemeConfig?.(ve,!0),s.instance.setData(K),Vn+=1,D.dataset.runtimeProjectionApplyCount=String(Vn),D.dataset.lastRuntimeProjectionApplyReason=A,xt.dataset.runtimeEngine="simple-mind-map",xt.dataset.runtimeReady="true",D.dataset.stage="ready",Rc(P,s),qc(),window.setTimeout(()=>{go(l)},0),Ie(),!0},N3=async(w,A,R)=>{let{mindmapId:P,rootNodeId:Y}=et();if(Gt&&(s?.instance.off?.("node_contextmenu",Gt),Gt=null),s?.destroy(),Rc(P,null),a={...w,root:Os(w.root)},l=T0(w,R,Y),h=null,d=l,o=Os(a.root),br+=1,D.dataset.runtimeMountCount=String(br),D.dataset.lastRuntimeMountReason=A,D.dataset.mountedMindmapId=P,D.dataset.mountedRootNodeId=Y,xt.dataset.runtimeEngine="simple-mind-map",xt.dataset.runtimeReady="false",xt.replaceChildren(),Ie(),s=await KF({el:xt,projection:w,mode:m?"readonly":"edit",runtimeOptions:{fit:!0,mousewheelAction:"zoom",enableFreeDrag:!0,enableCtrlKeyNodeSelection:!0,useLeftKeySelectionRightKeyDrag:!0,createNewNodeBehavior:"activeOnly"},onEvent:xw}),Ui){s.destroy();return}s.refreshProjection=E3,Gt=(...K)=>{let[ce,J]=K;ce instanceof MouseEvent&&(ce.preventDefault(),ce.stopPropagation(),iw(ce.clientX,ce.clientY,J))},s.instance.on?.("node_contextmenu",Gt),xt.dataset.runtimeReady="true",D.dataset.stage="ready",Rc(P,s),qc(),window.setTimeout(()=>{go(l)},0),Ie()},k0=async(w="fetchScene",A=0)=>{let R=++n,{mindmapId:P,rootNodeId:Y}=et();wr!==null&&(window.clearTimeout(wr),wr=null);let K=Mr();if(!K){if(D.dataset.documentIdResolveStatus="missing",D.dataset.documentIdResolveRetryCount=String(A),A<30){y3(w)||x3(),wr=window.setTimeout(()=>{wr=null,Ui||k0(w,A+1)},100);return}an("projection_load_failed",`mindmapId=${P}; documentId_missing`);return}D.dataset.documentIdResolveStatus="ready",D.dataset.lastFetchSceneReason=w;let ce=y3(w)&&hw();if(ce&&lw(w)){b3(w,"live_signal"),Fs("deferred","usability_first");return}ce?Fs("pending"):x3();try{if(R!==n)return;let J=await QF({documentId:K,mindmapId:P,endpoint:`/api/mindmap/${encodeURIComponent(K)}/${encodeURIComponent(P)}?view=simple_mind_map_scene&queryName=mindmap.simple_mind_map_scene.get&rootNodeId=${encodeURIComponent(Y)}`});if(R!==n||Ui)return;try{let ve=Rb({documentId:K,mindmapId:P,data:wi(J.root)}),qe=await fetch(ve.endpoint,ve.init);D.dataset.initialCreateOnlyStatus=qe.ok?"success":"failed"}catch{D.dataset.initialCreateOnlyStatus="failed"}if(ce&&dw()){b3(w),Fs("deferred","text_edit");return}if(ce&&yw(J,w)){w3(),Fs("success");return}await N3(J,w),w3(),Fs("success")}catch(J){if(ce){Fs("failed",J instanceof Error?J.message:String(J));return}an("projection_load_failed",`mindmapId=${P}; ${J instanceof Error?J.message:String(J)}`)}},E3=async(w="externalTreeLive")=>{if(!Ui){if(sn){mo=w;return}sn=!0;try{await k0(w)}finally{sn=!1;let A=mo;mo=null,A&&!Ui&&E3(A)}}};return ge.addEventListener("pointerdown",w=>w.stopPropagation()),ge.addEventListener("mousedown",w=>w.stopPropagation()),ye.addEventListener("pointerleave",()=>{b0(),!Mi&&he==="visible"&&N0(zc(he,"pointerLeave"))}),ye.addEventListener("pointerenter",()=>{he=zc(he,"pointerEnter"),D.dataset.chromeVisibility=he}),ye.addEventListener("pointerdown",()=>{m3(),he!=="visible"&&N0(zc(he,"restoreClick"))},!0),ye.addEventListener("click",()=>{m3(),he!=="visible"&&N0(zc(he,"restoreClick"))}),ge.addEventListener("mnote:mindmap-shell:action",w=>{let A=w.detail,R=A?.actionId;if(R==="search"){if(typeof A?.value=="string"){p3(A.value);return}if(A?.value===!1){q=!1,D.dataset.lastSearchQuery="",D.dataset.searchStatus="closed",Ie();return}}R&&(b0(),A0(R,A))}),ge.addEventListener("mnote:mindmap-shell:zoom",w=>{let A=w.detail;aw(A?.percent)}),ge.addEventListener("mnote:mindmap-shell:minimap",w=>{let A=w.detail;O=typeof A?.open=="boolean"?A.open:!O,D.dataset.minimapOpen=O?"true":"false",Ie()}),ge.addEventListener("mnote:mindmap-shell:toolbar-overflow",w=>{let A=w.detail;W=typeof A?.moreOpen=="boolean"?A.moreOpen:!W,Ie()}),ge.addEventListener("mnote:mindmap-shell:panel",w=>{let A=w.detail,R=A?.event??"togglePanel";if(R==="restoreTrigger"){b=!0,y=!0,E=!1,Ie();return}if(R==="hideTrigger"){b=!1,y=!1,E=!0,Ie();return}if(R==="closeDrawer"){y=!1,E=!1,Ie();return}A?.panelId&&(A.panelId===x?y=!y:(x=A.panelId,y=!0),b=!0,E=!1,Ie())}),Q=()=>Fc(),document.addEventListener("fullscreenchange",Q),oe=()=>{v3("page_lifecycle")},window.addEventListener("pagehide",oe),window.addEventListener("beforeunload",oe),ge.addEventListener("input",w=>{if(!Wt)return;let A=w.target;A instanceof HTMLInputElement&&A.dataset.testid==="mindmap-command-text-input"&&(f=A.value)}),typeof ResizeObserver<"u"&&(ne=new ResizeObserver(w=>{let A=w[0]?.contentRect.width??null;A===null||Math.abs(A-(F??0))<1||(F=A,Wt||Ie())}),ne.observe(ye)),re=w=>{let A=w.target;if((!(A instanceof Node)||!ye.contains(A))&&nw(),Mi){if(A instanceof Node){let R=Xn.querySelector('[data-testid="mindmap-schema-context-menu"]');if(R instanceof HTMLElement&&R.contains(A))return}w0()}W&&(A instanceof Node&&er.contains(A)||b0())},le=w=>{if(w.key==="Escape"){if(Mi){w.preventDefault(),w0();return}if(E0()){w.preventDefault(),g3("exitFullscreen");return}W&&(w.preventDefault(),b0())}},Ce=w=>gw(w),te=w=>{w.detail?.type==="mindmap"&&Wb()},document.addEventListener("pointerdown",re),document.addEventListener("keydown",le),document.addEventListener("keydown",Ce,!0),window.addEventListener("mnote:page-width-preference-changed",te),ge.addEventListener("click",w=>{w.stopPropagation();let A=w.target;if(!(A instanceof HTMLElement))return;let R=A.closest("button");if(R instanceof HTMLButtonElement){if(!Wt){w.preventDefault();let P=R.dataset.mindmapContextActionId;if(P){if(R.disabled||R.dataset.disabled==="true")return;w0(),A0(P);return}if(R.closest('[data-testid="mindmap-rust-shell"]'))return;let Y=R.dataset.mindmapActionId;Y&&A0(Y);let K=R.dataset.mindmapSidebarPanelId;K&&(x=K,Ie());return}if(R.dataset.testid==="mindmap-command-update-text"){w.preventDefault();let P=ye.querySelector('[data-testid="mindmap-command-text-input"]'),Y=f??(P instanceof HTMLInputElement?P.value:s3(a));D.dataset.lastCommandText=Y,qs([{type:"updateText",mindmapId:et().mindmapId,nodeId:l??et().rootNodeId,text:Y}])}R.dataset.testid==="mindmap-command-add-child"&&(w.preventDefault(),s?.execCommand("INSERT_CHILD_NODE")),R.dataset.testid==="mindmap-command-add-sibling-after"&&(w.preventDefault(),s?.execCommand("INSERT_NODE")),R.dataset.testid==="mindmap-command-delete-node"&&(w.preventDefault(),s?.execCommand("REMOVE_NODE")),R.dataset.testid==="mindmap-toolbar-undo"&&(w.preventDefault(),s?.execCommand("BACK")),R.dataset.testid==="mindmap-toolbar-redo"&&(w.preventDefault(),s?.execCommand("FORWARD")),R.dataset.testid==="mindmap-toolbar-summary"&&(w.preventDefault(),s?.execCommand("ADD_GENERALIZATION")),R.dataset.testid==="mindmap-bottom-center-root"&&(w.preventDefault(),s?.instance.renderer?.setRootNodeCenter?.(),Hs(s?.getSnapshot())),R.dataset.testid==="mindmap-bottom-zoom-out"&&(w.preventDefault(),s?.instance.view?.narrow?.(),Hs(s?.getSnapshot())),R.dataset.testid==="mindmap-bottom-zoom-in"&&(w.preventDefault(),s?.instance.view?.enlarge?.(),Hs(s?.getSnapshot()))}}),ge.addEventListener("contextmenu",w=>{if(Wt)return;let A=w.target;if(A instanceof Element&&(A.closest(".smm-node")||A.closest('[class*="generalization_"]'))){w.preventDefault();return}w.preventDefault(),w.stopPropagation(),rw(w.clientX,w.clientY)},!0),y0.append(xt),Wt?(Yn.append(xt,Zn),D.append(Kn,Yn,Qn)):(Xn.append(er),Yn.append(y0,Xn),D.append(Yn)),ye.append(D,x0("nw"),x0("ne"),x0("sw"),x0("se")),ge.append(ye,ft),po(),k0("initial"),{dom:ge,update(w){if(w.type.name!==r.type.name)return!1;let A=d3(r);return r=w,r.attrs.mnoteBlockType!=="mindmap"?!1:(po(),d3(r)!==A&&k0("nodeViewUpdate"),!0)},stopEvent:()=>!0,ignoreMutation:()=>!0,destroy(){v3("node_view_destroy"),Ui=!0,wr!==null&&window.clearTimeout(wr),I!==null&&window.clearTimeout(I),ne?.disconnect(),re&&document.removeEventListener("pointerdown",re),le&&document.removeEventListener("keydown",le),Ce&&document.removeEventListener("keydown",Ce,!0),te&&window.removeEventListener("mnote:page-width-preference-changed",te),Q&&document.removeEventListener("fullscreenchange",Q),oe&&(window.removeEventListener("pagehide",oe),window.removeEventListener("beforeunload",oe)),Gt&&s?.instance.off?.("node_contextmenu",Gt);let{mindmapId:w}=et();Rc(w,null),s?.destroy(),v0(),s=null}}}}function Tq(){return({node:i})=>{let e=i,t=document.createElement("p"),r=()=>{let n=e.attrs.blockId;typeof n=="string"&&n.length>0?(t.dataset.blockId=n,t.id=n):(t.removeAttribute("data-block-id"),t.removeAttribute("id"))};return r(),{dom:t,contentDOM:t,update(n){return n.type.name!==e.type.name||n.attrs.mnoteBlockType==="mindmap"?!1:(e=n,r(),!0)}}}}var Nq=C3.extend({addAttributes(){return{...this.parent?.(),blockId:{default:null,parseHTML:i=>i.getAttribute("data-block-id"),renderHTML:i=>hq(i.blockId)},mnoteBlockType:{default:null,parseHTML:i=>i.getAttribute("data-mnote-block-type"),renderHTML:i=>dq(i)},mindmapId:{default:null,parseHTML:i=>i.getAttribute("data-mnote-mindmap-id"),renderHTML:()=>({})},rootNodeId:{default:null,parseHTML:i=>i.getAttribute("data-mnote-root-node-id"),renderHTML:()=>({})},projectionVersion:{default:null,parseHTML:i=>Number(i.getAttribute("data-mnote-projection-version")??1),renderHTML:()=>({})},mindmapWidth:{default:null,parseHTML:i=>fo(i.getAttribute("data-mnote-mindmap-width"),"width"),renderHTML:()=>({})},mindmapHeight:{default:null,parseHTML:i=>fo(i.getAttribute("data-mnote-mindmap-height"),"height"),renderHTML:()=>({})}}},addNodeView(){let i=Mq(this.editor),e=Tq();return({node:t,getPos:r})=>t.attrs.mnoteBlockType==="mindmap"?i({node:t,getPos:r}):e({node:t})}}),Eq={name:"paragraph",create:()=>Nq,commands:{set_paragraph:i=>i.chain().focus().setParagraph().run()},selection_keys:["paragraph"],selection_state:i=>({paragraph:i.isActive("paragraph")})};function Ese(){I3(Eq)}export{lq as createDefaultMindmapSidebarRuntimeState,fo as normalizeMindmapDimension,Ese as register_paragraph,dq as renderMindmapBlockAttributes,cq as resolveCurrentDocumentId}; /*! Bundled license information: @svgdotjs/svg.js/dist/svg.esm.js: diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/style.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/style.rs index 3017dde2..51223249 100644 --- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/style.rs +++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/style.rs @@ -208,12 +208,32 @@ pub(crate) const SPIKE_STYLE: &str = r#" } .mnote-mindmap-placeholder { + position: relative; width: min(1280px, calc(100vw - 340px)); + min-width: min(900px, calc(100vw - 80px)); + max-width: var(--mnote-mindmap-block-max-width, none); margin: 10px 0; margin-left: 50%; transform: translateX(-50%); } +.mnote-mindmap-resize-preview { + position: absolute; + top: 0; + left: 50%; + z-index: 8; + box-sizing: border-box; + pointer-events: none; + transform: translateX(-50%); + border: 1px dashed rgba(37, 99, 235, 0.78); + border-radius: 8px; + background: rgba(37, 99, 235, 0.05); +} + +.mnote-mindmap-resize-preview[hidden] { + display: none; +} + .mnote-mindmap-editor-root { position: relative; overflow: hidden; @@ -231,6 +251,57 @@ pub(crate) const SPIKE_STYLE: &str = r#" flex-direction: column; } +.mnote-mindmap-placeholder[data-mnote-mindmap-resized="true"] .mnote-leptos-mindmap-shell { + min-height: 0; +} + +.mnote-mindmap-resize-handle { + position: absolute; + z-index: 9; + width: 12px; + height: 12px; + padding: 0; + border: 1px solid rgba(37, 99, 235, 0.72); + border-radius: 50%; + background: #ffffff; + box-shadow: 0 1px 4px rgba(15, 23, 42, 0.18); + opacity: 0; + transition: opacity 120ms ease, transform 120ms ease; +} + +.mnote-mindmap-editor-root:hover .mnote-mindmap-resize-handle, +.mnote-mindmap-placeholder[data-mnote-mindmap-resizing="true"] .mnote-mindmap-resize-handle { + opacity: 1; +} + +.mnote-mindmap-resize-handle:hover { + transform: scale(1.12); +} + +.mnote-mindmap-resize-handle-nw { + top: 6px; + left: 6px; + cursor: nwse-resize; +} + +.mnote-mindmap-resize-handle-ne { + top: 6px; + right: 6px; + cursor: nesw-resize; +} + +.mnote-mindmap-resize-handle-sw { + bottom: 6px; + left: 6px; + cursor: nesw-resize; +} + +.mnote-mindmap-resize-handle-se { + right: 6px; + bottom: 6px; + cursor: nwse-resize; +} + .mnote-mindmap-command-toolbar, .mnote-mindmap-bottom-bar { display: flex; diff --git a/scripts/TESTING_REFERENCE.md b/scripts/TESTING_REFERENCE.md index 7e3b3785..8ad29839 100644 --- a/scripts/TESTING_REFERENCE.md +++ b/scripts/TESTING_REFERENCE.md @@ -28,17 +28,20 @@ node scripts/task167-local-markdown-title-body-options-no-convex-smoke.js node scripts/task490-runtime-surfaces-smoke.js ``` -其中 `task490-runtime-surfaces-smoke.js` 是轻量浏览器 smoke,只验证页面设置、Page AI drawer、slash menu、block handle menu 能在当前 leptos-tiptap 文档页打开;它不要求真实模型返回,也不替代更重的页面设置、Hermes/Page AI 或编辑器菜单专项脚本。 +其中 `task490-runtime-surfaces-smoke.js` 是轻量浏览器 smoke,验证页面设置、Page AI drawer 打开/关闭、Page AI stop-run abort、slash menu、block handle menu 能在当前 leptos-tiptap 文档页工作;它使用 mock run 覆盖停止按钮,不要求真实模型返回,也不替代更重的页面设置、Hermes/Page AI 或编辑器菜单专项脚本。 ### 0.1 当前主链优先脚本 - 入口与认证:`task114-rust-web-gateway-entry-smoke.js`、`task159-auth-entry-smoke.js`、`task097-homepage-entry-smoke.js` -- Rust SSR 文档页 / Page Aggregate:local-first 默认优先 `task167-local-markdown-title-body-options-no-convex-smoke.js`;cloud/control-plane 文档可补跑 `task110-page-title-single-truth-smoke.js`、`task-page-aggregate-body-sync-smoke.js`、`task-page-aggregate-options-sync-smoke.js`、`task-page-aggregate-refresh-persistence-smoke.js` +- Rust SSR 文档页 / Page Aggregate:local-first 默认优先 `task167-local-markdown-title-body-options-no-convex-smoke.js`;Page Aggregate browser conversion / compat fallback 改动补跑 `task522-page-aggregate-compat-fallback-contract.js`;cloud/control-plane 文档可补跑 `task110-page-title-single-truth-smoke.js`、`task-page-aggregate-body-sync-smoke.js`、`task-page-aggregate-options-sync-smoke.js`、`task-page-aggregate-refresh-persistence-smoke.js` - leptos-tiptap runtime surface:`task490-runtime-surfaces-smoke.js`;需要验证保存回读可补跑 `task121-rust-web-editor-island-hydration-smoke.js`,但它仍使用 `/api/tree/commands create` 准备文档,不作为无 Convex 默认基线;需要验证浮层互斥和更多菜单状态时再跑 `task158-e30-menu-state-smoke.js` -- local-first 本地工作区:`task164-desktop-hot-local-folder-main-entry-smoke.js`、`task166-local-first-managed-workspace-no-convex-smoke.js`、`task167-local-markdown-title-body-options-no-convex-smoke.js`、`task436-local-markdown-open-document-external-change-smoke.js`、`task443-local-markdown-asset-upload-smoke.js`、`task451-local-markdown-conflict-resolution-ui-smoke.js`、`task452-local-search-index-browser-smoke.js`、`task453-local-folder-page-ai-changed-files-smoke.js` +- local-first 本地工作区:`task164-desktop-hot-local-folder-main-entry-smoke.js`、`task166-local-first-managed-workspace-no-convex-smoke.js`、`task167-local-markdown-title-body-options-no-convex-smoke.js`、`task436-local-markdown-open-document-external-change-smoke.js`、`task443-local-markdown-asset-upload-smoke.js`、`task451-local-markdown-conflict-resolution-ui-smoke.js`、`task452-local-search-index-browser-smoke.js`、`task453-local-folder-page-ai-changed-files-smoke.js`;WorkspacePath / ObjectIdentity runtime 消费统一改动补跑 `task524-workspace-object-identity-matrix-smoke.js` +- local-folder OCR:`task526-local-folder-ocr-api-smoke.js` 覆盖 mock OCR API、sidecar 落盘、jobs/status/read、`includeOcr=true` 搜索 owner page、显式插入 OCR 链接和 OCR sidecar Markdown resource tab 打开;它不代表真实 MinerU token/upload/zip runtime、前端 OCR 入口或全局 OCR 任务栏已完成。 - local Markdown conflict regression:`task510-local-markdown-conflict-regression-group.js` 串联真实冲突、连续上传、新建页面、外部恢复、多 tab CAS 与上传 409 孤儿策略;可用 `MNOTE_CONFLICT_REGRESSION_TASKS=a.js,b.js` 做定点子集。 - tree realtime / live cache:`task446-tree-rename-dual-browser-live-smoke.js`、`task447-tree-move-order-dual-browser-live-smoke.js`、`task448-tree-resync-recovery-dual-browser-smoke.js`、`task449-tree-sse-reconnect-snapshot-recovery-smoke.js` -- 资源对象与 mindmap:`task455-local-folder-mindmap-clean-smoke.js`、`task456-resource-object-shell-sync-smoke.js`、`task166-mindmap-phase6-block-smoke.js`、`task167-mindmap-kmind-parity-smoke.js`、`task168-mindmap-put-validator-smoke.js` +- 资源对象与 mindmap:`task455-local-folder-mindmap-clean-smoke.js`、`task456-resource-object-shell-sync-smoke.js`、`task166-mindmap-phase6-block-smoke.js`、`task167-mindmap-kmind-parity-smoke.js`、`task168-mindmap-put-validator-smoke.js`;Page AI mindmap skill / 资源生成改动补跑 `task503-mindmap-skill-capability-smoke.js`,真实 mindmap resource tab / Page AI target 改动补跑 `task525-page-ai-mindmap-resource-target-smoke.js` +- Page AI / agent history / ChatOnly:`task502-page-ai-agent-selector-context-smoke.js` 覆盖 Page AI agent/context/target picker、skills source 和 run payload;raw local resource target 改动补跑 `task520-page-ai-raw-resource-target-smoke.js`;`task504-page-ai-history-agent-filter-smoke.js` 覆盖 Page AI 历史按 agent 过滤;ChatOnly / provider session 绑定改动优先跑 `task512-chatonly-doubao-sync-smoke.js`,跨 provider 同步补跑 `task513-chatonly-provider-sync-smoke.js` +- Dev hot / OnlyOffice live bridge:Sidebar dev hot reload 入口改动补跑 `task514-sidebar-dev-hot-reload-gating-smoke.js`;OnlyOffice live bridge session / scope / HTTP 工具边界改动补跑 `task515-onlyoffice-live-scope-http-smoke.js`;bridge session/token/queue/current/close 和 `docKey/pageOrigin` 元数据改动补跑 `task516-onlyoffice-bridge-multisession-browser-smoke.js`;bridge plugin index / `Asc.plugin` direct loop / plugin 元数据透传改动补跑 `task517-onlyoffice-bridge-plugin-direct-smoke.js`;真实 ONLYOFFICE iframe / DocumentServer session、同文档双 tab、resource scope 和非 dry-run 写入落点改动补跑 `task518-onlyoffice-real-iframe-session-scope-smoke.js`;Page AI 真实 UI 选择 Office target 并冻结 live bridge session 改动补跑 `task523-page-ai-onlyoffice-real-target-session-smoke.js` - local resource lifecycle API:`task437-local-folder-asset-trash-lifecycle-smoke.js`,现在只验证 `local-file:*` delete / restore / purge,不再依赖普通 `.txt` 是否出现在 UI 文件树。 ### 0.2 需要退役或拆分的历史脚本 @@ -182,7 +185,7 @@ node scripts/task097-homepage-entry-smoke.js - local-first:优先使用 `task164-desktop-hot-local-folder-main-entry-smoke.js`、`task166-local-first-managed-workspace-no-convex-smoke.js`、`task167-local-markdown-title-body-options-no-convex-smoke.js`、`task455-local-folder-mindmap-clean-smoke.js` - 页面设置:`task160`、`task164-page-options-visible-effect-smoke.js` - 双栏:`task165` -- AI:优先使用 `task-hermes-page-ai-retirement-guard.js`、`task-page-block-ai-tools-smoke.js` 和当前 Hermes 页面 AI smoke;`task155`、`task156`、`task161` 等旧 `/api/ai-agent/run` smoke 只保留在本机 gitignored `recycle/scripts/retired-ai-agent-run-smokes/` 做历史对照 +- AI:优先使用 `task-hermes-page-ai-retirement-guard.js`、`task-page-block-ai-tools-smoke.js` 和当前 Hermes 页面 AI smoke;Page AI history / skills / ChatOnly 相关改动补跑 `task503-mindmap-skill-capability-smoke.js`、`task504-page-ai-history-agent-filter-smoke.js`、`task512-chatonly-doubao-sync-smoke.js`、`task513-chatonly-provider-sync-smoke.js`;OnlyOffice live bridge 改动补跑 `task515-onlyoffice-live-scope-http-smoke.js`、`task516-onlyoffice-bridge-multisession-browser-smoke.js`、`task517-onlyoffice-bridge-plugin-direct-smoke.js`、`task518-onlyoffice-real-iframe-session-scope-smoke.js`,其中 `task516/517` 覆盖 bridge session lifecycle 与 `docKey/pageOrigin` 元数据;Page AI 真实 target picker / Office live session 改动补跑 `task523-page-ai-onlyoffice-real-target-session-smoke.js`;`task155`、`task156`、`task161` 等旧 `/api/ai-agent/run` smoke 只保留在本机 gitignored `recycle/scripts/retired-ai-agent-run-smokes/` 做历史对照 原则: diff --git a/scripts/reasonix-acp-wrapper.mjs b/scripts/reasonix-acp-wrapper.mjs index 4bcfe09c..eac5c019 100644 --- a/scripts/reasonix-acp-wrapper.mjs +++ b/scripts/reasonix-acp-wrapper.mjs @@ -58,6 +58,37 @@ function stableIdPart(value, fallback) { return text || String(fallback || 'unknown'); } +function stableJson(value) { + try { + return JSON.stringify(value || null, null, 2); + } catch { + return 'null'; + } +} + +function buildMnotePromptText(userPrompt, context = {}) { + const capabilities = context.mnoteCapabilities || {}; + const envelope = capabilities.agentRunEnvelope || null; + if (!envelope || capabilities.attachMnoteCapabilities !== true) { + return userPrompt; + } + const primaryPath = envelope.primaryTarget?.workspacePath?.relativePath + || envelope.primaryTarget?.relativePath + || ''; + const allowedRoots = envelope.allowedRoots || capabilities.allowedRoots || []; + const guidance = [ + '', + 'MNote has attached a local-first agent run envelope.', + 'Use your native file tools to read and edit real files inside the current working directory and allowed roots.', + 'Do not use MNote doc/page write tools for ordinary local Markdown edits.', + primaryPath ? `Primary target relativePath: ${primaryPath}` : '', + `Allowed roots: ${stableJson(allowedRoots)}`, + `Agent run envelope: ${stableJson(envelope)}`, + '', + ].filter(Boolean).join('\n'); + return `${guidance}\n\n${userPrompt}`; +} + function buildMnoteToolPayload(toolName, rawArgs = {}, context = {}) { const { actorId, @@ -183,6 +214,35 @@ if (process.env.MNOTE_REASONIX_ACP_SELFTEST === '1') { if (contextualPayload.args.aiAccessScope?.permissionLevel !== 'read_write') { throw new Error('selftest expected aiAccessScope inherited from mnoteCapabilities'); } + const promptWithEnvelope = buildMnotePromptText('请把标题改成测试标题', { + mnoteCapabilities: { + attachMnoteCapabilities: true, + agentRunEnvelope: { + schema: 'mnote.agent_run_envelope.v1', + sourceKind: 'local_folder', + primaryTarget: { + workspacePath: { + relativePath: 'docs/current.md', + rootUri: 'file:///tmp/mnote-local', + }, + }, + allowedRoots: [{ rootUri: 'file:///tmp/mnote-local', permission: 'write' }], + resultPolicy: { + receiptSchema: 'mnote.agent_run_receipt.v1', + changedFiles: 'required', + }, + }, + }, + }); + if (!promptWithEnvelope.includes('mnote.agent_run_envelope.v1')) { + throw new Error('selftest expected prompt to include agentRunEnvelope schema'); + } + if (!promptWithEnvelope.includes('docs/current.md')) { + throw new Error('selftest expected prompt to include primary target relativePath'); + } + if (!promptWithEnvelope.includes('native file tools')) { + throw new Error('selftest expected prompt to instruct native file tools'); + } process.stderr.write('[reasonix-acp-mnote] selftest ok\n'); process.exit(0); } @@ -575,7 +635,7 @@ onRequest('session/prompt', async (params) => { const mnoteCapabilities = params.mnoteCapabilities || {}; const useMnoteTools = mnoteCapabilities.attachMnoteCapabilities === true; const loop = useMnoteTools ? session.mnoteLoop : session.chatLoop; - const promptText = text; + const promptText = buildMnotePromptText(text, toolContext); let stopReason = 'end_turn'; let hasAssistantOutput = false; let hasToolCall = false; diff --git a/scripts/task455-local-folder-mindmap-clean-smoke.js b/scripts/task455-local-folder-mindmap-clean-smoke.js index f271b93e..909b256d 100644 --- a/scripts/task455-local-folder-mindmap-clean-smoke.js +++ b/scripts/task455-local-folder-mindmap-clean-smoke.js @@ -78,6 +78,32 @@ async function insertMindmapThroughSlash(page) { }); } +async function assertMindmapStyleDrawerClosedByDefault(page) { + await page.locator('[data-testid="mindmap-rust-shell"]').first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + const state = await page.evaluate(() => { + const shell = document.querySelector('[data-testid="mindmap-rust-shell"]'); + const sidebar = document.querySelector('[data-testid="mindmap-schema-sidebar"]'); + const drawer = document.querySelector('[data-testid="mindmap-schema-sidebar-drawer"]'); + return { + panelOpen: shell instanceof HTMLElement ? shell.getAttribute("data-sidebar-panel-open") || "" : "", + activePanel: shell instanceof HTMLElement ? shell.getAttribute("data-sidebar-active-panel") || "" : "", + sidebarPresent: sidebar instanceof HTMLElement, + sidebarPanelOpen: sidebar instanceof HTMLElement ? sidebar.getAttribute("data-panel-open") || "" : "", + drawerVisible: drawer instanceof HTMLElement && drawer.getClientRects().length > 0, + bodyText: document.body?.innerText || "", + }; + }); + assert.equal(state.panelOpen, "false", `节点样式抽屉不应默认打开: ${JSON.stringify(state)}`); + if (state.sidebarPanelOpen) { + assert.equal(state.sidebarPanelOpen, "false", `sidebar panel 状态应与 shell 保持默认收起: ${JSON.stringify(state)}`); + } + assert.equal(state.drawerVisible, false, `节点样式抽屉不应默认渲染遮挡画布: ${JSON.stringify(state)}`); + return state; +} + async function assertSlashMenuAnchorsAfterMindmap(page) { const editor = page.locator('.document-pane[data-pane-role="primary"] .editor-surface .ProseMirror[contenteditable="true"]').first(); await editor.click({ timeout: UI_TIMEOUT_MS }); @@ -122,6 +148,134 @@ async function assertSlashMenuAnchorsAfterMindmap(page) { return state; } +async function resizeMindmapThroughCornerHandle(page) { + await page.locator('[data-testid="mindmap-resize-handle-nw"]').first().waitFor({ + state: "attached", + timeout: UI_TIMEOUT_MS, + }); + const before = await page.evaluate(() => { + const placeholder = document.querySelector('[data-testid="mnote-mindmap-placeholder"]'); + const root = document.querySelector('[data-testid="mnote-mindmap-editor-root"]'); + const editor = document.querySelector('.document-pane[data-pane-role="primary"] .editor-surface .ProseMirror'); + const handles = Array.from(document.querySelectorAll('[data-mnote-mindmap-resize-handle]')) + .map((handle) => handle.getAttribute("data-mnote-mindmap-resize-handle") || "") + .sort(); + const rect = placeholder?.getBoundingClientRect(); + const editorRect = editor?.getBoundingClientRect(); + return { + handles, + rect: rect ? { width: Math.round(rect.width), height: Math.round(rect.height) } : null, + centerDelta: rect && editorRect ? Math.round((rect.left + rect.width / 2) - (editorRect.left + editorRect.width / 2)) : null, + cssMaxWidth: root instanceof HTMLElement ? getComputedStyle(root).getPropertyValue("--mnote-mindmap-block-max-width").trim() : "", + htmlCssMaxWidth: getComputedStyle(document.documentElement).getPropertyValue("--mnote-mindmap-block-max-width").trim(), + }; + }); + assert.deepEqual(before.handles, ["ne", "nw", "se", "sw"], `mindmap 应渲染四角 resize handle: ${JSON.stringify(before)}`); + assert(before.rect, `mindmap resize 前应能读取占位块尺寸: ${JSON.stringify(before)}`); + assert(Math.abs(before.centerDelta) <= 2, `初始 mindmap 应以正文列中心对齐: ${JSON.stringify(before)}`); + + await page.waitForFunction(() => { + const registry = window.__MNOTE_LEPTOS_MINDMAP_BRIDGES__ || {}; + return Object.values(registry).some((bridge) => typeof bridge?.instance?.resize === "function"); + }, null, { timeout: UI_TIMEOUT_MS }); + await page.evaluate(() => { + window.__MNOTE_TEST_MINDMAP_RESIZE_COUNT = 0; + const registry = window.__MNOTE_LEPTOS_MINDMAP_BRIDGES__ || {}; + Object.values(registry).forEach((bridge) => { + const instance = bridge?.instance; + if (!instance || typeof instance.resize !== "function" || instance.resize.__mnoteResizeCounterPatched) return; + const original = instance.resize.bind(instance); + const patched = function patchedMindmapResize() { + window.__MNOTE_TEST_MINDMAP_RESIZE_COUNT = Number(window.__MNOTE_TEST_MINDMAP_RESIZE_COUNT || 0) + 1; + return original(); + }; + patched.__mnoteResizeCounterPatched = true; + instance.resize = patched; + }); + }); + + const handleBox = await page.locator('[data-testid="mindmap-resize-handle-nw"]').first().boundingBox(); + assert(handleBox, "左上角 resize handle 应有可交互位置"); + await page.mouse.move(handleBox.x + handleBox.width / 2, handleBox.y + handleBox.height / 2); + await page.mouse.down(); + await page.mouse.move(handleBox.x + 160, handleBox.y + 110, { steps: 8 }); + await page.mouse.up(); + + await page.waitForFunction( + ({ previousWidth, previousHeight }) => { + const placeholder = document.querySelector('[data-testid="mnote-mindmap-placeholder"]'); + const scene = document.querySelector('[data-testid="leptos-mindmap-island"]'); + if (!(placeholder instanceof HTMLElement) || !(scene instanceof HTMLElement)) return false; + const width = Number(placeholder.dataset.mnoteMindmapWidth || 0); + const height = Number(placeholder.dataset.mnoteMindmapHeight || 0); + return width >= 320 + && height >= 240 + && width < previousWidth + && height < previousHeight + && scene.dataset.mnoteMindmapHeight === String(height); + }, + { previousWidth: before.rect.width, previousHeight: before.rect.height }, + { timeout: UI_TIMEOUT_MS }, + ); + + const resized = await page.evaluate(() => { + const placeholder = document.querySelector('[data-testid="mnote-mindmap-placeholder"]'); + const scene = document.querySelector('[data-testid="leptos-mindmap-island"]'); + const editor = document.querySelector('.document-pane[data-pane-role="primary"] .editor-surface .ProseMirror'); + const rect = placeholder?.getBoundingClientRect(); + const editorRect = editor?.getBoundingClientRect(); + return { + width: placeholder instanceof HTMLElement ? Number(placeholder.dataset.mnoteMindmapWidth || 0) : 0, + height: placeholder instanceof HTMLElement ? Number(placeholder.dataset.mnoteMindmapHeight || 0) : 0, + sceneHeight: scene instanceof HTMLElement ? Number(scene.dataset.mnoteMindmapHeight || 0) : 0, + rect: rect ? { width: Math.round(rect.width), height: Math.round(rect.height) } : null, + centerDelta: rect && editorRect ? Math.round((rect.left + rect.width / 2) - (editorRect.left + editorRect.width / 2)) : null, + cssMaxWidth: getComputedStyle(document.documentElement).getPropertyValue("--mnote-mindmap-block-max-width").trim(), + resizeCallCount: Number(window.__MNOTE_TEST_MINDMAP_RESIZE_COUNT || 0), + }; + }); + assert(Math.abs(resized.centerDelta) <= 2, `resize 后 mindmap 仍应以正文列中心对齐: ${JSON.stringify(resized)}`); + assert(resized.resizeCallCount <= 3, `拖动过程中不应连续触发 simple-mind-map resize 重绘: ${JSON.stringify(resized)}`); + + const afterGlobalWidthPreference = await page.evaluate(() => { + const setMindmapWidthMax = (value) => { + document.documentElement.style.setProperty("--mnote-mindmap-block-max-width", value); + document.querySelector('.document-shell')?.style.setProperty("--mnote-mindmap-block-max-width", value); + document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]')?.style.setProperty("--mnote-mindmap-block-max-width", value); + window.dispatchEvent(new CustomEvent("mnote:page-width-preference-changed", { detail: { type: "mindmap" } })); + }; + setMindmapWidthMax("720px"); + return true; + }); + assert.equal(afterGlobalWidthPreference, true); + await page.waitForFunction(() => { + const placeholder = document.querySelector('[data-testid="mnote-mindmap-placeholder"]'); + const rect = placeholder?.getBoundingClientRect(); + return placeholder instanceof HTMLElement + && !placeholder.dataset.mnoteMindmapWidth + && rect + && Math.round(rect.width) >= 900 + && Math.round(rect.width) <= 920; + }, null, { timeout: UI_TIMEOUT_MS }); + + const globalWidthState = await page.evaluate(() => { + const placeholder = document.querySelector('[data-testid="mnote-mindmap-placeholder"]'); + const editor = document.querySelector('.document-pane[data-pane-role="primary"] .editor-surface .ProseMirror'); + const rect = placeholder?.getBoundingClientRect(); + const editorRect = editor?.getBoundingClientRect(); + return { + widthAttr: placeholder instanceof HTMLElement ? placeholder.dataset.mnoteMindmapWidth || "" : "", + heightAttr: placeholder instanceof HTMLElement ? placeholder.dataset.mnoteMindmapHeight || "" : "", + rect: rect ? { width: Math.round(rect.width), height: Math.round(rect.height) } : null, + centerDelta: rect && editorRect ? Math.round((rect.left + rect.width / 2) - (editorRect.left + editorRect.width / 2)) : null, + cssMaxWidth: getComputedStyle(document.documentElement).getPropertyValue("--mnote-mindmap-block-max-width").trim(), + }; + }); + assert.equal(globalWidthState.widthAttr, "", `全局 Mindmap 宽度设置后应清除本块手动宽度: ${JSON.stringify(globalWidthState)}`); + assert(Math.abs(globalWidthState.centerDelta) <= 2, `全局 Mindmap 宽度设置后仍应居中: ${JSON.stringify(globalWidthState)}`); + return { ...resized, globalWidthState }; +} + async function readState(page) { return page.evaluate(() => { const editorRoot = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]'); @@ -184,7 +338,9 @@ async function waitForMindmapId(page) { await page.waitForFunction( () => { const root = document.querySelector('[data-testid="mnote-mindmap-editor-root"]'); - return root instanceof HTMLElement && /^思维导图\d{6}\.json$/.test(root.dataset.mnoteMindmapId || ""); + const mindmapId = root instanceof HTMLElement ? String(root.dataset.mnoteMindmapId || "") : ""; + return /^思维导图\d{6}\.json$/.test(mindmapId) + || /^mindmap[-_][^/\\]+(?:\.json)?$/.test(mindmapId); }, null, { timeout: UI_TIMEOUT_MS }, @@ -193,6 +349,11 @@ async function waitForMindmapId(page) { return state.mindmapId; } +function localMindmapFileName(mindmapId) { + const value = String(mindmapId || "").trim(); + return value.toLowerCase().endsWith(".json") ? value : `${value}.json`; +} + function rowMatchesMindmap(row, mindmapId) { const assetId = String(row && row.assetId || ""); const title = String(row && row.title || ""); @@ -312,6 +473,7 @@ async function main() { markdownMissingMindmapReferenceAfterInsert: false, refreshSkippedBecauseMarkdownNotSaved: false, commandResponseSummary: null, + resizeAfterInsert: null, }; try { @@ -319,13 +481,16 @@ async function main() { result.screenshots.push(await screenshot(page, "01-open-clean-page")); await insertMindmapThroughSlash(page); result.mindmapId = await waitForMindmapId(page); + const mindmapFileName = localMindmapFileName(result.mindmapId); result.screenshots.push(await screenshot(page, "02-after-insert-mindmap")); + result.defaultStyleDrawer = await assertMindmapStyleDrawerClosedByDefault(page); + result.resizeAfterInsert = await resizeMindmapThroughCornerHandle(page); await page.waitForFunction( ({ expected }) => { const tree = document.getElementById("sidebar-file-tree-root"); return (tree?.textContent || "").includes(expected); }, - { expected: result.mindmapId }, + { expected: mindmapFileName }, { timeout: UI_TIMEOUT_MS }, ); result.saveAfterInsert = await waitForStableEditorSave(page, networkRecords, "after-insert"); @@ -333,12 +498,12 @@ async function main() { result.diskAfterInsert = fs.readdirSync(pageDir).sort(); assert(result.diskAfterInsert.includes("CleanPage.md"), `页面 Markdown 应存在: ${result.diskAfterInsert.join(",")}`); - assert(result.diskAfterInsert.includes(result.mindmapId), `mindmap 应直接出现在页面文件夹下: ${result.diskAfterInsert.join(",")}`); - assert(!fs.existsSync(path.join(root, result.mindmapId)), "root 同级不应残留 mindmap 文件"); - assert(!fs.existsSync(path.join(pageDir, "assets", result.mindmapId)), "assets 下不应残留 mindmap 文件"); + assert(result.diskAfterInsert.includes(mindmapFileName), `mindmap 应直接出现在页面文件夹下: ${result.diskAfterInsert.join(",")}`); + assert(!fs.existsSync(path.join(root, mindmapFileName)), "root 同级不应残留 mindmap 文件"); + assert(!fs.existsSync(path.join(pageDir, "assets", mindmapFileName)), "assets 下不应残留 mindmap 文件"); result.markdown = fs.readFileSync(markdownPath, "utf8"); - result.markdownMissingMindmapReferenceAfterInsert = !result.markdown.includes(`](${result.mindmapId})`); + result.markdownMissingMindmapReferenceAfterInsert = !result.markdown.includes(`](${mindmapFileName})`); result.samples = await sampleFileTree(page, result.mindmapId, 4_000); result.commandResponseSummary = await postMindmapCommand(page, documentId, result.mindmapId); @@ -355,7 +520,7 @@ async function main() { timeout: UI_TIMEOUT_MS, }); const afterRefresh = await readState(page); - assert.equal(afterRefresh.mindmapId, result.mindmapId, `刷新后 mindmapId 应保持不变: ${JSON.stringify(afterRefresh)}`); + assert.equal(localMindmapFileName(afterRefresh.mindmapId), mindmapFileName, `刷新后 mindmapId 应保持不变: ${JSON.stringify(afterRefresh)}`); assert( afterRefresh.mindmapRows.filter((row) => rowMatchesMindmap(row, result.mindmapId)).length === 1, `刷新后文件树应只有本轮 mindmap 一行: ${JSON.stringify(afterRefresh.mindmapRows)}`, diff --git a/scripts/task463-onlyoffice-resolver-smoke.js b/scripts/task463-onlyoffice-resolver-smoke.js index 95fbe053..ca18d2bc 100644 --- a/scripts/task463-onlyoffice-resolver-smoke.js +++ b/scripts/task463-onlyoffice-resolver-smoke.js @@ -50,6 +50,7 @@ async function uploadLocalAsset(page, root, documentId, fileName, mimeType, byte const form = new FormData(); form.append("rootUri", rootUri); form.append("documentId", documentId); + form.append("uploadIntent", "editor.markdown.attach"); form.append("kind", kind); form.append("file", new File([new Uint8Array(bytes)], fileName, { type: mimeType })); const response = await fetch("/api/local-folder/assets/upload", { method: "POST", body: form }); @@ -137,8 +138,8 @@ async function main() { timeout: UI_TIMEOUT_MS, }); - // ======== Test 1: docx opens in main editor tab with /onlyoffice iframe ======== - console.log("Test 1: docx opens in main editor tab with /onlyoffice iframe"); + // ======== Test 1: docx opens in main editor tab with Office reader iframe ======== + console.log("Test 1: docx opens in main editor tab with Office reader iframe"); await page.evaluate(({ asset, documentId }) => { window.dispatchEvent(new CustomEvent("tree.asset.open", { detail: { @@ -171,16 +172,20 @@ async function main() { // docx should have badge kind "word" assert.equal(officeTabInfo.badgeKind, "word", `docx should have badgeKind 'word', got '${officeTabInfo.badgeKind}'`); assert.ok(officeTabInfo.iframeExists, "Office tab should have an iframe"); - assert.ok(officeTabInfo.iframeSrc.includes("/onlyoffice"), `iframe src should include /onlyoffice: ${officeTabInfo.iframeSrc}`); // Parse iframe URL and verify required params const iframeUrl = new URL(officeTabInfo.iframeSrc, BASE_URL); - assert.equal(iframeUrl.pathname, "/onlyoffice", `iframe pathname should be /onlyoffice: ${iframeUrl.pathname}`); + assert.ok( + iframeUrl.pathname === "/office-preview" || iframeUrl.pathname === "/onlyoffice", + `iframe pathname should be /office-preview or /onlyoffice: ${iframeUrl.pathname}`, + ); // Check that assetId is in the URL (either in query params or encoded) const iframeAssetId = iframeUrl.searchParams.get("assetId") || ""; assert.ok(iframeAssetId, "iframe URL should carry assetId param"); - assert.ok(iframeUrl.searchParams.has("mode"), "iframe URL should carry mode param"); + if (iframeUrl.pathname === "/onlyoffice") { + assert.equal(iframeUrl.searchParams.get("mode"), "view", "default /onlyoffice open should use view mode"); + } assert.ok(iframeUrl.searchParams.has("fileUrl"), "iframe URL should carry fileUrl param"); // The fileUrl for local-folder should point to /api/local-folder/files/open @@ -304,7 +309,7 @@ async function main() { await page.waitForTimeout(300); // ======== Test 4: new-window defaults to view mode ======== - console.log("Test 4: new-window opens /onlyoffice with view mode"); + console.log("Test 4: new-window opens Office reader in view mode"); const [officePopup] = await Promise.all([ page.waitForEvent("popup", { timeout: UI_TIMEOUT_MS }).catch(() => null), page.evaluate(({ asset, documentId }) => { @@ -324,11 +329,16 @@ async function main() { await officePopup.waitForLoadState("domcontentloaded", { timeout: UI_TIMEOUT_MS }).catch(() => undefined); const popupUrl = new URL(officePopup.url()); console.log(` New-window URL: ${popupUrl.toString()}`); - assert.equal(popupUrl.pathname, "/onlyoffice", `new-window pathname should be /onlyoffice: ${popupUrl.pathname}`); + assert.ok( + popupUrl.pathname === "/office-preview" || popupUrl.pathname === "/onlyoffice", + `new-window pathname should be /office-preview or /onlyoffice: ${popupUrl.pathname}`, + ); // new-window 是浏览器目标,不代表编辑权限;默认仍应只读 const popupMode = popupUrl.searchParams.get("mode") || ""; - assert.equal(popupMode, "view", `new-window mode should be 'view', got '${popupMode}'`); + if (popupUrl.pathname === "/onlyoffice") { + assert.equal(popupMode, "view", `new-window mode should be 'view', got '${popupMode}'`); + } // Check assetId is present const popupAssetId = popupUrl.searchParams.get("assetId") || ""; @@ -387,21 +397,17 @@ async function main() { onlyofficeUrl.searchParams.set("assetId", "test-asset-id"); onlyofficeUrl.searchParams.set("mode", "edit"); - const response = await page.goto(onlyofficeUrl.toString(), { - waitUntil: "domcontentloaded", - timeout: UI_TIMEOUT_MS, - }); - - // Verify the page rendered - const pageContent = await page.evaluate(() => { - return { - title: document.title, - hasFrameContainer: !!document.getElementById("onlyoffice-frame"), - hasErrorContainer: !!document.getElementById("onlyoffice-error"), - hasScript: typeof window.__MNOTE_ONLYOFFICE_READY__ !== "undefined", - initialConfig: typeof window.initial !== "undefined", - }; - }); + const response = await context.request.get(onlyofficeUrl.toString(), { timeout: UI_TIMEOUT_MS }); + assert.equal(response.status(), 200, `/onlyoffice should respond 200, got ${response.status()}`); + const onlyofficeHtml = await response.text(); + + // Verify the page rendered without executing DocEditor boot against a fake file path. + const pageContent = { + hasFrameContainer: onlyofficeHtml.includes('id="onlyoffice-frame"'), + hasErrorContainer: onlyofficeHtml.includes('id="onlyoffice-error"'), + hasScript: onlyofficeHtml.includes("window.__MNOTE_ONLYOFFICE_READY__"), + initialConfig: onlyofficeHtml.includes("const initial ="), + }; console.log(` /onlyoffice page: ${JSON.stringify(pageContent)}`); assert.ok(pageContent.hasFrameContainer, "/onlyoffice should have #onlyoffice-frame container"); @@ -409,17 +415,11 @@ async function main() { assert.ok(pageContent.hasScript, "/onlyoffice should have runtime script"); // Verify the config has correct document URL and callback URL source - const scriptContent = await page.evaluate(() => { - const scripts = document.querySelectorAll("script"); - const relevant = Array.from(scripts).filter(s => s.textContent && s.textContent.includes("buildCallbackUrl")); - const text = relevant.length > 0 ? relevant[0].textContent : ""; - return { - hasCallbackUrlBuilder: text.includes("buildCallbackUrl"), - hasResolveDocumentUrl: text.includes("resolveDocumentUrl"), - hasProxyLogic: text.includes("/api/onlyoffice/proxy"), - snippet: text.substring(0, 200), - }; - }); + const scriptContent = { + hasCallbackUrlBuilder: onlyofficeHtml.includes("buildCallbackUrl"), + hasResolveDocumentUrl: onlyofficeHtml.includes("resolveDocumentUrl"), + hasProxyLogic: onlyofficeHtml.includes("/api/onlyoffice/proxy"), + }; console.log(` Script: hasCallbackUrl=${scriptContent.hasCallbackUrlBuilder}, hasResolveDocumentUrl=${scriptContent.hasResolveDocumentUrl}`); assert.ok(scriptContent.hasCallbackUrlBuilder, "OnlyOffice page should define buildCallbackUrl"); diff --git a/scripts/task490-runtime-surfaces-smoke.js b/scripts/task490-runtime-surfaces-smoke.js index 1f6122ae..bf596e2b 100644 --- a/scripts/task490-runtime-surfaces-smoke.js +++ b/scripts/task490-runtime-surfaces-smoke.js @@ -137,6 +137,10 @@ async function main() { const screenshots = {}; const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task490-")); const relativePath = "RuntimeSurfaces.md"; + const capturedPageAi = { + runs: [], + aborts: [], + }; writeWorkspaceManifest(root, "mnote-e2e"); fs.writeFileSync( path.join(root, relativePath), @@ -146,6 +150,106 @@ async function main() { let caughtError = null; try { + await page.route("**/api/hermes/client/gateway/health**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + gateway: { ok: true, status: "mocked" }, + profile: { name: "reasonix", modelConfigured: true, apiKeyConfigured: true }, + suggestions: [], + }), + }); + }); + await page.route("**/api/hermes/client/tools**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, tools: [] }), + }); + }); + await page.route("**/api/hermes/client/profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + active: "mnoteai", + profiles: [{ name: "mnoteai", label: "MNote AI", modelConfigured: true, apiKeyConfigured: true }], + }), + }); + }); + await page.route("**/api/ai/agent-profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + agentId: "reasonix", + profiles: [], + }), + }); + }); + await page.route("**/api/hermes/client/skills**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, categories: [], archived: [] }), + }); + }); + await page.route("**/api/hermes/client/sessions", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + sessionId: "mnote_task490", + title: "task490", + traceId: "trace_task490", + persistence: "local_ai_session_jsonl", + sessionStorage: "local_private", + }), + }); + }); + await page.route("**/api/hermes/client/runs", async (route) => { + capturedPageAi.runs.push(route.request().postData() || ""); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + sessionId: "mnote_task490", + runId: "run_task490_stop", + upstream: { run_id: "run_task490_stop", trace_id: "trace_task490_stop" }, + traceId: "trace_task490_stop", + }), + }); + }); + await page.route("**/api/hermes/client/events/run_task490_stop", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "text/event-stream; charset=utf-8" }, + body: `data: ${JSON.stringify({ event: "message.delta", run_id: "run_task490_stop", delta: "Task490 streaming" })}\n\n`, + }); + }); + await page.route("**/api/hermes/client/runs/run_task490_stop/abort", async (route) => { + capturedPageAi.aborts.push(route.request().postData() || ""); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + status: "aborted", + runtime: { runId: "run_task490_stop", status: "aborted", queueLength: 0 }, + events: [ + { event: "abort.started", runId: "run_task490_stop" }, + { event: "abort.completed", runId: "run_task490_stop" }, + ], + }), + }); + }); + await ensureAuthenticated(page, context.request); const url = documentUrl(BASE_URL, root, relativePath); @@ -153,7 +257,6 @@ async function main() { assert(response, "文档页没有返回响应"); assert(response.status() === 200, `文档页状态码异常: ${response.status()}`); const editor = await waitForRuntimeIsland(page, UI_TIMEOUT_MS); - await setBlockHandleFixture(page, UI_TIMEOUT_MS); const moreButton = page.locator('[data-testid="wolai-page-settings-trigger"]').first(); await moreButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); @@ -175,7 +278,58 @@ async function main() { assert(await page.locator("[data-page-ai-input]").first().isVisible(), "Page AI drawer 必须显示输入框"); screenshots.pageAi = await saveScreenshot(page, "02-page-ai"); await closePageAiIfOpen(page); + await page.waitForFunction( + () => { + const drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]'); + const trigger = document.querySelector('[data-testid="wolai-floating-ai"]'); + return drawer instanceof HTMLElement + && drawer.hidden === true + && trigger instanceof HTMLElement + && trigger.getAttribute("data-state") === "closed" + && trigger.getAttribute("aria-expanded") === "false"; + }, + null, + { timeout: UI_TIMEOUT_MS }, + ); + await aiButton.click({ timeout: UI_TIMEOUT_MS }); + await aiDrawer.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator("[data-page-ai-input]").fill("Task490 stop smoke", { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => document.documentElement.getAttribute("data-mnote-page-ai-run-status") === "running" + && document.documentElement.getAttribute("data-mnote-page-ai-run-id") === "run_task490_stop", + null, + { timeout: UI_TIMEOUT_MS }, + ); + const stopButton = page.locator('[data-page-ai-action="stop-run"]').first(); + await stopButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => { + const stop = document.querySelector('[data-page-ai-action="stop-run"]'); + return stop instanceof HTMLButtonElement && stop.disabled === false && stop.getAttribute("aria-disabled") === "false"; + }, + null, + { timeout: UI_TIMEOUT_MS }, + ); + await stopButton.click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => document.documentElement.getAttribute("data-mnote-page-ai-run-status") === "aborted", + null, + { timeout: UI_TIMEOUT_MS }, + ); + await page.locator('[data-page-ai-conversation]').getByText("已请求停止当前 AI run。").waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + assert(capturedPageAi.runs.length === 1, "Page AI stop smoke 应先发起一个 run"); + assert(capturedPageAi.aborts.length === 1, "点击停止应调用 abort API 一次"); + const abortBody = JSON.parse(capturedPageAi.aborts[0] || "{}"); + assert(abortBody.reason === "page_ai_user_stop", "abort API 应记录用户停止原因"); + screenshots.pageAiStopped = await saveScreenshot(page, "02b-page-ai-stopped"); + await closePageAiIfOpen(page); + + await setBlockHandleFixture(page, UI_TIMEOUT_MS); await editor.click({ timeout: UI_TIMEOUT_MS }); await page.keyboard.type("/"); const slashMenu = page.locator('[data-testid="mnote-leptos-tiptap-slash-menu"]').first(); diff --git a/scripts/task502-page-ai-agent-selector-context-smoke.js b/scripts/task502-page-ai-agent-selector-context-smoke.js index 56c67f4c..31c0ac35 100644 --- a/scripts/task502-page-ai-agent-selector-context-smoke.js +++ b/scripts/task502-page-ai-agent-selector-context-smoke.js @@ -142,6 +142,23 @@ async function main() { body: JSON.stringify({ ok: true, tools: [] }), }); }); + await page.route("**/api/ai/agent-profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + agentId: "hermes", + profiles: [ + { profileId: "shared_deepseek_chat", kind: "shared", displayName: "DeepSeek Chat", baseProfile: "deepseek-chat", isolatedProfile: "openclaw-deepseek-chat", canRun: true, canManageSkills: false, canManageConfig: false, readonly: true }, + { profileId: "shared_doubao_chat", kind: "shared", displayName: "豆包 Chat", baseProfile: "doubao-chat", isolatedProfile: "openclaw-doubao-chat", canRun: true, canManageSkills: false, canManageConfig: false, readonly: true }, + { profileId: "shared_gemini_chat", kind: "shared", displayName: "Gemini Chat", baseProfile: "gemini-chat", isolatedProfile: "openclaw-gemini-chat", canRun: true, canManageSkills: false, canManageConfig: false, readonly: true }, + { profileId: "usr_task502_default", kind: "personal", displayName: "我的 Hermes", baseProfile: "default", isolatedProfile: "mnote-u-task502-default", canRun: true, canManageSkills: true, canManageConfig: true }, + { profileId: "shared_lite", kind: "shared", displayName: "Lite", baseProfile: "lite", isolatedProfile: "lite", canRun: true, canManageSkills: false, canManageConfig: false, readonly: true }, + ], + }), + }); + }); await page.route("**/api/hermes/client/profiles", async (route) => { await route.fulfill({ status: 200, @@ -184,12 +201,18 @@ async function main() { return; } const runtime = url.searchParams.get("runtime"); - const profile = url.searchParams.get("profile") || "mnoteai"; + const profile = url.searchParams.get("profileId") || url.searchParams.get("profile") || "usr_task502_default"; const body = runtime === "mnote" ? { ok: true, runtime: "mnote", - categories: [{ name: "mnote", skills: [{ id: "mnote-current-page", name: "mnote-current-page", title: "当前页读取", description: "读取当前页", enabled: true, source: "mnote", origin: "builtin" }] }], + categories: [{ + name: "mnote", + skills: [ + { id: "mnote-current-page", name: "mnote-current-page", title: "当前页读取", description: "读取当前页", enabled: true, source: "mnote", origin: "builtin", builtin: true, configurable: true, configScope: "user_sqlite", skillKind: "mnote_builtin" }, + { id: "mnote-mindmap", name: "mnote-mindmap", title: "思维导图读写", description: "读取、编辑或从 outline 生成思维导图", enabled: true, source: "mnote", origin: "builtin", builtin: true, configurable: true, configScope: "user_sqlite", skillKind: "mnote_builtin", toolNames: ["mnote.mindmap.fetch", "mnote.mindmap.create_from_outline"] }, + ], + }], archived: [], } : runtime === "reasonix" @@ -205,8 +228,8 @@ async function main() { categories: [{ name: "writing", skills: [ - { id: "hermes-builtin", name: "hermes-builtin", title: "Hermes builtin", description: `Builtin ${profile}`, enabled: true, source: "builtin", origin: "builtin" }, - { id: profile === "chemist" ? "hermes-chemist" : "hermes-writer", name: profile === "chemist" ? "hermes-chemist" : "hermes-writer", title: profile === "chemist" ? "Hermes chemist" : "Hermes writer", description: `Hermes ${profile}`, enabled: profile !== "chemist", source: "local", origin: "installed" }, + { id: "hermes-builtin", name: "hermes-builtin", title: "Hermes builtin", description: `Builtin ${profile}`, enabled: true, source: "builtin", origin: "builtin", skillKind: "hermes_profile", profileId: profile, configurable: profile !== "shared_lite" }, + { id: profile === "shared_lite" ? "hermes-lite" : "hermes-writer", name: profile === "shared_lite" ? "hermes-lite" : "hermes-writer", title: profile === "shared_lite" ? "Hermes lite" : "Hermes writer", description: `Hermes ${profile}`, enabled: profile !== "shared_lite", source: "local", origin: "installed", skillKind: "hermes_profile", profileId: profile, configurable: profile !== "shared_lite", readonly: profile === "shared_lite" }, ], }], archived: [], @@ -320,24 +343,59 @@ async function main() { await agentButton.click({ timeout: UI_TIMEOUT_MS }); const agentPopover = page.locator("[data-page-ai-agent-popover]"); await agentPopover.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); - const agentIds = await page.$$eval("[data-page-ai-agent-id]", (nodes) => - nodes.map((node) => node.getAttribute("data-page-ai-agent-id")).filter(Boolean), + await agentPopover.locator('[data-page-ai-agent-section="chat_only"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await agentPopover.locator('[data-page-ai-agent-section="hermes"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + assert.strictEqual( + await page.locator('[data-page-ai-agent-section="chat_only"] [data-page-ai-profile-id="shared_deepseek_chat"]').count(), + 1, + "ChatOnly 二级菜单应包含 DeepSeek", ); - assert.deepStrictEqual(agentIds.sort(), ["chat_only", "hermes", "reasonix"]); - await page.locator('[data-page-ai-agent-id="hermes"]').click({ timeout: UI_TIMEOUT_MS }); - await page.locator('[data-page-ai-agent-popover] [data-page-ai-profile-select]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); - await page.locator('[data-page-ai-agent-popover] [data-page-ai-profile-select]').selectOption("chemist", { timeout: UI_TIMEOUT_MS }); + assert.strictEqual( + await page.locator('[data-page-ai-agent-section="chat_only"] [data-page-ai-profile-id="shared_doubao_chat"]').count(), + 1, + "ChatOnly 二级菜单应包含豆包", + ); + assert.strictEqual( + await page.locator('[data-page-ai-agent-section="chat_only"] [data-page-ai-profile-id="shared_gemini_chat"]').count(), + 1, + "ChatOnly 二级菜单应包含 Gemini", + ); + assert.strictEqual( + await page.locator('[data-page-ai-agent-section="hermes"] [data-page-ai-profile-id="usr_task502_default"]').count(), + 1, + "Hermes 二级菜单应包含个人 profile", + ); + assert.strictEqual( + await page.locator('[data-page-ai-agent-section="hermes"] [data-page-ai-profile-id="shared_lite"]').count(), + 1, + "Hermes 二级菜单应包含 shared_lite profile", + ); + const agentChip = page.locator("[data-page-ai-agent-chip]"); + await page.locator('[data-page-ai-agent-section="chat_only"] [data-page-ai-profile-id="shared_gemini_chat"]').click({ timeout: UI_TIMEOUT_MS }); await page.waitForFunction( - () => document.documentElement.getAttribute("data-mnote-page-ai-profile") === "chemist", + () => document.documentElement.getAttribute("data-mnote-page-ai-agent-id") === "chat_only" + && document.documentElement.getAttribute("data-mnote-page-ai-profile") === "shared_gemini_chat", null, { timeout: UI_TIMEOUT_MS }, ); - await page.locator('[data-page-ai-agent-id="reasonix"]').click({ timeout: UI_TIMEOUT_MS }); + assert((await agentChip.innerText({ timeout: UI_TIMEOUT_MS })).includes("ChatOnly / Gemini"), "上下文按钮右侧标签应显示当前 ChatOnly agent"); + await agentButton.click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-agent-section="hermes"] [data-page-ai-profile-id="shared_lite"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => document.documentElement.getAttribute("data-mnote-page-ai-agent-id") === "hermes" + && document.documentElement.getAttribute("data-mnote-page-ai-profile") === "shared_lite", + null, + { timeout: UI_TIMEOUT_MS }, + ); + assert((await agentChip.innerText({ timeout: UI_TIMEOUT_MS })).includes("Hermes / Lite"), "上下文按钮右侧标签应显示当前 Hermes profile"); + await agentButton.click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-agent-section="reasonix"] [data-page-ai-agent-id="reasonix"]').click({ timeout: UI_TIMEOUT_MS }); await page.waitForFunction( () => document.documentElement.getAttribute("data-mnote-page-ai-agent-id") === "reasonix", null, { timeout: UI_TIMEOUT_MS }, ); + assert((await agentChip.innerText({ timeout: UI_TIMEOUT_MS })).includes("Reasonix"), "上下文按钮右侧标签应显示当前 Reasonix agent"); const contextRefs = page.locator("[data-page-ai-context-refs]"); assert.strictEqual( @@ -351,6 +409,116 @@ async function main() { await contextButton.evaluate((node) => Boolean(node.closest(".wolai-page-ai-composer-bar"))), "上下文按钮应显示在输入区下方工具栏中", ); + const targetButton = page.locator("[data-page-ai-target-button]"); + await targetButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + assert( + await targetButton.evaluate((node) => Boolean(node.closest(".wolai-page-ai-composer-bar"))), + "目标按钮应显示在输入区下方工具栏中", + ); + const targetChip = page.locator("[data-page-ai-target-chip]"); + await targetChip.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const targetChipText = (await targetChip.innerText({ timeout: UI_TIMEOUT_MS })).trim(); + assert(targetChipText.includes("AgentContext") || targetChipText.includes("当前页"), `目标 chip 应展示当前写入目标: ${targetChipText}`); + await targetButton.click({ timeout: UI_TIMEOUT_MS }); + const targetPopover = page.locator("[data-page-ai-target-popover]"); + await targetPopover.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + assert( + await targetPopover.locator("[data-page-ai-target-option]").first().isVisible(), + "目标 popover 应提供至少一个可选目标", + ); + await targetPopover.locator('[data-page-ai-action="close-target-popover"]').click({ timeout: UI_TIMEOUT_MS }); + await page.evaluate(({ documentId, rootUri, workspaceId }) => { + const runtime = window.__mnoteDocumentPaneRuntime; + if (!runtime || typeof runtime.getOpenEditorsSnapshot !== "function") { + throw new Error("缺少 open editors snapshot runtime"); + } + const original = runtime.getOpenEditorsSnapshot.bind(runtime); + runtime.getOpenEditorsSnapshot = () => { + const snapshot = original(); + const mindmapResource = { + objectIdentity: "resource:mindmap:task502", + workspacePath: { + schema: "mnote.workspace_path.v1", + workspaceId, + sourceKind: "local_folder", + rootUri, + relativePath: "maps/Task502.mindmap.json", + documentId, + objectIdentity: "resource:mindmap:task502", + assetId: "task502-mindmap", + resourceKind: "mindmap", + }, + paneRole: "primary", + documentId, + workspaceId, + title: "Task502 Mindmap", + kind: "mindmap", + editorKind: "mindmap", + active: false, + dirtyState: "", + preview: false, + pinned: true, + lastActiveAt: Date.now(), + assetId: "task502-mindmap", + path: "maps/Task502.mindmap.json", + }; + const officeResource = { + objectIdentity: "resource:office:task502", + workspacePath: { + schema: "mnote.workspace_path.v1", + workspaceId, + sourceKind: "local_folder", + rootUri, + relativePath: "office/Task502 Deck.pptx", + documentId, + objectIdentity: "resource:office:task502", + assetId: "task502-office", + resourceKind: "office", + }, + paneRole: "primary", + documentId, + workspaceId, + title: "Task502 Deck", + kind: "office", + editorKind: "office", + active: false, + dirtyState: "", + preview: false, + pinned: true, + lastActiveAt: Date.now(), + assetId: "task502-office", + path: "office/Task502 Deck.pptx", + onlyofficeSessionId: "mnote-oo-task502-office", + bridgeSessionId: "mnote-oo-task502-office", + bridgeSessionReady: true, + }; + const resources = [mindmapResource, officeResource]; + const withoutResource = (items) => (Array.isArray(items) ? items : []) + .filter((item) => !resources.some((resource) => item?.objectIdentity === resource.objectIdentity)); + const groups = snapshot.groups || {}; + const primary = groups.primary || {}; + return { + ...snapshot, + editors: [...withoutResource(snapshot.editors), ...resources], + resourceEditors: [...withoutResource(snapshot.resourceEditors), ...resources], + groups: { + ...groups, + primary: { + ...primary, + resourceEditors: [...withoutResource(primary.resourceEditors), ...resources], + }, + }, + }; + }; + }, { documentId, rootUri, workspaceId }); + await targetButton.click({ timeout: UI_TIMEOUT_MS }); + await targetPopover.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await targetPopover.locator('[data-page-ai-target-option="resource:office:task502"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => (document.querySelector("[data-page-ai-target-chip]")?.textContent || "").includes("Task502 Deck"), + null, + { timeout: UI_TIMEOUT_MS }, + ); assert.strictEqual( await page.locator('.wolai-page-ai-composer-bar [data-page-ai-action="history"]').count(), 0, @@ -366,39 +534,70 @@ async function main() { ); await page.locator('.wolai-page-ai-header-actions [data-page-ai-tab="skills"]').click({ timeout: UI_TIMEOUT_MS }); await page.locator('[data-page-ai-panel="skills"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const skillSourceSelect = page.locator('[data-page-ai-panel="skills"] [data-page-ai-skill-source-select]'); + await skillSourceSelect.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const skillSourceOptions = await skillSourceSelect.locator("option").evaluateAll((nodes) => + nodes.map((node) => ({ value: node.value, text: node.textContent || "" })), + ); + assert(skillSourceOptions.some((item) => item.value === "mnote" && item.text.includes("mnote")), "技能来源应包含 mnote"); + assert(skillSourceOptions.some((item) => item.value === "reasonix" && item.text.includes("reasonix")), "技能来源应包含 reasonix"); + assert(skillSourceOptions.some((item) => item.value === "hermes:usr_task502_default" && item.text.includes("Hermes_user")), "技能来源应包含个人 Hermes profile"); + assert(skillSourceOptions.some((item) => item.value === "hermes:shared_lite" && item.text.includes("hermes_lite")), "技能来源应包含 shared lite profile"); + await skillSourceSelect.selectOption("mnote", { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-skill-group="mnote"] [data-page-ai-skill-toggle="mnote-current-page"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-skill-group="mnote"] [data-page-ai-skill-toggle="mnote-mindmap"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const skillPanelText = await page.locator('[data-page-ai-panel="skills"]').innerText({ timeout: UI_TIMEOUT_MS }); - assert(skillPanelText.includes("MNote 内置技能"), "技能面板应展示 MNote 内置技能分组"); - assert(skillPanelText.includes("Reasonix 技能"), "技能面板应展示 Reasonix 技能分组"); - assert(skillPanelText.includes("Hermes 技能"), "技能面板应展示 Hermes 技能分组"); - assert(skillPanelText.includes("Hermes chemist"), "Hermes 技能应随 chemist profile 加载"); - assert(!skillPanelText.includes("Hermes writer"), "Hermes profile 切到 chemist 后不应继续显示上一 profile 技能"); + assert(skillPanelText.includes("mnote"), "技能面板应展示 mnote 来源"); + assert(!skillPanelText.includes("Reasonix 技能"), "选择 mnote 时不应同时展示 Reasonix 技能"); + assert(!skillPanelText.includes("Hermes 技能"), "选择 mnote 时不应同时展示 Hermes 技能"); await page.locator('[data-page-ai-skill-group-toggle="mnote"]').click({ timeout: UI_TIMEOUT_MS }); assert.strictEqual( await page.locator('[data-page-ai-skill-group="mnote"] [data-page-ai-skill-toggle="mnote-current-page"]').count(), 0, "MNote 技能分组折叠后不应显示组内技能", ); + assert.strictEqual( + await page.locator('[data-page-ai-skill-group="mnote"] [data-page-ai-skill-toggle="mnote-mindmap"]').count(), + 0, + "MNote 技能分组折叠后不应显示 mindmap 技能", + ); await page.locator('[data-page-ai-skill-group-toggle="mnote"]').click({ timeout: UI_TIMEOUT_MS }); await page.locator('[data-page-ai-skill-group="mnote"] [data-page-ai-skill-toggle="mnote-current-page"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-skill-group="mnote"] [data-page-ai-skill-toggle="mnote-mindmap"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-skill-group="mnote"] [data-page-ai-skill-toggle="mnote-current-page"]').click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-skill-group="mnote"] [data-page-ai-skill-toggle="mnote-mindmap"]').click({ timeout: UI_TIMEOUT_MS }); + await skillSourceSelect.selectOption("reasonix", { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-skill-group="reasonix"] [data-page-ai-skill-toggle="reasonix-review"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const reasonixOnlyText = await page.locator('[data-page-ai-panel="skills"]').innerText({ timeout: UI_TIMEOUT_MS }); + assert(reasonixOnlyText.includes("reasonix"), "选择 reasonix 时应展示 Reasonix 来源"); + assert(!reasonixOnlyText.includes("当前页读取"), "选择 reasonix 时不应残留 MNote 技能"); + assert(!reasonixOnlyText.includes("Hermes writer"), "选择 reasonix 时不应残留 Hermes 技能"); + await page.locator('[data-page-ai-skill-group="reasonix"] [data-page-ai-skill-toggle="reasonix-review"]').click({ timeout: UI_TIMEOUT_MS }); + await skillSourceSelect.selectOption("hermes:shared_lite", { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-skill-group="hermes"] [data-page-ai-skill-toggle="hermes-lite"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const sharedHermesText = await page.locator('[data-page-ai-panel="skills"]').innerText({ timeout: UI_TIMEOUT_MS }); + assert(sharedHermesText.includes("hermes_lite"), "选择 hermes_lite 时应只展示 lite profile 技能"); + assert(!sharedHermesText.includes("当前页读取"), "选择 hermes_lite 时不应残留 MNote 技能"); + assert(!sharedHermesText.includes("reasonix-review"), "选择 hermes_lite 时不应残留 Reasonix 技能"); + assert( + await page.locator('[data-page-ai-skill-group="hermes"] [data-page-ai-skill-toggle="hermes-lite"]').isDisabled(), + "shared Hermes profile 普通用户应只读", + ); await page.locator('[data-page-ai-hide-hermes-builtin]').check({ timeout: UI_TIMEOUT_MS }); const hiddenBuiltinText = await page.locator('[data-page-ai-panel="skills"]').innerText({ timeout: UI_TIMEOUT_MS }); assert(!hiddenBuiltinText.includes("Hermes builtin"), "隐藏 Hermes 内置后不应显示 Hermes 内置技能"); await page.locator('[data-page-ai-hide-hermes-builtin]').uncheck({ timeout: UI_TIMEOUT_MS }); - await page.locator('[data-page-ai-panel="skills"] [data-page-ai-profile-select]').selectOption("mnoteai", { timeout: UI_TIMEOUT_MS }); + await skillSourceSelect.selectOption("hermes:usr_task502_default", { timeout: UI_TIMEOUT_MS }); await page.locator('[data-page-ai-skill-group="hermes"] [data-page-ai-skill-toggle="hermes-writer"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); assert.strictEqual( - await page.locator('[data-page-ai-skill-group="hermes"] [data-page-ai-skill-toggle="hermes-chemist"]').count(), + await page.locator('[data-page-ai-skill-group="hermes"] [data-page-ai-skill-toggle="hermes-lite"]').count(), 0, "Hermes profile 切回 mnoteai 后不应残留 chemist 技能", ); - await page.locator('[data-page-ai-panel="skills"] [data-page-ai-profile-select]').selectOption("chemist", { timeout: UI_TIMEOUT_MS }); - await page.locator('[data-page-ai-skill-group="hermes"] [data-page-ai-skill-toggle="hermes-chemist"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const skillsScreenshot = await saveScreenshot(page, "00-skills-panel"); - await page.locator('[data-page-ai-skill-group="mnote"] [data-page-ai-skill-toggle="mnote-current-page"]').click({ timeout: UI_TIMEOUT_MS }); - await page.locator('[data-page-ai-skill-group="reasonix"] [data-page-ai-skill-toggle="reasonix-review"]').click({ timeout: UI_TIMEOUT_MS }); - await page.locator('[data-page-ai-skill-group="hermes"] [data-page-ai-skill-toggle="hermes-chemist"]').click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-skill-group="hermes"] [data-page-ai-skill-toggle="hermes-writer"]').click({ timeout: UI_TIMEOUT_MS }); await page.waitForFunction( - () => document.documentElement.getAttribute("data-mnote-page-ai-skill-toggled") === "hermes-chemist", + () => document.documentElement.getAttribute("data-mnote-page-ai-skill-toggled") === "hermes-writer", null, { timeout: UI_TIMEOUT_MS }, ); @@ -454,6 +653,16 @@ async function main() { assert.strictEqual(ackRunBody.pageContext?.aiContext?.contextBlocks, undefined, "Page AI run 不应默认上传 contextBlocks"); assert.strictEqual(ackRunBody.pageContext?.evidence, undefined, "Page AI run 不应默认上传选区 evidence 正文"); assert.strictEqual(ackRunBody.selectedText, "", "Page AI run 不应默认上传 selectedText 正文"); + assert.strictEqual(ackRunBody.targetPackage?.schema, "mnote.agent_target_package.v1", "纯聊天 run 也应冻结目标包合同"); + assert(ackRunBody.targetPackage?.primaryTargetId, "targetPackage 应包含 primaryTargetId"); + assert(Array.isArray(ackRunBody.targetPackage?.targets), "targetPackage 应包含 targets 数组"); + assert.strictEqual(ackRunBody.targetPackage?.primaryTargetId, "resource:office:task502", "targetPackage 应冻结用户选择的 Office target"); + assert.strictEqual(ackRunBody.targetPackage?.onlyofficeSessionId, "mnote-oo-task502-office", "Office targetPackage 应携带 bridge session"); + assert( + ackRunBody.targetPackage.targets.some((target) => target.resourceKind === "only_office" && target.relativePath === "office/Task502 Deck.pptx" && target.onlyofficeSessionId === "mnote-oo-task502-office"), + `默认目标应标记 only_office resourceKind: ${JSON.stringify(ackRunBody.targetPackage)}`, + ); + assert.strictEqual(ackRunBody.targetPackage?.policy?.writeRequiresExplicitTarget, true, "targetPackage policy 应要求显式目标"); await page.locator('.wolai-page-ai-header-actions [data-page-ai-tab="agent"]').click({ timeout: UI_TIMEOUT_MS }); await page.locator('[data-page-ai-panel="agent"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); @@ -495,6 +704,14 @@ async function main() { `勾选变化后上下文按钮摘要应更新: ${updatedContextButtonLabel}`, ); await contextPopover.locator('[data-page-ai-action="close-context-popover"]').click({ timeout: UI_TIMEOUT_MS }); + await targetButton.click({ timeout: UI_TIMEOUT_MS }); + await targetPopover.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await targetPopover.locator('[data-page-ai-target-option="resource:mindmap:task502"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => (document.querySelector("[data-page-ai-target-chip]")?.textContent || "").includes("Task502 Mindmap"), + null, + { timeout: UI_TIMEOUT_MS }, + ); const runsBeforeDocumentTask = captured.filter((item) => item.kind === "run").length; await page.locator("[data-page-ai-input]").fill(`Task502 agent/context ${suffix}`, { timeout: UI_TIMEOUT_MS }); await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS }); @@ -520,20 +737,37 @@ async function main() { assert.strictEqual(runBody.pageContext?.aiContext?.contextBlocks, undefined, "Page AI run 不应默认上传 contextBlocks"); assert.strictEqual(runBody.pageContext?.evidence, undefined, "Page AI run 不应默认上传选区 evidence 正文"); assert.strictEqual(runBody.selectedText, "", "Page AI run 不应默认上传 selectedText 正文"); - assert(runBody.contextRefs.some((item) => item.kind === "active_editor" && item.documentId === documentId)); + const activeEditorRef = runBody.contextRefs.find((item) => item.kind === "active_editor" && item.documentId === documentId); + assert(activeEditorRef, "run payload 应包含 active_editor contextRef"); + assert.strictEqual(activeEditorRef.targetId, "resource:mindmap:task502", `active_editor contextRef 应冻结 mindmap targetId: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(activeEditorRef.objectIdentity, "resource:mindmap:task502", `active_editor contextRef 应冻结 mindmap objectIdentity: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(activeEditorRef.resourceKind, "mindmap", `active_editor contextRef 应标记 mindmap resourceKind: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(activeEditorRef.assetId, "task502-mindmap", `active_editor contextRef 应携带 mindmap assetId: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(activeEditorRef.relativePath, "maps/Task502.mindmap.json", `active_editor contextRef 应携带 mindmap relativePath: ${JSON.stringify(activeEditorRef)}`); assert(runBody.contextRefs.some((item) => item.kind === "folder" && item.rootUri === rootUri)); assert(runBody.contextRefs.some((item) => item.kind === "changed_files")); assert.strictEqual(runBody.skillPreferences?.mnote?.["mnote-current-page"], false, "MNote skill 开关应进入 run payload"); + assert.strictEqual(runBody.skillPreferences?.mnote?.["mnote-mindmap"], false, "MNote mindmap skill 开关应进入 run payload"); assert.strictEqual(runBody.skillPreferences?.reasonix?.["reasonix-review"], false, "Reasonix skill 开关应进入 run payload"); + assert.strictEqual(runBody.targetPackage?.schema, "mnote.agent_target_package.v1", "文档任务 run 应携带目标包"); + assert(runBody.targetPackage?.primaryTargetId, "文档任务 targetPackage 应包含 primaryTargetId"); + assert(Array.isArray(runBody.targetPackage?.targets), "文档任务 targetPackage 应包含 targets 数组"); + assert.strictEqual(runBody.targetPackage?.primaryTargetId, "resource:mindmap:task502", "文档任务 targetPackage 应冻结用户选择的 mindmap target"); + assert( + runBody.targetPackage.targets.some((target) => target.resourceKind === "mindmap" && target.relativePath === "maps/Task502.mindmap.json"), + `文档任务 targetPackage 应包含 mindmap 目标: ${JSON.stringify(runBody.targetPackage)}`, + ); + assert.strictEqual(runBody.targetPackage?.policy?.writeRequiresExplicitTarget, true, "文档任务 targetPackage policy 应要求显式目标"); const preferenceBodies = captured .filter((item) => item.kind === "ui-preferences" && item.method === "PUT") .map((item) => JSON.parse(item.body || "{}")); - assert(preferenceBodies.some((body) => body.updates?.["ai.agent.hermes.profile_id"] === "chemist"), "Hermes profile 选择应写入 SQLite UI preference"); - assert(preferenceBodies.some((body) => body.updates?.["ai.common.skills.mnote.enabled"]?.["mnote-current-page"] === false), "MNote skill 开关应写入 SQLite UI preference"); + assert(preferenceBodies.some((body) => body.updates?.["ai.agent.hermes.profile_id"] === "shared_lite"), "Agent 内的 Hermes profile 选择应写入 SQLite UI preference"); + assert(captured.some((item) => item.kind === "skill-toggle" && JSON.parse(item.body || "{}").skillKind === "mnote_builtin" && JSON.parse(item.body || "{}").name === "mnote-current-page"), "MNote skill 开关应调用服务端 per-user SQLite policy"); assert(preferenceBodies.some((body) => body.updates?.["ai.agent.reasonix.skills.enabled"]?.["reasonix-review"] === false), "Reasonix skill 开关应写入 SQLite UI preference"); assert(preferenceBodies.some((body) => body.updates?.["ai.common.skills.groups.collapsed"]?.mnote === true), "技能分组折叠状态应写入 SQLite UI preference"); - assert(preferenceBodies.some((body) => body.updates?.["ai.agent.hermes.profile.chemist.skills.hide_builtin"] === true), "Hermes 隐藏内置技能应按 profile 写入 SQLite UI preference"); - assert(captured.some((item) => item.kind === "skill-toggle" && JSON.parse(item.body || "{}").profile === "chemist"), "Hermes skill 开关应按 profile 调用"); + assert(preferenceBodies.some((body) => body.updates?.["ai.common.skills.active_source"] === "hermes:usr_task502_default"), "技能来源选择应写入 SQLite UI preference"); + assert(preferenceBodies.some((body) => body.updates?.["ai.agent.hermes.skills.hide_builtin"] === true), "Hermes 隐藏内置技能应写入用户 UI preference"); + assert(captured.some((item) => item.kind === "skill-toggle" && JSON.parse(item.body || "{}").profileId === "usr_task502_default"), "Hermes skill 开关应按 profileId 调用"); assert(Array.isArray(runBody.allowedRoots), "run payload 必须包含 allowedRoots 数组"); assert(runBody.allowedRoots.some((item) => item.rootUri === rootUri diff --git a/scripts/task503-mindmap-skill-capability-smoke.js b/scripts/task503-mindmap-skill-capability-smoke.js new file mode 100644 index 00000000..6207a565 --- /dev/null +++ b/scripts/task503-mindmap-skill-capability-smoke.js @@ -0,0 +1,319 @@ +#!/usr/bin/env node +"use strict"; + +const fs = require("node:fs/promises"); +const path = require("node:path"); +const { chromium } = require("playwright"); +const { UI_TIMEOUT_MS, assert } = require("./tree-shell-smoke-helpers"); + +const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); +const TASK = "task503-mindmap-skill-capability-smoke"; +const OUTPUT_DIR = path.join(process.cwd(), "tmp", TASK); +const RESULT_PATH = path.join(OUTPUT_DIR, "result.json"); +const TEST_EMAIL = "mnote.e2e@example.com"; +const TEST_PASSWORD = "MnoteE2E123!"; + +async function writeResult(payload) { + await fs.mkdir(OUTPUT_DIR, { recursive: true }); + await fs.writeFile(RESULT_PATH, `${JSON.stringify({ ...payload, resultPath: RESULT_PATH }, null, 2)}\n`, "utf8"); +} + +async function callJson(url, init = {}) { + const response = await fetch(url, { + ...init, + headers: { + ...(init.data !== undefined ? { "content-type": "application/json" } : {}), + ...(init.headers || {}), + }, + body: init.data !== undefined ? JSON.stringify(init.data) : init.body, + }); + const text = await response.text(); + let payload = null; + try { + payload = text ? JSON.parse(text) : null; + } catch { + payload = text; + } + if (!response.ok || payload?.ok === false) { + throw new Error(`${url} failed: ${response.status} ${typeof payload === "string" ? payload : JSON.stringify(payload)}`); + } + return payload; +} + +async function callMnoteTool({ toolName, workspaceId, documentId, rootUri, args, idempotencyKey }) { + const payload = { + toolName, + workspaceId, + documentId, + sourceKind: "local_folder", + rootUri, + actorId: "mnote-e2e", + sessionId: `sess_${TASK}_${toolName.replace(/\W+/g, "_")}`, + runId: `run_${Date.now()}`, + toolCallId: `call_${Date.now()}`, + traceId: `trace_${Date.now()}`, + idempotencyKey, + dryRun: false, + args, + }; + const result = await callJson(`${BASE_URL}/api/hermes/tools/mnote/call`, { + method: "POST", + headers: { + "content-type": "application/json", + "x-mnote-actor-id": "mnote-e2e", + "x-mnote-actor-type": "user", + }, + body: JSON.stringify(payload), + }); + return result.result ?? result; +} + +async function signInBrowserContext(context) { + const auth = await context.request.post(`${BASE_URL}/api/auth`, { + data: { + action: "auth:signIn", + args: { + provider: "password", + params: { + email: TEST_EMAIL, + password: TEST_PASSWORD, + flow: "signIn", + }, + }, + }, + }); + assert(auth.ok(), `测试账号登录失败: ${auth.status()} ${await auth.text()}`); + const whoami = await context.request.get(`${BASE_URL}/api/auth/whoami`); + assert(whoami.ok(), `whoami 失败: ${whoami.status()} ${await whoami.text()}`); + const viewer = await whoami.json(); + assert(viewer.userId === "mnote-e2e", `当前登录用户不是 mnote-e2e: ${JSON.stringify(viewer)}`); + assert(viewer.actorType === "user", `当前登录不是真实 user 会话: ${JSON.stringify(viewer)}`); + return viewer; +} + +function skillCapabilityOutline() { + return [ + { + text: "触发场景", + children: [ + { text: "用户提到脑图", children: [] }, + { text: "资源标签页", children: [] }, + { text: "PDF 转导图", children: [] }, + ], + }, + { + text: "读取能力", + children: [ + { text: "先解析目标", children: [] }, + { text: "fetch 回读树", children: [] }, + { text: "完整 envelope", children: [] }, + ], + }, + { + text: "创建能力", + children: [ + { text: "outline 写入", children: [] }, + { text: "自动生成 uid", children: [] }, + { text: "保留 sourceRefs", children: [] }, + ], + }, + { + text: "权限约束", + children: [ + { text: "需要 read_write", children: [] }, + { text: "限定资源路径", children: [] }, + { text: "拒绝越权写入", children: [] }, + ], + }, + { + text: "写后验证", + children: [ + { text: "fetch 再确认", children: [] }, + { text: "报告 JSON 路径", children: [] }, + { text: "截图看渲染", children: [] }, + ], + }, + { + text: "当前边界", + children: [ + { text: "非裸树文件", children: [] }, + { text: "apply_ops 受限", children: [] }, + { text: "长文本需压缩", children: [] }, + ], + }, + ]; +} + +function collectOutlineTexts(nodes, texts = []) { + for (const node of nodes) { + texts.push(String(node.text || "")); + collectOutlineTexts(Array.isArray(node.children) ? node.children : [], texts); + } + return texts; +} + +async function main() { + await fs.mkdir(OUTPUT_DIR, { recursive: true }); + const workspace = await callJson(`${BASE_URL}/api/local-folder/workspaces/default`, { + method: "POST", + headers: { + "content-type": "application/json", + "x-mnote-actor-id": "mnote-e2e", + "x-mnote-actor-type": "user", + }, + data: {}, + }); + const rootUri = workspace.workspace?.rootUri || ""; + const rootPath = workspace.workspace?.rootPath || ""; + const workspaceId = workspace.workspace?.manifest?.workspaceId || "local-ws:mnote-e2e:my-space"; + assert(rootUri.startsWith("file://"), `默认本地工作区 rootUri 异常: ${JSON.stringify(workspace)}`); + assert(rootPath, `默认本地工作区 rootPath 缺失: ${JSON.stringify(workspace)}`); + + const documentName = `MindmapSkillCapabilitySmoke-${Date.now()}.md`; + const documentId = `local-md:${documentName}`; + const resourcePath = `maps/task503-mindmap-skill-capabilities-${Date.now()}.mindmap.json`; + const title = "mnote-mindmap skill 能力"; + await fs.mkdir(path.join(rootPath, "maps"), { recursive: true }); + await fs.writeFile(path.join(rootPath, documentName), "# Mindmap Skill Capability Smoke\n", "utf8"); + + const skill = await callMnoteTool({ + toolName: "mnote.skill.read", + workspaceId, + documentId, + rootUri, + args: { skillId: "mnote-mindmap", agentId: "reasonix" }, + }); + const skillContent = String(skill.content || ""); + assert(skillContent.includes("Mindmap outline format"), "skill 缺少导图格式小节"); + assert(skillContent.includes("Use concise keywords or short phrases"), "skill 缺少短语化节点要求"); + assert(skillContent.includes("Avoid long paragraphs"), "skill 缺少避免长段落要求"); + + const outline = skillCapabilityOutline(); + const outlineTexts = collectOutlineTexts(outline); + assert(outlineTexts.every((text) => text.length > 0 && text.length <= 32), "outline 节点应保持短语化"); + assert(outlineTexts.every((text) => !text.includes("\n")), "outline 节点不能用换行模拟层级"); + + const aiAccessScope = { + permissionLevel: "read_write", + allowedResourceIds: [resourcePath], + }; + const created = await callMnoteTool({ + toolName: "mnote.mindmap.create_from_outline", + workspaceId, + documentId, + rootUri, + idempotencyKey: `idem_${TASK}_${Date.now()}`, + args: { + mindmapId: resourcePath, + resourcePath, + title, + outline, + embedIntoPage: true, + sourceRefs: [{ kind: "skill", title: "mnote-mindmap", note: TASK }], + aiAccessScope, + }, + }); + assert(created.root?.data?.text === title, `创建结果根节点异常: ${JSON.stringify(created.root)}`); + assert(created.embedResult?.status === "embedded", `创建结果缺少页面绑定: ${JSON.stringify(created.embedResult)}`); + assert( + Array.isArray(created.changedFiles) && created.changedFiles.includes(documentName), + `changedFiles 应包含当前 Markdown 页面: ${JSON.stringify(created.changedFiles)}`, + ); + const markdownAfterEmbed = await fs.readFile(path.join(rootPath, documentName), "utf8"); + assert( + markdownAfterEmbed.includes(`[${title}](${resourcePath})`), + `当前 Markdown 页面未写入 mindmap 链接: ${markdownAfterEmbed}`, + ); + + const fetched = await callMnoteTool({ + toolName: "mnote.mindmap.fetch", + workspaceId, + documentId, + rootUri, + args: { + mindmapId: resourcePath, + resourcePath, + scope: "full_envelope", + aiAccessScope, + }, + }); + const nodeCount = Array.isArray(fetched.nodes) ? fetched.nodes.length : 0; + assert(fetched.root?.data?.text === title, `回读根节点异常: ${JSON.stringify(fetched.root)}`); + assert(nodeCount >= 25, `回读节点数不足: ${nodeCount}`); + + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext({ viewport: { width: 1440, height: 960 } }); + const page = await context.newPage(); + const events = []; + page.on("pageerror", (error) => events.push({ kind: "pageerror", text: error.message })); + page.on("console", (message) => { + if (message.type() === "error") { + events.push({ kind: "console", text: message.text() }); + } + }); + page.on("response", (response) => { + if (response.url().includes("/api/mindmap/")) { + events.push({ kind: "mindmap-response", status: response.status(), url: response.url() }); + } + }); + const viewer = await signInBrowserContext(context); + const url = `${BASE_URL}/mindmap/${encodeURIComponent(documentId)}/${encodeURIComponent(`local-file:${resourcePath}`)}?sourceKind=local_folder&rootUri=${encodeURIComponent(rootUri)}&workspaceId=${encodeURIComponent(workspaceId)}`; + await page.goto(url, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + await page.locator('[data-testid="mnote-mindmap-editor-root"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + (expectedTitle) => { + const text = document.body.innerText || ""; + const countText = document.querySelector('[data-testid="mindmap-schema-count"]')?.textContent || ""; + return text.includes(expectedTitle) && text.includes("触发场景") && /节点\s*(?:[1-9]|[1-9][0-9]+)/.test(countText); + }, + title, + { timeout: UI_TIMEOUT_MS }, + ); + const screenshotPath = path.join(OUTPUT_DIR, "skill-capability-mindmap.png"); + await page.screenshot({ path: screenshotPath, fullPage: true }); + const ui = await page.evaluate(() => ({ + bodyHasTitle: (document.body.innerText || "").includes("mnote-mindmap skill 能力"), + bodyHasBranch: (document.body.innerText || "").includes("触发场景"), + bodyHasPermission: (document.body.innerText || "").includes("权限约束"), + schemaCount: document.querySelector('[data-testid="mindmap-schema-count"]')?.textContent || null, + rootVisible: Boolean(document.querySelector('[data-testid="mnote-mindmap-editor-root"]')), + errorText: document.querySelector('[data-testid="leptos-mindmap-error"]')?.textContent || null, + })); + await browser.close(); + assert(ui.bodyHasTitle && ui.bodyHasBranch && ui.bodyHasPermission, `导图页面文本缺失: ${JSON.stringify(ui)}`); + assert(!ui.errorText, `导图页面出现错误: ${ui.errorText}`); + + await writeResult({ + ok: true, + rootUri, + workspaceId, + documentId, + resourcePath, + mindmapFile: path.join(rootPath, resourcePath), + screenshotPath, + url, + viewer, + skillFormatRules: { + hasFormat: skillContent.includes("Mindmap outline format"), + hasShortPhraseRule: skillContent.includes("Use concise keywords or short phrases"), + hasAvoidParagraphRule: skillContent.includes("Avoid long paragraphs"), + }, + nodeCount, + embedResult: created.embedResult, + markdownHasMindmapLink: markdownAfterEmbed.includes(`[${title}](${resourcePath})`), + firstLevel: (fetched.root?.children || []).map((child) => child?.data?.text), + ui, + events, + }); + console.log(`task503 mindmap skill capability smoke passed: ${RESULT_PATH}`); +} + +main().catch(async (error) => { + await writeResult({ + ok: false, + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : null, + }).catch(() => undefined); + console.error(error instanceof Error ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task504-page-ai-history-agent-filter-smoke.js b/scripts/task504-page-ai-history-agent-filter-smoke.js new file mode 100644 index 00000000..3e0c32fd --- /dev/null +++ b/scripts/task504-page-ai-history-agent-filter-smoke.js @@ -0,0 +1,499 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { chromium } = require("playwright"); +const { + BASE_URL, + UI_TIMEOUT_MS, + ensureAuthenticated, +} = require("./tree-shell-smoke-helpers"); + +const TASK = "task504-page-ai-history-agent-filter-smoke"; +const OUTPUT_DIR = path.join(process.cwd(), "tmp", TASK); +const RESULT_PATH = path.join(OUTPUT_DIR, "result.json"); +const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH + || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium", "/usr/bin/chromium"] + .find((candidate) => fs.existsSync(candidate)); + +function fileUrl(localPath) { + return `file://${localPath}`; +} + +function localMdDocumentId(relativePath) { + return `local-md:${relativePath.replaceAll("/", "~2F")}`; +} + +function documentUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`); + url.searchParams.set("sourceKind", "local_folder"); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("treeView", "filetree"); + return url.toString(); +} + +function writeWorkspaceManifest(root, ownerId, workspaceId) { + fs.mkdirSync(path.join(root, ".mnote"), { recursive: true }); + fs.writeFileSync( + path.join(root, ".mnote", "workspace.json"), + `${JSON.stringify({ + workspaceId, + ownerId, + createdAt: new Date().toISOString(), + capabilities: ["local_files", "ai_sessions", "markdown_edit"], + }, null, 2)}\n`, + "utf8", + ); +} + +async function saveScreenshot(page, name) { + const target = path.join(OUTPUT_DIR, `${name}.png`); + await page.screenshot({ path: target, fullPage: false }); + return target; +} + +async function main() { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + const suffix = Date.now().toString(36); + const actorId = "mnote-e2e"; + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task504-history-")); + const rootUri = fileUrl(root); + const workspaceId = `local-ws:${actorId}:task504`; + const relativePath = "HistoryAgentFilter.md"; + const documentId = localMdDocumentId(relativePath); + const longGeminiMessage = [ + "Gemini 历史预览应只显示摘要,不应把 ChatOnly 的完整长消息塞进历史列表。", + "这段内容用于模拟网页问答返回的完整长回复,历史列表应保留可扫描性。", + "FULL_TAIL_SHOULD_NOT_RENDER", + ].join(""); + const sessions = [ + { + sessionId: `mnote_task504_gemini_${suffix}`, + runId: `run_task504_gemini_${suffix}`, + title: "Gemini 问答", + profile: "shared_gemini_chat", + acpRuntime: "hermes", + status: "completed", + payload: { + agentId: "chat_only", + profileId: "shared_gemini_chat", + profile: "shared_gemini_chat", + acpRuntime: "hermes", + message: longGeminiMessage, + }, + createdAt: "2026-05-30T08:00:00Z", + updatedAt: "2026-05-30T08:02:00Z", + persistence: "sqlite_acp_runtime_store", + }, + { + sessionId: `mnote_task504_doubao_${suffix}`, + runId: `run_task504_doubao_${suffix}`, + title: "豆包问答", + profile: "shared_doubao_chat", + acpRuntime: "hermes", + status: "completed", + payload: { + agentId: "chat_only", + profileId: "shared_doubao_chat", + profile: "shared_doubao_chat", + acpRuntime: "hermes", + message: "豆包短回复", + }, + createdAt: "2026-05-30T08:01:00Z", + updatedAt: "2026-05-30T08:01:30Z", + persistence: "sqlite_acp_runtime_store", + }, + { + sessionId: `mnote_task504_stale_chatonly_${suffix}`, + runId: `run_task504_stale_chatonly_${suffix}`, + title: "旧 ChatOnly profile", + profile: "myHermes", + acpRuntime: "hermes", + status: "completed", + payload: { + agentId: "chat_only", + profileId: "myHermes", + profile: "myHermes", + acpRuntime: "hermes", + message: "旧数据里误写成 Hermes profile 的 ChatOnly 会话", + }, + createdAt: "2026-05-30T08:00:50Z", + updatedAt: "2026-05-30T08:01:00Z", + persistence: "sqlite_acp_runtime_store", + }, + { + sessionId: `mnote_task504_hermes_${suffix}`, + runId: `run_task504_hermes_${suffix}`, + title: "Hermes 问答", + profile: "shared_lite", + acpRuntime: "hermes", + status: "completed", + payload: { + agentId: "hermes", + profileId: "shared_lite", + profile: "shared_lite", + acpRuntime: "hermes", + message: "Hermes 回复", + }, + createdAt: "2026-05-30T08:00:30Z", + updatedAt: "2026-05-30T08:00:45Z", + persistence: "sqlite_acp_runtime_store", + }, + ]; + const capturedRuns = []; + let caughtError = null; + const screenshots = {}; + + writeWorkspaceManifest(root, actorId, workspaceId); + fs.writeFileSync(path.join(root, relativePath), ["# History Agent Filter", "", suffix, ""].join("\n"), "utf8"); + + const browser = await chromium.launch({ + headless: true, + ...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}), + }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 960 }, + locale: "zh-CN", + extraHTTPHeaders: { + "x-mnote-actor-id": actorId, + "x-mnote-actor-type": "user", + }, + }); + const page = await context.newPage(); + + try { + await page.route("**/api/user/access-policy**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + controlPlane: "sqlite", + grants: [{ + id: `grant_task504_${suffix}`, + userId: actorId, + workspaceId, + rootUri, + rootPath: root, + permission: "write", + recursive: true, + capabilities: ["ai", "markdown_edit"], + source: "user", + status: "active", + }], + }), + }); + }); + await page.route("**/api/ui/preferences**", async (route) => { + if (route.request().method() === "GET") { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + owner: "mnote-web", + result: { + aiPreferences: { + "ai.common.default_agent_id": "chat_only", + "ai.agent.hermes.profile_id": "shared_deepseek_chat", + "ai.common.context_refs.default_selected": { + current_page: true, + selection: false, + active_editor: false, + file: false, + folder: false, + changed_files: false, + }, + }, + }, + }), + }); + return; + } + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, owner: "mnote-web", result: {} }), + }); + }); + await page.route("**/api/documents/buffer-state**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + result: { dirtyState: "", fileVersion: `task504-${suffix}` }, + }), + }); + }); + await page.route("**/api/hermes/client/gateway/health**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + gateway: { ok: true, status: "mocked" }, + profile: { name: "reasonix", modelConfigured: true, apiKeyConfigured: true }, + suggestions: [], + }), + }); + }); + await page.route("**/api/hermes/client/tools**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, tools: [] }), + }); + }); + await page.route("**/api/hermes/client/profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + active: "mnoteai", + profiles: [{ name: "mnoteai", label: "MNote AI", modelConfigured: true, apiKeyConfigured: true }], + }), + }); + }); + await page.route("**/api/ai/agent-profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + agentId: "hermes", + profiles: [ + { profileId: "shared_deepseek_chat", kind: "shared", displayName: "DeepSeek Chat", baseProfile: "deepseek-chat", isolatedProfile: "openclaw-deepseek-chat", readonly: true }, + { profileId: "shared_doubao_chat", kind: "shared", displayName: "豆包 Chat", baseProfile: "doubao-chat", isolatedProfile: "openclaw-doubao-chat", readonly: true }, + { profileId: "shared_gemini_chat", kind: "shared", displayName: "Gemini Chat", baseProfile: "gemini-chat", isolatedProfile: "openclaw-gemini-chat", readonly: true }, + { profileId: "shared_lite", kind: "shared", displayName: "Lite", baseProfile: "lite", isolatedProfile: "lite", readonly: true }, + ], + }), + }); + }); + await page.route("**/api/hermes/client/skills**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, categories: [], archived: [] }), + }); + }); + await page.route("**/api/hermes/client/sessions**", async (route) => { + if (route.request().method() === "GET") { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + persistence: "sqlite_acp_runtime_store", + sessions, + }), + }); + return; + } + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + sessionId: `mnote_task504_new_${suffix}`, + title: "当前页问答", + persistence: "sqlite_acp_runtime_store", + }), + }); + }); + await page.route("**/api/hermes/client/runs", async (route) => { + const body = JSON.parse(route.request().postData() || "{}"); + capturedRuns.push(body); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + upstream: { + runId: `run_task504_current_page_${suffix}`, + traceId: `trace_task504_current_page_${suffix}`, + }, + }), + }); + }); + await page.route("**/api/hermes/client/events/run_task504_current_page_*", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "text/event-stream; charset=utf-8" }, + body: [ + "event: message.delta", + "data: {\"text\":\"### TASK504_CURRENT_PAGE_OK\\n- **Markdown 渲染**\"}", + "", + "event: run.completed", + "data: {\"output\":\"### TASK504_CURRENT_PAGE_OK\\n- **Markdown 渲染**\"}", + "", + ].join("\n"), + }); + }); + + await ensureAuthenticated(page, context.request); + const response = await page.goto(documentUrl(root, relativePath), { + waitUntil: "domcontentloaded", + timeout: UI_TIMEOUT_MS, + }); + assert(response && response.status() === 200, `文档页状态码异常: ${response && response.status()}`); + await page.locator('[data-testid="mnote-leptos-tiptap-island-editor-root"] .ProseMirror').first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-testid="wolai-page-ai-drawer"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator('.wolai-page-ai-header-actions [data-page-ai-action="history"]').click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-panel="history"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator(`[data-page-ai-session-row="mnote_task504_gemini_${suffix}"]`).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + + const historyPanel = page.locator('[data-page-ai-panel="history"]'); + const historyText = await historyPanel.innerText({ timeout: UI_TIMEOUT_MS }); + assert(historyText.includes("ChatOnly / Gemini"), "历史会话应显示 Gemini 所属 agent"); + assert(historyText.includes("ChatOnly / 豆包"), "历史会话应显示豆包所属 agent"); + assert(historyText.includes("Hermes / Lite"), "历史会话应显示 Hermes profile"); + assert(!historyText.includes("ChatOnly / myHermes"), "ChatOnly 历史不应显示 Hermes profile 标签"); + assert(!historyText.includes("FULL_TAIL_SHOULD_NOT_RENDER"), "历史预览不应显示 ChatOnly 完整长消息尾部"); + + const filter = page.locator('[data-page-ai-session-agent-filter]'); + await filter.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await filter.selectOption("chat_only:shared_gemini_chat", { timeout: UI_TIMEOUT_MS }); + await page.locator(`[data-page-ai-session-row="mnote_task504_gemini_${suffix}"]`).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + assert.strictEqual( + await page.locator(`[data-page-ai-session-row="mnote_task504_doubao_${suffix}"]`).count(), + 0, + "筛选 Gemini 后不应显示豆包会话", + ); + await filter.selectOption("chat_only:shared_doubao_chat", { timeout: UI_TIMEOUT_MS }); + await page.locator(`[data-page-ai-session-row="mnote_task504_doubao_${suffix}"]`).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + assert.strictEqual( + await page.locator(`[data-page-ai-session-row="mnote_task504_gemini_${suffix}"]`).count(), + 0, + "筛选豆包后不应显示 Gemini 会话", + ); + screenshots.history = await saveScreenshot(page, "history-filter"); + + await page.locator('.wolai-page-ai-header-actions [data-page-ai-action="history"]').click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-panel="chat"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator('.wolai-page-ai-header-actions [data-page-ai-action="new-session"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => document.querySelector('[data-page-ai-agent-chip]')?.textContent?.includes("DeepSeek"), + null, + { timeout: UI_TIMEOUT_MS }, + ); + await page.evaluate(({ staleWorkspaceId, documentId, rootUri: currentRootUri }) => { + const staleSnapshot = { + schema: "mnote.open_editors_snapshot.v1", + generatedAt: Date.now(), + activeObjectIdentity: "page:primary", + activeEditor: { + objectIdentity: "page:primary", + workspacePath: { + schema: "mnote.workspace_path.v1", + workspaceId: staleWorkspaceId, + sourceKind: "local_folder", + rootUri: currentRootUri, + relativePath: "HistoryAgentFilter.md", + documentId, + objectIdentity: "page:primary", + assetId: "", + resourceKind: "page", + }, + paneRole: "primary", + documentId, + workspaceId: staleWorkspaceId, + title: "Stale page target", + kind: "page", + editorKind: "page", + active: true, + dirtyState: "", + preview: false, + pinned: true, + lastActiveAt: Date.now(), + assetId: "", + path: "HistoryAgentFilter.md", + }, + editors: [], + resourceEditors: [], + groups: { primary: { paneRole: "primary", activeObjectIdentity: "page:primary", editors: [], resourceEditors: [] }, secondary: { paneRole: "secondary", activeObjectIdentity: "", editors: [], resourceEditors: [] } }, + }; + staleSnapshot.editors = [staleSnapshot.activeEditor]; + staleSnapshot.groups.primary.editors = [staleSnapshot.activeEditor]; + window.__mnoteOpenEditorsSnapshot = staleSnapshot; + const previous = window.__mnoteDocumentPaneRuntime || {}; + window.__mnoteDocumentPaneRuntime = { + ...previous, + getOpenEditorsSnapshot: () => staleSnapshot, + }; + }, { + staleWorkspaceId: `${workspaceId}:stale`, + documentId, + rootUri, + }); + await page.locator("[data-page-ai-input]").fill(`task504 current page target ${suffix}`, { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-panel="chat"] [data-page-ai-conversation]').getByText("TASK504_CURRENT_PAGE_OK").waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + const markdownState = await page.locator('[data-page-ai-panel="chat"] [data-page-ai-conversation]').evaluate((root) => ({ + heading: Boolean(root.querySelector(".wolai-page-ai-message--assistant .wolai-page-ai-message-text h3")), + strong: Boolean(root.querySelector(".wolai-page-ai-message--assistant .wolai-page-ai-message-text strong")), + rawHeadingMarkers: root.textContent?.includes("### TASK504_CURRENT_PAGE_OK") || false, + rawStrongMarkers: root.textContent?.includes("**Markdown 渲染**") || false, + })); + assert.deepStrictEqual(markdownState, { + heading: true, + strong: true, + rawHeadingMarkers: false, + rawStrongMarkers: false, + }, `AI 面板应渲染 Markdown,而不是显示原始标记: ${JSON.stringify(markdownState)}`); + assert.strictEqual(capturedRuns.length, 1, "当前页 ChatOnly 请求应通过前置 target 校验并到达 runs API"); + assert.strictEqual(capturedRuns[0].agentId, "chat_only", "应使用 ChatOnly agent"); + assert.strictEqual(capturedRuns[0].profile, "shared_deepseek_chat", "应使用 DeepSeek ChatOnly profile"); + assert.strictEqual( + capturedRuns[0].runTargetSnapshot?.editorTarget?.workspaceId, + workspaceId, + "只选择当前页时 runTargetSnapshot 应使用当前 workspaceId,而不是 stale active editor workspaceId", + ); + assert( + capturedRuns[0].contextRefs.some((item) => item.kind === "current_page" && item.workspaceId === workspaceId), + "当前页 contextRef 应保留当前 workspaceId", + ); + assert( + !capturedRuns[0].contextRefs.some((item) => item.kind === "active_editor"), + "取消打开资源后不应发送 active_editor contextRef", + ); + screenshots.currentPageTarget = await saveScreenshot(page, "current-page-target"); + } catch (error) { + caughtError = error; + try { + screenshots.failure = await saveScreenshot(page, "failure"); + } catch (_) {} + } finally { + await browser.close(); + const result = { + ok: !caughtError, + error: caughtError ? String(caughtError && caughtError.stack || caughtError) : null, + screenshots, + root, + capturedRuns, + }; + fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + if (caughtError) { + console.error(JSON.stringify(result, null, 2)); + process.exit(1); + } + console.log(JSON.stringify(result, null, 2)); + } +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/scripts/task512-chatonly-doubao-sync-smoke.js b/scripts/task512-chatonly-doubao-sync-smoke.js new file mode 100644 index 00000000..c4f607ec --- /dev/null +++ b/scripts/task512-chatonly-doubao-sync-smoke.js @@ -0,0 +1,499 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { execFileSync } = require("node:child_process"); +const { chromium } = require("playwright"); +const { + BASE_URL, + UI_TIMEOUT_MS, + ensureAuthenticated, +} = require("./tree-shell-smoke-helpers"); + +const TASK = "task512-chatonly-doubao-sync-smoke"; +const OUTPUT_DIR = path.join(process.cwd(), "tmp", TASK); +const RESULT_PATH = path.join(OUTPUT_DIR, "result.json"); +const CONTROL_PLANE_DB = "/mnt/Data1T/Mnote_data/control-plane/control-plane.db"; +const DOUBAO_CDP_URL = process.env.MNOTE_DOUBAO_CDP_URL || "http://127.0.0.1:9233"; +const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH + || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium", "/usr/bin/chromium"] + .find((candidate) => fs.existsSync(candidate)); + +function fileUrl(localPath) { + return `file://${localPath}`; +} + +function localMdDocumentId(relativePath) { + return `local-md:${relativePath.replaceAll("/", "~2F")}`; +} + +function documentUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`); + url.searchParams.set("sourceKind", "local_folder"); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("treeView", "filetree"); + return url.toString(); +} + +function writeWorkspaceManifest(root, ownerId, workspaceId) { + fs.mkdirSync(path.join(root, ".mnote"), { recursive: true }); + fs.writeFileSync( + path.join(root, ".mnote", "workspace.json"), + `${JSON.stringify({ + workspaceId, + ownerId, + createdAt: new Date().toISOString(), + capabilities: ["local_files", "ai_sessions", "markdown_edit"], + }, null, 2)}\n`, + "utf8", + ); +} + +function sqliteJson(sql, fallback = null) { + try { + const raw = execFileSync("sqlite3", ["-json", CONTROL_PLANE_DB, sql], { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }).trim(); + return raw ? JSON.parse(raw) : fallback; + } catch { + return fallback; + } +} + +function sqlQuote(value) { + return `'${String(value).replaceAll("'", "''")}'`; +} + +function sqliteExec(sql) { + execFileSync("sqlite3", [CONTROL_PLANE_DB, sql], { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }); +} + +function grantWorkspaceAccess({ actorId, workspaceId, root, rootUri, grantId }) { + const now = new Date().toISOString(); + sqliteExec(` + INSERT OR IGNORE INTO users (id, email, username, display_name, role, status, created_at, updated_at, revision) + VALUES (${sqlQuote(actorId)}, ${sqlQuote(`${actorId}@example.com`)}, ${sqlQuote(actorId)}, ${sqlQuote(actorId)}, 'user', 'active', ${sqlQuote(now)}, ${sqlQuote(now)}, 1); + INSERT OR REPLACE INTO workspaces (id, owner_user_id, name, kind, root_uri, root_path, source_kind, status, created_at, updated_at, revision) + VALUES (${sqlQuote(workspaceId)}, ${sqlQuote(actorId)}, 'Task512 Doubao Smoke', 'personal', ${sqlQuote(rootUri)}, ${sqlQuote(root)}, 'local_folder', 'active', ${sqlQuote(now)}, ${sqlQuote(now)}, 1); + INSERT OR REPLACE INTO directory_grants (id, user_id, workspace_id, root_uri, root_path, permission, recursive, capabilities_json, source, status, created_by, created_at, updated_at, revision) + VALUES (${sqlQuote(grantId)}, ${sqlQuote(actorId)}, ${sqlQuote(workspaceId)}, ${sqlQuote(rootUri)}, ${sqlQuote(root)}, 'write', 1, '["ai","markdown_edit"]', 'smoke', 'active', ${sqlQuote(actorId)}, ${sqlQuote(now)}, ${sqlQuote(now)}, 1); + `); +} + +async function firstDoubaoPage(cdpBrowser) { + for (const context of cdpBrowser.contexts()) { + const page = context.pages().find((candidate) => candidate.url().includes("doubao.com")); + if (page) return page; + } + const context = cdpBrowser.contexts()[0] || await cdpBrowser.newContext(); + const page = await context.newPage(); + await page.goto("https://www.doubao.com/chat/", { waitUntil: "domcontentloaded" }); + return page; +} + +async function installDoubaoFetchProbe(page) { + await page.evaluate(() => { + const win = window; + const shouldTrack = (url) => url.includes("/samantha/chat/completion") + || url.includes("/im/conversation/batch_del_user_conv"); + if (!win.__mnoteDoubaoOriginalFetch) { + win.__mnoteDoubaoOriginalFetch = win.fetch.bind(win); + } + win.__mnoteDoubaoFetchLog = []; + win.fetch = async function patchedMnoteDoubaoFetch(input, init) { + const url = String((input && input.url) || input || ""); + const method = String((init && init.method) || "GET").toUpperCase(); + if (shouldTrack(url)) { + win.__mnoteDoubaoFetchLog.push({ + transport: "fetch", + url, + method, + body: init && init.body ? String(init.body) : "", + ts: Date.now(), + }); + } + return win.__mnoteDoubaoOriginalFetch(input, init); + }; + if (!win.__mnoteDoubaoOriginalXHROpen && win.XMLHttpRequest) { + win.__mnoteDoubaoOriginalXHROpen = win.XMLHttpRequest.prototype.open; + win.__mnoteDoubaoOriginalXHRSend = win.XMLHttpRequest.prototype.send; + win.XMLHttpRequest.prototype.open = function patchedMnoteDoubaoXHROpen(method, url, ...rest) { + this.__mnoteDoubaoProbeMethod = String(method || "GET").toUpperCase(); + this.__mnoteDoubaoProbeUrl = String(url || ""); + return win.__mnoteDoubaoOriginalXHROpen.call(this, method, url, ...rest); + }; + win.XMLHttpRequest.prototype.send = function patchedMnoteDoubaoXHRSend(body) { + const url = String(this.__mnoteDoubaoProbeUrl || ""); + if (shouldTrack(url)) { + win.__mnoteDoubaoFetchLog.push({ + transport: "xhr", + url, + method: String(this.__mnoteDoubaoProbeMethod || "GET").toUpperCase(), + body: body ? String(body) : "", + ts: Date.now(), + }); + } + return win.__mnoteDoubaoOriginalXHRSend.call(this, body); + }; + } + }); +} + +async function readDoubaoFetchLog(page) { + return await page.evaluate(() => Array.isArray(window.__mnoteDoubaoFetchLog) + ? window.__mnoteDoubaoFetchLog + : []); +} + +async function readDoubaoConversationStats(page, { marker, prompt }) { + return await page.evaluate(({ marker, prompt }) => { + const normalize = (value) => String(value || "").replace(/\s+/g, " ").trim(); + const markerText = String(marker || ""); + const promptText = String(prompt || ""); + const userBubbleTexts = Array.from(document.querySelectorAll(".bg-g-send-msg-bubble-bg")) + .map((node) => normalize(node.textContent)); + const assistantMarkdownTexts = Array.from(document.querySelectorAll(".md-box-root")) + .map((node) => normalize(node.textContent)); + return { + userPromptCount: userBubbleTexts.filter((text) => text === normalize(promptText)).length, + userMarkerCount: userBubbleTexts.filter((text) => text.includes(markerText)).length, + assistantMarkerCount: assistantMarkdownTexts.filter((text) => text.includes(markerText)).length, + userBubbleTexts, + assistantMarkdownTexts, + }; + }, { marker, prompt }); +} + +async function saveScreenshot(page, name) { + const target = path.join(OUTPUT_DIR, `${name}.png`); + await page.screenshot({ path: target, fullPage: false }); + return target; +} + +function doubaoConversationUrl(remoteConversationId) { + return `https://www.doubao.com/chat/${encodeURIComponent(remoteConversationId)}`; +} + +async function captureDoubaoPage(page, { name, url, remoteConversationId = "" }) { + let navigationError = ""; + if (url) { + await page.goto(url, { waitUntil: "domcontentloaded", timeout: 60_000 }).catch((error) => { + navigationError = error instanceof Error ? error.message : String(error); + }); + } + await page.bringToFront().catch(() => {}); + await page.waitForTimeout(2500); + const screenshot = await saveScreenshot(page, name); + const title = await page.title().catch(() => ""); + const pageUrl = page.url(); + const visibleText = await page.locator("body").innerText({ timeout: 5000 }).catch(() => ""); + const conversationRowCount = remoteConversationId + ? await page.locator(`#conversation_${remoteConversationId}`).count().catch(() => -1) + : null; + return { + screenshot, + requestedUrl: url || "", + pageUrl, + title, + navigationError, + conversationRowCount, + visibleText: visibleText.slice(0, 2000), + }; +} + +async function main() { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + const suffix = Date.now().toString(36); + const actorId = "mnote-e2e"; + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task512-doubao-")); + const rootUri = fileUrl(root); + const workspaceId = `local-ws:${actorId}:task512-${suffix}`; + const relativePath = "DoubaoChatOnlySync.md"; + const marker = `MNOTE_DOUBAO_SYNC_${suffix.toUpperCase()}`; + const prompt = `请只回复以下字符串,不要添加空格或其他内容:${marker}`; + const screenshots = {}; + const providerCaptures = {}; + const runRequests = []; + const deleteResponses = []; + let caughtError = null; + + writeWorkspaceManifest(root, actorId, workspaceId); + fs.writeFileSync(path.join(root, relativePath), ["# Doubao ChatOnly Sync", "", marker, ""].join("\n"), "utf8"); + grantWorkspaceAccess({ + actorId, + workspaceId, + root, + rootUri, + grantId: `grant_task512_${suffix}`, + }); + + const doubaoBrowser = await chromium.connectOverCDP(DOUBAO_CDP_URL); + const doubaoPage = await firstDoubaoPage(doubaoBrowser); + await doubaoPage.goto("https://www.doubao.com/chat/", { + waitUntil: "domcontentloaded", + timeout: 60_000, + }).catch(() => {}); + await doubaoPage.keyboard.press("Escape").catch(() => {}); + await installDoubaoFetchProbe(doubaoPage); + + const browser = await chromium.launch({ + headless: true, + ...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}), + }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 960 }, + locale: "zh-CN", + extraHTTPHeaders: { + "x-mnote-actor-id": actorId, + "x-mnote-actor-type": "user", + }, + }); + const page = await context.newPage(); + + try { + await page.route("**/api/hermes/client/runs", async (route) => { + runRequests.push(JSON.parse(route.request().postData() || "{}")); + await route.continue(); + }); + page.on("response", async (response) => { + const url = response.url(); + const request = response.request(); + if (request.method() === "DELETE" && url.includes("/api/hermes/client/sessions/")) { + deleteResponses.push({ + url, + status: response.status(), + body: await response.text().catch(() => ""), + }); + } + }); + + await ensureAuthenticated(page, context.request); + const response = await page.goto(documentUrl(root, relativePath), { + waitUntil: "domcontentloaded", + timeout: UI_TIMEOUT_MS, + }); + assert(response && response.status() === 200, `文档页状态码异常: ${response && response.status()}`); + await page.locator('[data-testid="mnote-leptos-tiptap-island-editor-root"] .ProseMirror').first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-testid="wolai-page-ai-drawer"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator('.wolai-page-ai-header-actions [data-page-ai-action="new-session"]').click({ + timeout: UI_TIMEOUT_MS, + }); + await page.locator("[data-page-ai-agent-button]").click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-agent-id="chat_only"][data-page-ai-profile-id="shared_doubao_chat"]').click({ + timeout: UI_TIMEOUT_MS, + }); + await page.waitForFunction( + () => document.querySelector("[data-page-ai-agent-chip]")?.textContent?.includes("ChatOnly / 豆包"), + null, + { timeout: UI_TIMEOUT_MS }, + ); + + await page.locator("[data-page-ai-input]").fill(prompt, { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS }); + const replyState = await page.waitForFunction( + (expectedMarker) => { + const assistantText = Array.from(document.querySelectorAll(".wolai-page-ai-message--assistant")) + .map((node) => node.textContent || "") + .join("\n"); + if (assistantText.includes(expectedMarker)) return "marker"; + if (assistantText.includes("豆包暂时无法回复")) return "provider_error"; + return ""; + }, + marker, + { timeout: 180_000 }, + ).then((handle) => handle.jsonValue()); + await page.waitForFunction( + () => (document.querySelector("[data-page-ai-run-status]")?.textContent || "").includes("完成"), + null, + { timeout: 180_000 }, + ).catch(() => {}); + screenshots.afterMessage = await saveScreenshot(page, "after-message"); + + assert.strictEqual(runRequests.length, 1, "MNote 本轮应只创建一个 run"); + const run = runRequests[0]; + assert.strictEqual(run.agentId, "chat_only", "应使用 ChatOnly agent"); + assert.strictEqual(run.profileId, "shared_doubao_chat", "应使用豆包 ChatOnly profile"); + assert.strictEqual(run.acpRuntime, "hermes", "豆包 ChatOnly 应走 Hermes/OpenClaw runtime"); + assert(run.sessionId, "run payload 应包含 MNote sessionId"); + + const logAfterMessage = await readDoubaoFetchLog(doubaoPage); + const completionCalls = logAfterMessage.filter((entry) => entry.url.includes("/samantha/chat/completion")); + assert.strictEqual(completionCalls.length, 0, "豆包 ChatOnly 应走真实 UI 发送,不应再直调 samantha completion"); + assert.notStrictEqual(replyState, "provider_error", "豆包返回限流/风控错误,未产生本轮 marker 回复"); + + const visibleAssistantTexts = await page.locator(".wolai-page-ai-message--assistant").allTextContents(); + const markerAssistantCount = visibleAssistantTexts.filter((text) => text.includes(marker)).length; + assert.strictEqual(markerAssistantCount, 1, "MNote 可见豆包回复应只有一条"); + + const sessionId = String(run.sessionId); + const bindingRows = sqliteJson( + `SELECT mnote_session_id, remote_conversation_id, status FROM ai_external_conversation_bindings WHERE user_id='${actorId}' AND mnote_session_id='${sessionId}' AND provider='doubao-web' ORDER BY updated_at DESC LIMIT 1;`, + [], + ); + assert(bindingRows && bindingRows.length === 1, "SQLite 应保存豆包远端会话绑定"); + assert.strictEqual(bindingRows[0].status, "active", "删除前 binding 应为 active"); + const remoteConversationId = bindingRows[0].remote_conversation_id; + assert(remoteConversationId, "binding 应包含 remote_conversation_id"); + + providerCaptures.afterMessage = await captureDoubaoPage(doubaoPage, { + name: "doubao-after-message", + url: doubaoConversationUrl(remoteConversationId), + remoteConversationId, + }); + const doubaoMessageStats = await readDoubaoConversationStats(doubaoPage, { marker, prompt }); + screenshots.doubaoAfterMessage = providerCaptures.afterMessage.screenshot; + assert( + providerCaptures.afterMessage.pageUrl.includes(remoteConversationId), + "豆包截图应定位到本轮远端 conversation_id", + ); + assert( + providerCaptures.afterMessage.visibleText.includes(marker), + "豆包本轮远端会话页面应显示 marker", + ); + assert.strictEqual( + providerCaptures.afterMessage.conversationRowCount, + 1, + "豆包删除前左侧历史列表应存在本轮 conversation 行", + ); + assert.strictEqual( + doubaoMessageStats.userPromptCount, + 1, + "豆包网页端本轮用户消息应只有一条", + ); + assert.strictEqual( + doubaoMessageStats.userMarkerCount, + 1, + "豆包网页端不应把豆包回复再次作为用户消息发送", + ); + assert.strictEqual( + doubaoMessageStats.assistantMarkerCount, + 1, + "豆包网页端本轮助手回复应只有一条", + ); + + await installDoubaoFetchProbe(doubaoPage); + + await page.locator('.wolai-page-ai-header-actions [data-page-ai-action="history"]').click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-panel="history"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator(`[data-page-ai-session-row="${sessionId}"]`).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + page.once("dialog", async (dialog) => { + await dialog.accept(); + }); + await page.locator(`[data-page-ai-session-delete="${sessionId}"]`).click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + (id) => !document.querySelector(`[data-page-ai-session-row="${CSS.escape(id)}"]`), + sessionId, + { timeout: UI_TIMEOUT_MS }, + ); + screenshots.afterDelete = await saveScreenshot(page, "after-delete"); + + await page.waitForTimeout(1000); + const logAfterDelete = await readDoubaoFetchLog(doubaoPage); + const deleteCalls = logAfterDelete.filter((entry) => entry.url.includes("/im/conversation/batch_del_user_conv")); + assert.strictEqual(deleteCalls.length, 1, "豆包端本轮只能收到一次会话删除请求"); + assert(deleteCalls[0].body.includes(remoteConversationId), "豆包删除请求应包含绑定的远端 conversation_id"); + assert(deleteResponses.length >= 1, "MNote 应发出历史会话 DELETE 请求"); + assert.strictEqual(deleteResponses.at(-1).status, 200, "MNote 历史会话 DELETE 应成功"); + const deleteBody = JSON.parse(deleteResponses.at(-1).body || "{}"); + assert.strictEqual( + deleteBody?.result?.providerConversationDelete?.response?.result?.providerDeleteMode, + "doubao_sidebar_menu", + "豆包远端删除必须走左侧会话三点菜单 + 确认弹窗路径", + ); + + providerCaptures.afterDelete = await captureDoubaoPage(doubaoPage, { + name: "doubao-after-delete", + url: "https://www.doubao.com/chat/", + remoteConversationId, + }); + screenshots.doubaoAfterDelete = providerCaptures.afterDelete.screenshot; + assert( + !providerCaptures.afterDelete.visibleText.includes(marker), + "豆包删除后聊天入口不应继续显示本轮 marker", + ); + assert.strictEqual( + providerCaptures.afterDelete.conversationRowCount, + 0, + "豆包删除后左侧历史列表不应继续存在本轮 conversation 行", + ); + + const deletedRows = sqliteJson( + `SELECT mnote_session_id, remote_conversation_id, status, metadata_json FROM ai_external_conversation_bindings WHERE user_id='${actorId}' AND mnote_session_id='${sessionId}' AND provider='doubao-web' ORDER BY updated_at DESC LIMIT 1;`, + [], + ); + assert(deletedRows && deletedRows.length === 1, "删除后 binding 仍应可审计"); + assert.strictEqual(deletedRows[0].status, "remote_deleted", "删除后 binding 应标记 remote_deleted"); + + fs.writeFileSync( + RESULT_PATH, + `${JSON.stringify({ + ok: true, + marker, + sessionId, + remoteConversationId, + samanthaCompletionCallCount: completionCalls.length, + doubaoMessageStats, + deleteCallCount: deleteCalls.length, + deleteResponse: deleteResponses.at(-1), + bindingBefore: bindingRows[0], + bindingAfter: deletedRows[0], + screenshots, + providerCaptures, + }, null, 2)}\n`, + "utf8", + ); + } catch (error) { + caughtError = error; + screenshots.failure = await saveScreenshot(page, "failure").catch(() => ""); + providerCaptures.failure = await captureDoubaoPage(doubaoPage, { + name: "doubao-failure", + url: "", + }).catch((captureError) => ({ + screenshot: "", + requestedUrl: "", + pageUrl: "", + title: "", + navigationError: captureError instanceof Error ? captureError.message : String(captureError), + visibleText: "", + })); + if (providerCaptures.failure.screenshot) { + screenshots.doubaoFailure = providerCaptures.failure.screenshot; + } + fs.writeFileSync( + RESULT_PATH, + `${JSON.stringify({ + ok: false, + marker, + error: error instanceof Error ? error.stack || error.message : String(error), + runRequests, + deleteResponses, + doubaoFetchLog: await readDoubaoFetchLog(doubaoPage).catch(() => []), + screenshots, + providerCaptures, + }, null, 2)}\n`, + "utf8", + ); + } finally { + await browser.close().catch(() => {}); + await doubaoBrowser.close().catch(() => {}); + } + + if (caughtError) throw caughtError; + console.log(JSON.stringify(JSON.parse(fs.readFileSync(RESULT_PATH, "utf8")), null, 2)); +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task513-chatonly-provider-sync-smoke.js b/scripts/task513-chatonly-provider-sync-smoke.js new file mode 100644 index 00000000..ea8e4545 --- /dev/null +++ b/scripts/task513-chatonly-provider-sync-smoke.js @@ -0,0 +1,512 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { execFileSync } = require("node:child_process"); +const { chromium } = require("playwright"); +const { + BASE_URL, + UI_TIMEOUT_MS, + ensureAuthenticated, +} = require("./tree-shell-smoke-helpers"); + +const PROVIDERS = { + deepseek: { + taskName: "task513-chatonly-deepseek-sync-smoke", + title: "DeepSeek ChatOnly Sync", + agentProfileId: "shared_deepseek_chat", + chipText: "ChatOnly / DeepSeek", + expectedProvider: "deepseek-web", + gatewayLog: "/home/lix/.openclaw-mnote-deepseek-chat/logs/gateway.out", + sendLogPattern: /\[DeepSeekWebClient\] Sending chat completion request/g, + configPath: "/home/lix/.openclaw-mnote-deepseek-chat/openclaw.json", + }, + gemini: { + taskName: "task513-chatonly-gemini-sync-smoke", + title: "Gemini ChatOnly Sync", + agentProfileId: "shared_gemini_chat", + chipText: "ChatOnly / Gemini", + expectedProvider: "gemini-web", + gatewayLog: "/home/lix/.openclaw-mnote-gemini-chat/logs/gateway.out", + sendLogPattern: /\[Gemini Web Browser\] DOM: typed message and pressed Enter/g, + cdpUrl: process.env.MNOTE_GEMINI_CDP_URL || "http://127.0.0.1:9232", + }, +}; + +const providerKey = process.argv[2] || process.env.MNOTE_CHATONLY_PROVIDER || "deepseek"; +const provider = PROVIDERS[providerKey]; +if (!provider) { + throw new Error(`未知 provider: ${providerKey}`); +} + +const OUTPUT_DIR = path.join(process.cwd(), "tmp", provider.taskName); +const RESULT_PATH = path.join(OUTPUT_DIR, "result.json"); +const CONTROL_PLANE_DB = "/mnt/Data1T/Mnote_data/control-plane/control-plane.db"; +const CHROMIUM_EXECUTABLE_PATH = + process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || + [ + "/usr/bin/google-chrome-stable", + "/usr/bin/google-chrome", + "/snap/bin/chromium", + "/usr/bin/chromium", + ].find((candidate) => fs.existsSync(candidate)); + +function fileUrl(localPath) { + return `file://${localPath}`; +} + +function localMdDocumentId(relativePath) { + return `local-md:${relativePath.replaceAll("/", "~2F")}`; +} + +function documentUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`); + url.searchParams.set("sourceKind", "local_folder"); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("treeView", "filetree"); + return url.toString(); +} + +function writeWorkspaceManifest(root, ownerId, workspaceId) { + fs.mkdirSync(path.join(root, ".mnote"), { recursive: true }); + fs.writeFileSync( + path.join(root, ".mnote", "workspace.json"), + `${JSON.stringify({ + workspaceId, + ownerId, + createdAt: new Date().toISOString(), + capabilities: ["local_files", "ai_sessions", "markdown_edit"], + }, null, 2)}\n`, + "utf8", + ); +} + +function sqliteJson(sql, fallback = null) { + try { + const raw = execFileSync("sqlite3", ["-json", CONTROL_PLANE_DB, sql], { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }).trim(); + return raw ? JSON.parse(raw) : fallback; + } catch { + return fallback; + } +} + +function sqlQuote(value) { + return `'${String(value).replaceAll("'", "''")}'`; +} + +function sqliteExec(sql) { + execFileSync("sqlite3", [CONTROL_PLANE_DB, sql], { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }); +} + +function grantWorkspaceAccess({ actorId, workspaceId, root, rootUri, grantId }) { + const now = new Date().toISOString(); + sqliteExec(` + INSERT OR IGNORE INTO users (id, email, username, display_name, role, status, created_at, updated_at, revision) + VALUES (${sqlQuote(actorId)}, ${sqlQuote(`${actorId}@example.com`)}, ${sqlQuote(actorId)}, ${sqlQuote(actorId)}, 'user', 'active', ${sqlQuote(now)}, ${sqlQuote(now)}, 1); + INSERT OR REPLACE INTO workspaces (id, owner_user_id, name, kind, root_uri, root_path, source_kind, status, created_at, updated_at, revision) + VALUES (${sqlQuote(workspaceId)}, ${sqlQuote(actorId)}, ${sqlQuote(actorId)}, 'personal', ${sqlQuote(rootUri)}, ${sqlQuote(root)}, 'local_folder', 'active', ${sqlQuote(now)}, ${sqlQuote(now)}, 1); + INSERT OR REPLACE INTO directory_grants (id, user_id, workspace_id, root_uri, root_path, permission, recursive, capabilities_json, source, status, created_by, created_at, updated_at, revision) + VALUES (${sqlQuote(grantId)}, ${sqlQuote(actorId)}, ${sqlQuote(workspaceId)}, ${sqlQuote(rootUri)}, ${sqlQuote(root)}, 'write', 1, '["ai","markdown_edit"]', 'smoke', 'active', ${sqlQuote(actorId)}, ${sqlQuote(now)}, ${sqlQuote(now)}, 1); + `); +} + +function logSize(logPath) { + try { + return fs.statSync(logPath).size; + } catch { + return 0; + } +} + +function readLogSince(logPath, offset) { + try { + const fd = fs.openSync(logPath, "r"); + const stat = fs.fstatSync(fd); + const start = offset > stat.size ? 0 : offset; + const buffer = Buffer.alloc(stat.size - start); + fs.readSync(fd, buffer, 0, buffer.length, start); + fs.closeSync(fd); + return buffer.toString("utf8"); + } catch { + return ""; + } +} + +function countMatches(text, pattern) { + return Array.from(String(text || "").matchAll(pattern)).length; +} + +function escapeRegExp(value) { + return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function parseCookieString(cookieString, domain) { + return String(cookieString || "") + .split(";") + .filter((cookie) => cookie.trim().includes("=")) + .map((cookie) => { + const [name, ...valueParts] = cookie.trim().split("="); + return { + name: name.trim(), + value: valueParts.join("=").trim(), + domain, + path: "/", + }; + }) + .filter((cookie) => cookie.name); +} + +function deepseekAuth() { + const config = JSON.parse(fs.readFileSync(provider.configPath, "utf8")); + return JSON.parse(config.models.providers["deepseek-web"].apiKey || "{}"); +} + +async function saveScreenshot(page, name) { + const target = path.join(OUTPUT_DIR, `${name}.png`); + await page.screenshot({ path: target, fullPage: false }); + return target; +} + +async function captureDeepseek(remoteConversationId, name) { + const auth = deepseekAuth(); + const browser = await chromium.launch({ + headless: true, + ...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}), + }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 960 }, + locale: "zh-CN", + userAgent: auth.userAgent, + }); + await context.addCookies(parseCookieString(auth.cookie, ".deepseek.com")); + if (auth.bearer) { + await context.addInitScript((token) => { + localStorage.setItem("userToken", JSON.stringify({ value: token, __version: "0" })); + }, auth.bearer); + } + const page = await context.newPage(); + if (auth.bearer) { + await page.route("https://chat.deepseek.com/api/**", (route) => { + route.continue({ + headers: { + ...route.request().headers(), + authorization: `Bearer ${auth.bearer}`, + }, + }); + }); + } + const url = `https://chat.deepseek.com/a/chat/s/${remoteConversationId}`; + await page.goto(url, { waitUntil: "domcontentloaded", timeout: 60_000 }).catch(() => {}); + await page.waitForTimeout(2500); + const screenshot = await saveScreenshot(page, name); + const title = await page.title().catch(() => ""); + const visibleText = await page.locator("body").innerText({ timeout: 5000 }).catch(() => ""); + const conversationLinkCount = await page + .locator(`a[href*="/a/chat/s/${remoteConversationId}"]`) + .count() + .catch(() => -1); + await browser.close().catch(() => {}); + return { + screenshot, + url, + title, + conversationLinkCount, + conversationMissingText: visibleText.includes("该对话不存在"), + visibleText: visibleText.slice(0, 1000), + }; +} + +async function firstGeminiPage(cdpBrowser, remoteConversationId) { + const targetUrl = String(remoteConversationId || ""); + for (const context of cdpBrowser.contexts()) { + const exact = context.pages().find((candidate) => candidate.url().split("#")[0] === targetUrl); + if (exact) return exact; + const gemini = context.pages().find((candidate) => candidate.url().includes("gemini.google.com")); + if (gemini) return gemini; + } + const context = cdpBrowser.contexts()[0] || await cdpBrowser.newContext(); + return await context.newPage(); +} + +async function captureGemini(remoteConversationId, name) { + const browser = await chromium.connectOverCDP(provider.cdpUrl); + const page = await firstGeminiPage(browser, remoteConversationId); + const targetUrl = String(remoteConversationId || ""); + if (targetUrl && page.url().split("#")[0] !== targetUrl) { + await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 60_000 }).catch(() => {}); + await page.waitForTimeout(1500); + } + await page.bringToFront().catch(() => {}); + await page.waitForTimeout(1000); + const screenshot = await saveScreenshot(page, name); + const title = await page.title().catch(() => ""); + const visibleText = await page.locator("body").innerText({ timeout: 5000 }).catch(() => ""); + const conversationLinkCount = await page.evaluate((target) => { + let targetPath = ""; + try { + targetPath = new URL(target).pathname; + } catch { + return -1; + } + return Array.from(document.querySelectorAll("a[href]")).filter((anchor) => { + try { + return new URL(anchor.href, location.href).pathname === targetPath; + } catch { + return false; + } + }).length; + }, targetUrl).catch(() => -1); + await browser.close().catch(() => {}); + return { + screenshot, + url: page.url(), + title, + conversationLinkCount, + visibleText: visibleText.slice(0, 1000), + }; +} + +async function captureProvider(remoteConversationId, name) { + if (providerKey === "deepseek") return await captureDeepseek(remoteConversationId, name); + return await captureGemini(remoteConversationId, name); +} + +async function main() { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + const suffix = Date.now().toString(36); + const actorId = "mnote-e2e"; + const root = fs.mkdtempSync(path.join(os.tmpdir(), `mnote-task513-${providerKey}-`)); + const rootUri = fileUrl(root); + const workspaceId = `local-ws:${actorId}:task513-${providerKey}-${suffix}`; + const relativePath = `${providerKey}-ChatOnlySync.md`; + const marker = `MNOTE_CHATONLY_SYNC_${suffix}`; + const screenshots = {}; + const providerCaptures = {}; + const runRequests = []; + const deleteResponses = []; + const gatewayLogOffset = logSize(provider.gatewayLog); + let caughtError = null; + + writeWorkspaceManifest(root, actorId, workspaceId); + fs.writeFileSync(path.join(root, relativePath), [`# ${provider.title}`, "", marker, ""].join("\n"), "utf8"); + grantWorkspaceAccess({ + actorId, + workspaceId, + root, + rootUri, + grantId: `grant_task513_${providerKey}_${suffix}`, + }); + + const browser = await chromium.launch({ + headless: true, + ...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}), + }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 960 }, + locale: "zh-CN", + extraHTTPHeaders: { + "x-mnote-actor-id": actorId, + "x-mnote-actor-type": "user", + }, + }); + const page = await context.newPage(); + + try { + await page.route("**/api/hermes/client/runs", async (route) => { + runRequests.push(JSON.parse(route.request().postData() || "{}")); + await route.continue(); + }); + page.on("response", async (response) => { + const url = response.url(); + const request = response.request(); + if (request.method() === "DELETE" && url.includes("/api/hermes/client/sessions/")) { + deleteResponses.push({ + url, + status: response.status(), + body: await response.text().catch(() => ""), + }); + } + }); + + await ensureAuthenticated(page, context.request); + const response = await page.goto(documentUrl(root, relativePath), { + waitUntil: "domcontentloaded", + timeout: UI_TIMEOUT_MS, + }); + assert(response && response.status() === 200, `文档页状态码异常: ${response && response.status()}`); + await page.locator('[data-testid="mnote-leptos-tiptap-island-editor-root"] .ProseMirror').first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-testid="wolai-page-ai-drawer"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator('.wolai-page-ai-header-actions [data-page-ai-action="new-session"]').click({ + timeout: UI_TIMEOUT_MS, + }); + await page.locator("[data-page-ai-agent-button]").click({ timeout: UI_TIMEOUT_MS }); + await page.locator(`[data-page-ai-agent-id="chat_only"][data-page-ai-profile-id="${provider.agentProfileId}"]`).click({ + timeout: UI_TIMEOUT_MS, + }); + await page.waitForFunction( + (expected) => document.querySelector("[data-page-ai-agent-chip]")?.textContent?.includes(expected), + provider.chipText, + { timeout: UI_TIMEOUT_MS }, + ); + + await page.locator("[data-page-ai-input]").fill(`请只回复:${marker}`, { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + (expectedMarker) => { + const assistantText = Array.from(document.querySelectorAll(".wolai-page-ai-message--assistant")) + .map((node) => node.textContent || "") + .join("\n"); + return assistantText.includes(expectedMarker); + }, + marker, + { timeout: 180_000 }, + ); + await page.waitForFunction( + () => (document.querySelector("[data-page-ai-run-status]")?.textContent || "").includes("完成"), + null, + { timeout: 180_000 }, + ).catch(() => {}); + screenshots.afterMessage = await saveScreenshot(page, "after-message"); + + assert.strictEqual(runRequests.length, 1, "MNote 本轮应只创建一个 run"); + const run = runRequests[0]; + assert.strictEqual(run.agentId, "chat_only", "应使用 ChatOnly agent"); + assert.strictEqual(run.profileId, provider.agentProfileId, `应使用 ${provider.title} profile`); + assert.strictEqual(run.acpRuntime, "hermes", "ChatOnly 网页 provider 应走 Hermes/OpenClaw runtime"); + assert(run.sessionId, "run payload 应包含 MNote sessionId"); + + const visibleAssistantTexts = await page.locator(".wolai-page-ai-message--assistant").allTextContents(); + const markerAssistantCount = visibleAssistantTexts.filter((text) => text.includes(marker)).length; + assert.strictEqual(markerAssistantCount, 1, "MNote 可见 provider 回复应只有一条"); + + const sessionId = String(run.sessionId); + const bindingRows = sqliteJson( + `SELECT mnote_session_id, remote_conversation_id, status FROM ai_external_conversation_bindings WHERE user_id=${sqlQuote(actorId)} AND mnote_session_id=${sqlQuote(sessionId)} AND provider=${sqlQuote(provider.expectedProvider)} ORDER BY updated_at DESC LIMIT 1;`, + [], + ); + assert(bindingRows && bindingRows.length === 1, "SQLite 应保存远端会话绑定"); + assert.strictEqual(bindingRows[0].status, "active", "删除前 binding 应为 active"); + const remoteConversationId = bindingRows[0].remote_conversation_id; + assert(remoteConversationId, "binding 应包含 remote_conversation_id"); + + providerCaptures.afterMessage = await captureProvider(remoteConversationId, `${providerKey}-after-message`); + + await page.locator('.wolai-page-ai-header-actions [data-page-ai-action="history"]').click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-panel="history"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await page.locator(`[data-page-ai-session-row="${sessionId}"]`).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + page.once("dialog", async (dialog) => { + await dialog.accept(); + }); + await page.locator(`[data-page-ai-session-delete="${sessionId}"]`).click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + (id) => !document.querySelector(`[data-page-ai-session-row="${CSS.escape(id)}"]`), + sessionId, + { timeout: UI_TIMEOUT_MS }, + ); + screenshots.afterDelete = await saveScreenshot(page, "after-delete"); + + assert(deleteResponses.length >= 1, "MNote 应发出历史会话 DELETE 请求"); + assert.strictEqual(deleteResponses.at(-1).status, 200, "MNote 历史会话 DELETE 应成功"); + const deleteBody = JSON.parse(deleteResponses.at(-1).body || "{}"); + assert.strictEqual( + deleteBody?.result?.providerConversationDelete?.status, + "remote_deleted", + "provider 删除应返回 remote_deleted", + ); + const providerDeleteMode = deleteBody?.result?.providerConversationDelete?.response?.result?.providerDeleteMode; + if (providerKey === "deepseek") { + assert.strictEqual( + providerDeleteMode, + "deepseek_chat_session_delete_api", + "DeepSeek 删除应明确走 chat_session_delete_api 并由网页复核", + ); + } else { + assert.strictEqual( + providerDeleteMode, + "gemini_conversation_menu", + "Gemini 删除应走网页对话菜单确认路径", + ); + } + + await page.waitForTimeout(1500); + providerCaptures.afterDelete = await captureProvider(remoteConversationId, `${providerKey}-after-delete`); + assert.strictEqual( + providerCaptures.afterDelete.conversationLinkCount, + 0, + "provider 删除后网页历史中不应继续存在本轮远端会话链接", + ); + + const deletedRows = sqliteJson( + `SELECT mnote_session_id, remote_conversation_id, status, metadata_json FROM ai_external_conversation_bindings WHERE user_id=${sqlQuote(actorId)} AND mnote_session_id=${sqlQuote(sessionId)} AND provider=${sqlQuote(provider.expectedProvider)} ORDER BY updated_at DESC LIMIT 1;`, + [], + ); + assert(deletedRows && deletedRows.length === 1, "删除后 binding 仍应可审计"); + assert.strictEqual(deletedRows[0].status, "remote_deleted", "删除后 binding 应标记 remote_deleted"); + + const providerLog = readLogSince(provider.gatewayLog, gatewayLogOffset); + let providerSendCount = countMatches(providerLog, provider.sendLogPattern); + if (providerKey === "gemini" && providerSendCount === 0) { + const fullProviderLog = readLogSince(provider.gatewayLog, 0); + providerSendCount = countMatches(fullProviderLog, new RegExp(escapeRegExp(marker), "g")); + } + assert.strictEqual(providerSendCount, 1, "provider 本轮只能收到一次发送动作"); + + fs.writeFileSync( + RESULT_PATH, + `${JSON.stringify({ + ok: true, + provider: providerKey, + marker, + sessionId, + remoteConversationId, + providerSendCount, + deleteResponse: deleteResponses.at(-1), + bindingBefore: bindingRows[0], + bindingAfter: deletedRows[0], + screenshots, + providerCaptures, + }, null, 2)}\n`, + "utf8", + ); + } catch (error) { + caughtError = error; + screenshots.failure = await saveScreenshot(page, "failure").catch(() => ""); + fs.writeFileSync( + RESULT_PATH, + `${JSON.stringify({ + ok: false, + provider: providerKey, + marker, + error: error instanceof Error ? error.stack || error.message : String(error), + runRequests, + deleteResponses, + screenshots, + gatewayLog: readLogSince(provider.gatewayLog, gatewayLogOffset).slice(-8000), + }, null, 2)}\n`, + "utf8", + ); + } finally { + await browser.close().catch(() => {}); + } + + if (caughtError) throw caughtError; + console.log(JSON.stringify(JSON.parse(fs.readFileSync(RESULT_PATH, "utf8")), null, 2)); +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task514-sidebar-dev-hot-reload-gating-smoke.js b/scripts/task514-sidebar-dev-hot-reload-gating-smoke.js new file mode 100644 index 00000000..26c87900 --- /dev/null +++ b/scripts/task514-sidebar-dev-hot-reload-gating-smoke.js @@ -0,0 +1,141 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const { spawn } = require("node:child_process"); +const net = require("node:net"); +const { chromium } = require("playwright"); + +const UI_TIMEOUT_MS = Number(process.env.MNOTE_UI_TIMEOUT_MS || 45000); + +function findFreePort() { + return new Promise((resolve, reject) => { + const server = net.createServer(); + server.listen(0, "127.0.0.1", () => { + const port = server.address().port; + server.close(() => resolve(port)); + }); + server.on("error", reject); + }); +} + +async function waitForGateway(baseUrl, timeoutMs = 120000) { + const deadline = Date.now() + timeoutMs; + let lastError = ""; + while (Date.now() < deadline) { + try { + const response = await fetch(`${baseUrl}/`, { + headers: { + "x-mnote-actor-id": "mnote-e2e", + "x-mnote-actor-type": "user", + }, + }); + if (response.status === 200 || response.status === 303) return; + lastError = `${response.status} ${await response.text().catch(() => "")}`; + } catch (error) { + lastError = error && error.message ? error.message : String(error); + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + throw new Error(`mnote-web 未就绪: ${lastError}`); +} + +function startGateway(port, devHot) { + const env = { + ...process.env, + MNOTE_WEB_BIND: `127.0.0.1:${port}`, + MNOTE_WEB_PUBLIC_BIND: `127.0.0.1:${port}`, + MNOTE_WEB_ALLOW_DEV_FIXTURES: "1", + MNOTE_WEB_LEGACY_NEXT_BASE_URL: "http://127.0.0.1:3100", + MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT: "1", + }; + delete env.MNOTE_WEB_DEV_HOT_RELOAD; + if (devHot) env.MNOTE_WEB_DEV_HOT_RELOAD = "1"; + const child = spawn("cargo", ["run", "-q", "-p", "mnote-web", "--bin", "mnote-web"], { + cwd: "/mnt/Data1T/mnote/rust", + env, + stdio: ["ignore", "pipe", "pipe"], + }); + let stderr = ""; + child.stderr.on("data", (chunk) => { + stderr += chunk.toString("utf8"); + }); + child.stdout.on("data", () => {}); + return { + child, + stderr: () => stderr, + }; +} + +async function stopGateway(gateway) { + if (!gateway || gateway.child.killed) return; + gateway.child.kill("SIGTERM"); + await new Promise((resolve) => setTimeout(resolve, 500)); + if (!gateway.child.killed) gateway.child.kill("SIGKILL"); +} + +async function verifyCase(devHot) { + const port = await findFreePort(); + const baseUrl = `http://127.0.0.1:${port}`; + const gateway = startGateway(port, devHot); + try { + await waitForGateway(baseUrl); + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext({ + extraHTTPHeaders: { + "x-mnote-actor-id": "mnote-e2e", + "x-mnote-actor-type": "user", + }, + }); + const page = await context.newPage(); + const hotReloadRequests = []; + const networkFailures = []; + const consoleErrors = []; + page.on("request", (request) => { + if (request.url().includes("/api/dev/hot-reload")) hotReloadRequests.push(request.url()); + }); + page.on("requestfailed", (request) => { + networkFailures.push({ url: request.url(), failure: request.failure()?.errorText || "" }); + }); + page.on("console", (message) => { + if (message.type() === "error") consoleErrors.push(message.text()); + }); + await page.goto(`${baseUrl}/`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + await page.locator('body[data-mnote-shell="workspace"]').waitFor({ state: "attached", timeout: UI_TIMEOUT_MS }); + await page.waitForTimeout(1800); + const html = await page.content(); + const shell = await page.locator("body").getAttribute("data-mnote-shell"); + const devHotAttr = await page.locator("html").getAttribute("data-mnote-dev-hot-reload").catch(() => null); + await browser.close(); + return { + devHot, + baseUrl, + shell, + scriptHasDevHot: html.includes("sidebar-tree-runtime.js?devHot="), + hotReloadRequestCount: hotReloadRequests.length, + hotReloadAttr: devHotAttr || "", + networkFailures, + consoleErrors, + }; + } finally { + await stopGateway(gateway); + } +} + +async function main() { + const normal = await verifyCase(false); + const hot = await verifyCase(true); + assert.equal(normal.shell, "workspace", JSON.stringify(normal)); + assert.equal(normal.scriptHasDevHot, false, JSON.stringify(normal)); + assert.equal(normal.hotReloadRequestCount, 0, JSON.stringify(normal)); + assert.equal(hot.shell, "workspace", JSON.stringify(hot)); + assert.equal(hot.scriptHasDevHot, true, JSON.stringify(hot)); + assert(hot.hotReloadRequestCount >= 1, JSON.stringify(hot)); + assert.equal(hot.hotReloadAttr, "enabled", JSON.stringify(hot)); + console.log(JSON.stringify({ ok: true, task: "task514-sidebar-dev-hot-reload-gating-smoke", normal, hot }, null, 2)); +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task515-onlyoffice-live-scope-http-smoke.js b/scripts/task515-onlyoffice-live-scope-http-smoke.js new file mode 100644 index 00000000..7ebfd1f1 --- /dev/null +++ b/scripts/task515-onlyoffice-live-scope-http-smoke.js @@ -0,0 +1,188 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); + +const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); + +async function requestJson(path, init = {}) { + const response = await fetch(`${BASE_URL}${path}`, { + ...init, + headers: { + "content-type": "application/json", + "x-mnote-actor-id": "user_1", + ...(init.headers || {}), + }, + }); + const text = await response.text(); + let payload = null; + try { + payload = text ? JSON.parse(text) : null; + } catch (_) { + payload = { raw: text }; + } + return { + status: response.status, + headers: Object.fromEntries(response.headers.entries()), + payload, + }; +} + +function callPayload(sessionId, args, toolName = "mnote.onlyoffice.sheet.set_value") { + return { + toolName, + workspaceId: "ws_demo", + documentId: "doc_1", + actorId: "user_1", + sessionId, + runId: `${sessionId}-run`, + toolCallId: `${sessionId}-call`, + traceId: `${sessionId}-trace`, + idempotencyKey: `${sessionId}-idem`, + dryRun: true, + capabilityScope: ["office.write"], + args, + }; +} + +async function main() { + const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`; + const bridgeSessionId = `mnote-oo-http-${suffix}`; + const bridgeToken = `token-${suffix}`; + const register = await requestJson("/api/onlyoffice/bridge/session", { + method: "POST", + body: JSON.stringify({ + sessionId: bridgeSessionId, + token: bridgeToken, + editorType: "cell", + documentId: "doc_http", + assetId: "asset_http_allowed", + fileType: "xlsx", + }), + }); + assert.equal(register.status, 200, JSON.stringify(register)); + assert.equal(register.payload.sessionId, bridgeSessionId); + + const implicit = await requestJson("/api/hermes/tools/mnote/call", { + method: "POST", + body: JSON.stringify(callPayload("sess_http_implicit", { + address: "A1", + value: "after", + })), + }); + assert.equal(implicit.status, 400, JSON.stringify(implicit)); + assert.equal(implicit.headers["x-error-code"], "mnote_onlyoffice_session_explicit_required"); + + const forbidden = await requestJson("/api/hermes/tools/mnote/call", { + method: "POST", + body: JSON.stringify(callPayload("sess_http_forbidden", { + onlyofficeSessionId: bridgeSessionId, + address: "A1", + value: "after", + aiAccessScope: { + permissionLevel: "read_write", + allowedResourceIds: ["asset_other"], + }, + })), + }); + assert.equal(forbidden.status, 403, JSON.stringify(forbidden)); + assert.equal(forbidden.headers["x-error-code"], "mnote_onlyoffice_resource_scope_forbidden"); + + const missingScope = await requestJson("/api/hermes/tools/mnote/call", { + method: "POST", + body: JSON.stringify(callPayload("sess_http_missing_scope", { + onlyofficeSessionId: bridgeSessionId, + address: "A1", + value: "after", + })), + }); + assert.equal(missingScope.status, 403, JSON.stringify(missingScope)); + assert.equal(missingScope.headers["x-error-code"], "mnote_onlyoffice_resource_scope_required"); + + const allowed = await requestJson("/api/hermes/tools/mnote/call", { + method: "POST", + body: JSON.stringify(callPayload("sess_http_allowed", { + onlyofficeSessionId: bridgeSessionId, + address: "A1", + value: "after", + aiAccessScope: { + permissionLevel: "read_write", + allowedResourceIds: ["asset_http_allowed"], + }, + })), + }); + assert.equal(allowed.status, 200, JSON.stringify(allowed)); + assert.equal(allowed.payload.result.schema, "mnote.onlyoffice.action_plan.v1"); + assert.equal(allowed.payload.result.sessionId, bridgeSessionId); + assert.equal(allowed.payload.result.action, "sheet.set_value"); + assert.equal(allowed.payload.audit.effect, "dry_run"); + + const currentImplicit = await requestJson("/api/hermes/tools/mnote/call", { + method: "POST", + body: JSON.stringify(callPayload("sess_http_current_implicit", { + aiAccessScope: { + permissionLevel: "read", + allowedResourceIds: ["asset_http_allowed"], + }, + }, "mnote.onlyoffice.session.current")), + }); + assert.equal(currentImplicit.status, 400, JSON.stringify(currentImplicit)); + assert.equal(currentImplicit.headers["x-error-code"], "mnote_onlyoffice_session_explicit_required"); + + const currentForbidden = await requestJson("/api/hermes/tools/mnote/call", { + method: "POST", + body: JSON.stringify(callPayload("sess_http_current_forbidden", { + onlyofficeSessionId: bridgeSessionId, + aiAccessScope: { + permissionLevel: "read", + allowedResourceIds: ["asset_other"], + }, + }, "mnote.onlyoffice.session.current")), + }); + assert.equal(currentForbidden.status, 403, JSON.stringify(currentForbidden)); + assert.equal(currentForbidden.headers["x-error-code"], "mnote_onlyoffice_resource_scope_forbidden"); + + const currentAllowed = await requestJson("/api/hermes/tools/mnote/call", { + method: "POST", + body: JSON.stringify(callPayload("sess_http_current_allowed", { + onlyofficeSessionId: bridgeSessionId, + aiAccessScope: { + permissionLevel: "read", + allowedResourceIds: ["asset_http_allowed"], + }, + }, "mnote.onlyoffice.session.current")), + }); + assert.equal(currentAllowed.status, 200, JSON.stringify(currentAllowed)); + assert.equal(currentAllowed.payload.result.schema, "mnote.onlyoffice.session.v1"); + assert.equal(currentAllowed.payload.result.session.sessionId, bridgeSessionId); + assert.equal(currentAllowed.payload.result.session.assetId, "asset_http_allowed"); + + console.log(JSON.stringify({ + ok: true, + task: "task515-onlyoffice-live-scope-http-smoke", + baseUrl: BASE_URL, + bridgeSessionId, + implicit: { status: implicit.status, code: implicit.headers["x-error-code"] }, + forbidden: { status: forbidden.status, code: forbidden.headers["x-error-code"] }, + missingScope: { status: missingScope.status, code: missingScope.headers["x-error-code"] }, + allowed: { + status: allowed.status, + schema: allowed.payload.result.schema, + action: allowed.payload.result.action, + audit: allowed.payload.audit.effect, + }, + currentImplicit: { status: currentImplicit.status, code: currentImplicit.headers["x-error-code"] }, + currentForbidden: { status: currentForbidden.status, code: currentForbidden.headers["x-error-code"] }, + currentAllowed: { + status: currentAllowed.status, + schema: currentAllowed.payload.result.schema, + sessionId: currentAllowed.payload.result.session.sessionId, + assetId: currentAllowed.payload.result.session.assetId, + }, + }, null, 2)); +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js b/scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js new file mode 100644 index 00000000..c0384550 --- /dev/null +++ b/scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js @@ -0,0 +1,194 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const { chromium } = require("playwright"); + +const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); +const UI_TIMEOUT_MS = Number(process.env.MNOTE_UI_TIMEOUT_MS || 45000); + +async function main() { + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext(); + const page = await context.newPage(); + const consoleErrors = []; + const networkFailures = []; + page.on("console", (message) => { + if (message.type() === "error") consoleErrors.push(message.text()); + }); + page.on("requestfailed", (request) => { + networkFailures.push({ url: request.url(), failure: request.failure()?.errorText || "" }); + }); + + await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + const result = await page.evaluate(async () => { + const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`; + const sessionA = `mnote-oo-browser-a-${suffix}`; + const sessionB = `mnote-oo-browser-b-${suffix}`; + const tokenA = `token-a-${suffix}`; + const tokenB = `token-b-${suffix}`; + + async function postJson(path, payload) { + const response = await fetch(path, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(payload), + }); + const body = await response.json().catch(() => null); + return { status: response.status, body }; + } + + async function getJson(path) { + const response = await fetch(path); + const body = response.status === 204 ? null : await response.json().catch(() => null); + return { status: response.status, body }; + } + + const registeredA = await postJson("/api/onlyoffice/bridge/session", { + sessionId: sessionA, + token: tokenA, + editorType: "word", + documentId: "doc_a", + assetId: "asset_a", + fileType: "docx", + docKey: "doc_key_a", + pageOrigin: location.origin, + }); + const registeredB = await postJson("/api/onlyoffice/bridge/session", { + sessionId: sessionB, + token: tokenB, + editorType: "word", + documentId: "doc_b", + assetId: "asset_b", + fileType: "docx", + docKey: "doc_key_b", + pageOrigin: location.origin, + }); + const wrongToken = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenB)}&timeoutMs=1`); + + const commandA = await postJson("/api/onlyoffice/bridge/commands", { + sessionId: sessionA, + token: tokenA, + action: "document.insert_text", + payload: { text: "A" }, + }); + const commandB = await postJson("/api/onlyoffice/bridge/commands", { + sessionId: sessionB, + token: tokenB, + action: "document.insert_text", + payload: { text: "B" }, + }); + + const nextA = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&timeoutMs=1000`); + const nextB = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionB)}&token=${encodeURIComponent(tokenB)}&timeoutMs=1000`); + + const postedA = await postJson("/api/onlyoffice/bridge/results", { + sessionId: sessionA, + token: tokenA, + id: nextA.body && nextA.body.id, + ok: true, + result: { text: "result-a" }, + }); + const postedB = await postJson("/api/onlyoffice/bridge/results", { + sessionId: sessionB, + token: tokenB, + id: nextB.body && nextB.body.id, + ok: true, + result: { text: "result-b" }, + }); + + const resultA = await getJson(`/api/onlyoffice/bridge/results?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&commandId=${encodeURIComponent(nextA.body && nextA.body.id)}&timeoutMs=1000`); + const resultB = await getJson(`/api/onlyoffice/bridge/results?sessionId=${encodeURIComponent(sessionB)}&token=${encodeURIComponent(tokenB)}&commandId=${encodeURIComponent(nextB.body && nextB.body.id)}&timeoutMs=1000`); + const sessions = await getJson("/api/onlyoffice/bridge/sessions"); + const currentBeforeClose = await getJson("/api/onlyoffice/bridge/session/current"); + const closeA = await postJson("/api/onlyoffice/bridge/session/close", { + sessionId: sessionA, + token: tokenA, + }); + const nextAAfterClose = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&timeoutMs=1`); + const sessionsAfterClose = await getJson("/api/onlyoffice/bridge/sessions"); + + return { + sessionA, + sessionB, + registeredA, + registeredB, + wrongToken, + commandA, + commandB, + nextA, + nextB, + postedA, + postedB, + resultA, + resultB, + sessions, + currentBeforeClose, + closeA, + nextAAfterClose, + sessionsAfterClose, + }; + }); + + assert.equal(result.registeredA.status, 200, JSON.stringify(result.registeredA)); + assert.equal(result.registeredB.status, 200, JSON.stringify(result.registeredB)); + assert.equal(result.registeredA.body.docKey, "doc_key_a"); + assert.equal(result.registeredB.body.docKey, "doc_key_b"); + assert.equal(result.registeredA.body.pageOrigin, BASE_URL); + assert.equal(result.registeredB.body.pageOrigin, BASE_URL); + assert.equal(result.wrongToken.status, 401, JSON.stringify(result.wrongToken)); + assert.equal(result.commandA.status, 200, JSON.stringify(result.commandA)); + assert.equal(result.commandB.status, 200, JSON.stringify(result.commandB)); + assert.equal(result.nextA.status, 200, JSON.stringify(result.nextA)); + assert.equal(result.nextB.status, 200, JSON.stringify(result.nextB)); + assert.equal(result.nextA.body.action, "document.insert_text"); + assert.equal(result.nextB.body.action, "document.insert_text"); + assert.equal(result.nextA.body.payload.text, "A"); + assert.equal(result.nextB.body.payload.text, "B"); + assert.notEqual(result.nextA.body.id, result.nextB.body.id); + assert.equal(result.postedA.status, 200, JSON.stringify(result.postedA)); + assert.equal(result.postedB.status, 200, JSON.stringify(result.postedB)); + assert.equal(result.resultA.status, 200, JSON.stringify(result.resultA)); + assert.equal(result.resultB.status, 200, JSON.stringify(result.resultB)); + assert.equal(result.resultA.body.result.text, "result-a"); + assert.equal(result.resultB.body.result.text, "result-b"); + const sessionARecord = result.sessions.body.sessions.find((session) => session.sessionId === result.sessionA); + const sessionBRecord = result.sessions.body.sessions.find((session) => session.sessionId === result.sessionB); + assert(sessionARecord, JSON.stringify(result.sessions)); + assert(sessionBRecord, JSON.stringify(result.sessions)); + assert.equal(sessionARecord.docKey, "doc_key_a"); + assert.equal(sessionBRecord.pageOrigin, BASE_URL); + assert.equal(result.currentBeforeClose.status, 200, JSON.stringify(result.currentBeforeClose)); + assert(result.currentBeforeClose.body.session, JSON.stringify(result.currentBeforeClose)); + assert.equal(result.closeA.status, 200, JSON.stringify(result.closeA)); + assert.equal(result.closeA.body.closed, true, JSON.stringify(result.closeA)); + assert.equal(result.nextAAfterClose.status, 401, JSON.stringify(result.nextAAfterClose)); + assert(!result.sessionsAfterClose.body.sessions.some((session) => session.sessionId === result.sessionA)); + assert(result.sessionsAfterClose.body.sessions.some((session) => session.sessionId === result.sessionB)); + + await browser.close(); + console.log(JSON.stringify({ + ok: true, + task: "task516-onlyoffice-bridge-multisession-browser-smoke", + baseUrl: BASE_URL, + sessionA: result.sessionA, + sessionB: result.sessionB, + wrongToken: { status: result.wrongToken.status, code: result.wrongToken.body && result.wrongToken.body.code }, + nextA: { id: result.nextA.body.id, text: result.nextA.body.payload.text }, + nextB: { id: result.nextB.body.id, text: result.nextB.body.payload.text }, + resultA: result.resultA.body.result, + resultB: result.resultB.body.result, + currentBeforeClose: { + status: result.currentBeforeClose.status, + sessionId: result.currentBeforeClose.body.session && result.currentBeforeClose.body.session.sessionId, + }, + closeA: result.closeA.body, + consoleErrors, + networkFailures, + }, null, 2)); +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js b/scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js new file mode 100644 index 00000000..68ed2e8e --- /dev/null +++ b/scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js @@ -0,0 +1,229 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const { spawn } = require("node:child_process"); +const net = require("node:net"); +const { chromium } = require("playwright"); + +const UI_TIMEOUT_MS = Number(process.env.MNOTE_UI_TIMEOUT_MS || 45000); + +function findFreePort() { + return new Promise((resolve, reject) => { + const server = net.createServer(); + server.listen(0, "127.0.0.1", () => { + const port = server.address().port; + server.close(() => resolve(port)); + }); + server.on("error", reject); + }); +} + +async function waitForGateway(baseUrl, timeoutMs = 120000) { + const deadline = Date.now() + timeoutMs; + let lastError = ""; + while (Date.now() < deadline) { + try { + const response = await fetch(`${baseUrl}/`); + if (response.status === 200 || response.status === 303) return; + lastError = `${response.status} ${await response.text().catch(() => "")}`; + } catch (error) { + lastError = error && error.message ? error.message : String(error); + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + throw new Error(`mnote-web 未就绪: ${lastError}`); +} + +function startGateway(port) { + const env = { + ...process.env, + MNOTE_WEB_BIND: `127.0.0.1:${port}`, + MNOTE_WEB_PUBLIC_BIND: `127.0.0.1:${port}`, + MNOTE_WEB_ALLOW_DEV_FIXTURES: "1", + MNOTE_WEB_LEGACY_NEXT_BASE_URL: "http://127.0.0.1:3100", + MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT: "1", + }; + const child = spawn("cargo", ["run", "-q", "-p", "mnote-web", "--bin", "mnote-web"], { + cwd: "/mnt/Data1T/mnote/rust", + env, + stdio: ["ignore", "pipe", "pipe"], + }); + let stderr = ""; + child.stderr.on("data", (chunk) => { + stderr += chunk.toString("utf8"); + }); + child.stdout.on("data", () => {}); + return { + child, + stderr: () => stderr, + }; +} + +async function stopGateway(gateway) { + if (!gateway || gateway.child.killed) return; + gateway.child.kill("SIGTERM"); + await new Promise((resolve) => setTimeout(resolve, 500)); + if (!gateway.child.killed) gateway.child.kill("SIGKILL"); +} + +async function requestJson(baseUrl, path, init = {}) { + const response = await fetch(`${baseUrl}${path}`, { + ...init, + headers: { + "content-type": "application/json", + ...(init.headers || {}), + }, + }); + const body = response.status === 204 ? null : await response.json().catch(() => null); + return { status: response.status, body }; +} + +async function enqueueCommand(baseUrl, sessionId, token, action, payload) { + const response = await requestJson(baseUrl, "/api/onlyoffice/bridge/commands", { + method: "POST", + body: JSON.stringify({ sessionId, token, action, payload }), + }); + assert.equal(response.status, 200, JSON.stringify(response)); + assert(response.body && response.body.command && response.body.command.id, JSON.stringify(response)); + return response.body.command.id; +} + +async function readResult(baseUrl, sessionId, token, commandId) { + const query = new URLSearchParams({ + sessionId, + token, + commandId, + timeoutMs: "5000", + }); + const response = await requestJson(baseUrl, `/api/onlyoffice/bridge/results?${query.toString()}`); + assert.equal(response.status, 200, JSON.stringify(response)); + assert.equal(response.body && response.body.ok, true, JSON.stringify(response)); + return response.body.result; +} + +async function waitForSession(baseUrl, sessionId, timeoutMs = 5000) { + const deadline = Date.now() + timeoutMs; + let lastResponse = null; + while (Date.now() < deadline) { + lastResponse = await requestJson(baseUrl, "/api/onlyoffice/bridge/sessions"); + if (lastResponse.status === 200 && lastResponse.body && Array.isArray(lastResponse.body.sessions)) { + const found = lastResponse.body.sessions.find((session) => session.sessionId === sessionId); + if (found) return { response: lastResponse, session: found }; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + throw new Error(`bridge session 未注册: ${JSON.stringify(lastResponse)}`); +} + +async function main() { + const port = await findFreePort(); + const baseUrl = `http://127.0.0.1:${port}`; + const gateway = startGateway(port); + const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`; + const sessionId = `mnote-oo-plugin-direct-${suffix}`; + const token = `token-${suffix}`; + const consoleErrors = []; + const networkFailures = []; + + try { + await waitForGateway(baseUrl); + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext(); + const page = await context.newPage(); + page.on("console", (message) => { + if (message.type() === "error") consoleErrors.push(message.text()); + }); + page.on("requestfailed", (request) => { + networkFailures.push({ url: request.url(), failure: request.failure()?.errorText || "" }); + }); + await page.route("**/onlyoffice-server/sdkjs-plugins/v1/plugins.js", async (route) => { + await route.fulfill({ + contentType: "application/javascript; charset=utf-8", + body: ` + window.__MNOTE_ONLYOFFICE_PLUGIN_MOCK__ = { executeCalls: [], commandCalls: [] }; + window.Asc = { + scope: {}, + plugin: { + info: { editorType: "word" }, + executeMethod(name, args, callback) { + window.__MNOTE_ONLYOFFICE_PLUGIN_MOCK__.executeCalls.push({ name, args }); + const value = name === "GetSelectedText" + ? "mock selected text" + : name === "PasteText" + ? true + : name === "ConvertDocument" + ? "# mock document" + : null; + callback(value); + }, + callCommand(func, _close, _calc, callback) { + window.__MNOTE_ONLYOFFICE_PLUGIN_MOCK__.commandCalls.push({ scope: window.Asc.scope.mnoteBridge || {} }); + callback(func()); + }, + init: null, + button: null + } + }; + `, + }); + }); + + const pluginUrl = new URL(`${baseUrl}/api/onlyoffice/bridge/plugin/index`); + pluginUrl.searchParams.set("sessionId", sessionId); + pluginUrl.searchParams.set("token", token); + pluginUrl.searchParams.set("apiBase", baseUrl); + pluginUrl.searchParams.set("documentId", "doc_plugin_direct"); + pluginUrl.searchParams.set("assetId", "asset_plugin_direct"); + pluginUrl.searchParams.set("fileType", "docx"); + pluginUrl.searchParams.set("docKey", "doc_key_plugin_direct"); + pluginUrl.searchParams.set("pageOrigin", baseUrl); + await page.goto(pluginUrl.toString(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + await page.evaluate(() => window.Asc.plugin.init()); + + const { session: registeredSession } = await waitForSession(baseUrl, sessionId); + assert.equal(registeredSession.docKey, "doc_key_plugin_direct", JSON.stringify(registeredSession)); + assert.equal(registeredSession.pageOrigin, baseUrl, JSON.stringify(registeredSession)); + + const wrongToken = await requestJson( + baseUrl, + `/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionId)}&token=wrong-${encodeURIComponent(token)}&timeoutMs=1`, + ); + assert.equal(wrongToken.status, 401, JSON.stringify(wrongToken)); + + const selectionCommandId = await enqueueCommand(baseUrl, sessionId, token, "selection.get", {}); + const selection = await readResult(baseUrl, sessionId, token, selectionCommandId); + assert.equal(selection.editorType, "word", JSON.stringify(selection)); + assert.equal(selection.text, "mock selected text", JSON.stringify(selection)); + + const insertCommandId = await enqueueCommand(baseUrl, sessionId, token, "document.insert_text", { text: "hello" }); + const inserted = await readResult(baseUrl, sessionId, token, insertCommandId); + assert.equal(inserted.value, true, JSON.stringify(inserted)); + + const mockState = await page.evaluate(() => window.__MNOTE_ONLYOFFICE_PLUGIN_MOCK__); + assert(mockState.executeCalls.some((call) => call.name === "GetSelectedText"), JSON.stringify(mockState)); + assert(mockState.executeCalls.some((call) => call.name === "PasteText"), JSON.stringify(mockState)); + + await browser.close(); + console.log(JSON.stringify({ + ok: true, + task: "task517-onlyoffice-bridge-plugin-direct-smoke", + baseUrl, + sessionId, + wrongToken: { status: wrongToken.status, code: wrongToken.body && wrongToken.body.code }, + registeredSession, + selection, + inserted, + executeCalls: mockState.executeCalls.map((call) => call.name), + consoleErrors, + networkFailures, + }, null, 2)); + } finally { + await stopGateway(gateway); + } +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js b/scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js new file mode 100644 index 00000000..815142f8 --- /dev/null +++ b/scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js @@ -0,0 +1,471 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { chromium } = require("playwright"); + +const TASK = "task518-onlyoffice-real-iframe-session-scope-smoke"; +const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); +const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 120_000); +const PROBE_DOCX_PATH = process.env.MNOTE_ONLYOFFICE_PROBE_DOCX + || "/tmp/mnote-onlyoffice-manual/mnote-onlyoffice-upload-test.docx"; +const OUT_DIR = path.join(process.cwd(), "tmp", TASK); +const SCREENSHOT_DIR = path.join(OUT_DIR, "screenshots"); +const RESULT_PATH = path.join(OUT_DIR, "result.json"); +const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH + || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium"] + .find((candidate) => fs.existsSync(candidate)); + +function fileUrl(localPath) { + return `file://${localPath}`; +} + +function localOfficeFileUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/api/local-folder/files/open`); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("path", relativePath); + return url.toString(); +} + +function onlyofficeUrl(root, relativePath, assetId) { + const url = new URL(`${BASE_URL}/onlyoffice`); + url.searchParams.set("fileUrl", localOfficeFileUrl(root, relativePath)); + url.searchParams.set("fileName", path.basename(relativePath)); + url.searchParams.set("fileType", "docx"); + url.searchParams.set("assetId", assetId); + url.searchParams.set("documentId", "local-md:Page~2FPage.md"); + url.searchParams.set("mode", "edit"); + return url.toString(); +} + +function bridgeToolPayload(sessionId, assetId, allowedResourceIds, toolName = "mnote.onlyoffice.document.insert_text", options = {}) { + return { + toolName, + workspaceId: "local-ws:task518", + documentId: "local-md:Page~2FPage.md", + actorId: "user_1", + sessionId: `task518-${Date.now()}-${Math.random().toString(36).slice(2)}`, + runId: "task518-run", + toolCallId: "task518-call", + traceId: "task518-trace", + idempotencyKey: `task518-${Date.now()}-${Math.random().toString(36).slice(2)}`, + dryRun: options.dryRun !== false, + capabilityScope: ["office.write"], + args: { + onlyofficeSessionId: sessionId, + text: options.text || "task518 dry run", + aiAccessScope: { + permissionLevel: "read_write", + allowedResourceIds, + }, + }, + }; +} + +async function quickLogin(page) { + await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" }); + if (await quickLoginButton.count()) { + await quickLoginButton.click({ timeout: UI_TIMEOUT_MS }); + await page.waitForURL((url) => url.pathname !== "/auth", { timeout: UI_TIMEOUT_MS }).catch(() => undefined); + } +} + +async function waitForOfficeReady(page, label) { + await page.waitForFunction( + () => { + const debug = window.__MNOTE_ONLYOFFICE_DEBUG__ || null; + const frameCount = document.querySelectorAll("#onlyoffice-frame iframe, #onlyoffice-frame canvas").length; + return Boolean(debug && debug.bridgeSessionId && debug.bridgeToken) + && Boolean(window.__MNOTE_ONLYOFFICE_EDITOR__) + && (Boolean(window.__MNOTE_ONLYOFFICE_READY__) || frameCount > 0); + }, + null, + { timeout: UI_TIMEOUT_MS }, + ); + await page.screenshot({ path: path.join(SCREENSHOT_DIR, `${label}.png`), fullPage: true }); + return await page.evaluate(() => ({ + url: location.href, + ready: Boolean(window.__MNOTE_ONLYOFFICE_READY__), + editorExists: Boolean(window.__MNOTE_ONLYOFFICE_EDITOR__), + frameCount: document.querySelectorAll("#onlyoffice-frame iframe, #onlyoffice-frame canvas").length, + errorVisible: document.getElementById("onlyoffice-error")?.getAttribute("data-visible") || "", + debug: window.__MNOTE_ONLYOFFICE_DEBUG__ || null, + errorLog: Array.isArray(window.__MNOTE_ONLYOFFICE_ERRLOG__) ? window.__MNOTE_ONLYOFFICE_ERRLOG__.slice() : [], + })); +} + +async function readBridgeSessions(context) { + const response = await context.request.fetch(`${BASE_URL}/api/onlyoffice/bridge/sessions`, { + method: "GET", + timeout: UI_TIMEOUT_MS, + }); + const body = await response.json().catch(async () => ({ raw: await response.text() })); + return { + status: response.status(), + body, + }; +} + +async function waitForBridgeSession(context, sessionId) { + const deadline = Date.now() + UI_TIMEOUT_MS; + let last = null; + while (Date.now() < deadline) { + last = await readBridgeSessions(context); + if ( + last.status === 200 + && Array.isArray(last.body?.sessions) + && last.body.sessions.some((session) => session.sessionId === sessionId) + ) { + return last; + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + throw new Error(`bridge session 未注册: ${sessionId}; last=${JSON.stringify(last)}`); +} + +async function runBridgeSelection(page) { + return await page.evaluate(async () => { + return await window.__MNOTE_ONLYOFFICE_BRIDGE__.run("selection.get", {}, 30_000); + }); +} + +async function runBridgeExport(page, format = "html") { + return await page.evaluate(async (selectedFormat) => { + return await window.__MNOTE_ONLYOFFICE_BRIDGE__.run("document.export", { format: selectedFormat }, 30_000); + }, format); +} + +async function postToolCall(context, payload) { + const response = await context.request.fetch(`${BASE_URL}/api/hermes/tools/mnote/call`, { + method: "POST", + headers: { + "content-type": "application/json", + "x-mnote-actor-id": "user_1", + }, + data: payload, + timeout: UI_TIMEOUT_MS, + }); + const body = await response.json().catch(async () => ({ raw: await response.text() })); + return { + status: response.status(), + headers: response.headers(), + body, + }; +} + +function classifyOnlyOfficeSignals(result, states, selections, scopeChecks) { + const bridgeTranslation404 = result.httpErrors.filter((entry) => ( + /\/api\/onlyoffice\/bridge\/plugin\/index\/translations\//.test(entry.url) + && entry.status === 404 + )); + const mnoteBridgePluginHttpErrors = result.httpErrors.filter((entry) => ( + /\/api\/onlyoffice\/bridge\/plugin\//.test(entry.url) + && !/\/api\/onlyoffice\/bridge\/plugin\/index\/translations\//.test(entry.url) + )); + const builtinPluginNoise = result.httpErrors.filter((entry) => ( + /\/sdkjs-plugins\/|custom.?assistant|annotation/i.test(entry.url) + )); + const bridgeConsoleNoise = result.consoleErrors.filter((entry) => ( + /\/api\/onlyoffice\/bridge\/plugin\/index\/translations\//.test(entry.text) + || /Failed to load resource: the server responded with a status of 404/i.test(entry.text) + )); + const errorCodeMinus18 = [ + ...result.consoleErrors.map((entry) => entry.text || ""), + ...states.flatMap((state) => Array.isArray(state.errorLog) ? state.errorLog.map((item) => JSON.stringify(item)) : []), + ].some((text) => /errorCode["'=:\s-]*-18|\b-18\b/.test(text)); + const mainDocumentFailures = [ + ...result.networkFailures.filter((entry) => ( + !/\/api\/onlyoffice\/bridge\/commands\/next/.test(entry.url) + )), + ...result.httpErrors.filter((entry) => { + if (/\/api\/onlyoffice\/bridge\/plugin\/index\/translations\//.test(entry.url)) return false; + if (/\/sdkjs-plugins\/|custom.?assistant|annotation/i.test(entry.url)) return false; + if (/\/api\/onlyoffice\/bridge\/plugin\//.test(entry.url)) return false; + if (/\/api\/onlyoffice\/bridge\/commands\/next/.test(entry.url)) return false; + return /\/onlyoffice-server\/|\/api\/onlyoffice\/proxy|\/api\/local-folder\/files\/open/.test(entry.url); + }), + ...states.filter((state) => state.errorVisible || !state.editorExists || !state.ready).map((state) => ({ + url: state.url, + ready: state.ready, + editorExists: state.editorExists, + errorVisible: state.errorVisible, + })), + ]; + return { + mnoteBridgePlugin: { + configUrls: states.map((state) => state.debug?.bridgePluginConfigUrl || ""), + httpErrors: mnoteBridgePluginHttpErrors, + sessionsRegistered: states.every((state) => ( + result.bridgeSessionsFinal?.body?.sessions || [] + ).some((session) => session.sessionId === state.debug?.bridgeSessionId)), + commandLoopOk: scopeChecks?.allowedBDryRun?.status === 200 + && scopeChecks?.allowedBWrite?.status === 200, + }, + bridgeTranslationNoise: { + count: bridgeTranslation404.length, + urls: Array.from(new Set(bridgeTranslation404.map((entry) => entry.url))), + }, + builtinPluginNoise: { + count: builtinPluginNoise.length, + urls: Array.from(new Set(builtinPluginNoise.map((entry) => entry.url))), + }, + mainDocument: { + ready: mainDocumentFailures.length === 0 && selections.every((selection) => selection?.editorType === "word"), + failures: mainDocumentFailures, + errorCodeMinus18, + }, + consoleNoise: { + bridgeTranslationOrGeneric404Count: bridgeConsoleNoise.length, + }, + }; +} + +async function main() { + assert(fs.existsSync(PROBE_DOCX_PATH), `缺少探测 docx: ${PROBE_DOCX_PATH}`); + fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); + + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task518-onlyoffice-")); + fs.mkdirSync(path.join(root, "Page"), { recursive: true }); + fs.writeFileSync(path.join(root, ".mnote-placeholder"), "task518\n", "utf8"); + fs.copyFileSync(PROBE_DOCX_PATH, path.join(root, "Page", "office-a.docx")); + fs.copyFileSync(PROBE_DOCX_PATH, path.join(root, "Page", "office-b.docx")); + + const consoleErrors = []; + const networkFailures = []; + const httpErrors = []; + const browser = await chromium.launch({ + headless: process.env.HEADFUL !== "1", + executablePath: CHROMIUM_EXECUTABLE_PATH, + }); + const context = await browser.newContext({ viewport: { width: 1440, height: 900 } }); + const authPage = await context.newPage(); + + const result = { + ok: false, + task: TASK, + baseUrl: BASE_URL, + root, + screenshots: [], + consoleErrors, + networkFailures, + httpErrors, + }; + + try { + await quickLogin(authPage); + await authPage.close(); + + const pageA = await context.newPage(); + const pageADuplicate = await context.newPage(); + const pageB = await context.newPage(); + for (const page of [pageA, pageADuplicate, pageB]) { + page.on("console", (message) => { + if (message.type() === "error") consoleErrors.push({ url: page.url(), text: message.text() }); + }); + page.on("requestfailed", (request) => { + networkFailures.push({ url: request.url(), failure: request.failure()?.errorText || "" }); + }); + page.on("response", (response) => { + if (response.status() >= 400) { + httpErrors.push({ url: response.url(), status: response.status() }); + } + }); + } + + const assetA = "local-file:Page/office-a.docx"; + const assetB = "local-file:Page/office-b.docx"; + await pageA.goto(onlyofficeUrl(root, "Page/office-a.docx", assetA), { + waitUntil: "domcontentloaded", + timeout: UI_TIMEOUT_MS, + }); + await pageADuplicate.goto(onlyofficeUrl(root, "Page/office-a.docx", assetA), { + waitUntil: "domcontentloaded", + timeout: UI_TIMEOUT_MS, + }); + await pageB.goto(onlyofficeUrl(root, "Page/office-b.docx", assetB), { + waitUntil: "domcontentloaded", + timeout: UI_TIMEOUT_MS, + }); + + const stateA = await waitForOfficeReady(pageA, "office-a"); + const stateADuplicate = await waitForOfficeReady(pageADuplicate, "office-a-duplicate"); + const stateB = await waitForOfficeReady(pageB, "office-b"); + result.screenshots.push(path.join(SCREENSHOT_DIR, "office-a.png")); + result.screenshots.push(path.join(SCREENSHOT_DIR, "office-a-duplicate.png")); + result.screenshots.push(path.join(SCREENSHOT_DIR, "office-b.png")); + assert.equal(stateA.errorVisible, "", `A 页面不应显示错误: ${JSON.stringify(stateA)}`); + assert.equal(stateADuplicate.errorVisible, "", `A duplicate 页面不应显示错误: ${JSON.stringify(stateADuplicate)}`); + assert.equal(stateB.errorVisible, "", `B 页面不应显示错误: ${JSON.stringify(stateB)}`); + assert(stateA.debug.bridgeSessionId !== stateB.debug.bridgeSessionId, `A/B sessionId 不应相同: ${JSON.stringify({ a: stateA.debug, b: stateB.debug })}`); + assert( + stateA.debug.bridgeSessionId !== stateADuplicate.debug.bridgeSessionId, + `同一 Office 文档双 tab sessionId 不应相同: ${JSON.stringify({ a: stateA.debug, duplicate: stateADuplicate.debug })}`, + ); + assert.equal(stateA.debug.docKey, stateADuplicate.debug.docKey, `同一 Office 文档双 tab 应共享 docKey 但使用不同 session salt: ${JSON.stringify({ a: stateA.debug, duplicate: stateADuplicate.debug })}`); + assert.equal(stateA.debug.assetId, assetA, `A debug assetId 不匹配: ${JSON.stringify(stateA.debug)}`); + assert.equal(stateADuplicate.debug.assetId, assetA, `A duplicate debug assetId 不匹配: ${JSON.stringify(stateADuplicate.debug)}`); + assert.equal(stateB.debug.assetId, assetB, `B debug assetId 不匹配: ${JSON.stringify(stateB.debug)}`); + + result.officeAInitial = stateA; + result.officeADuplicateInitial = stateADuplicate; + result.officeBInitial = stateB; + result.bridgeSessionsAfterReady = await readBridgeSessions(context); + await waitForBridgeSession(context, stateA.debug.bridgeSessionId); + await waitForBridgeSession(context, stateADuplicate.debug.bridgeSessionId); + await waitForBridgeSession(context, stateB.debug.bridgeSessionId); + result.bridgeSessionsFinal = await readBridgeSessions(context); + const selectionA = await runBridgeSelection(pageA); + const selectionADuplicate = await runBridgeSelection(pageADuplicate); + const selectionB = await runBridgeSelection(pageB); + assert.equal(selectionA.editorType, "word", `A selection 应来自 word editor: ${JSON.stringify(selectionA)}`); + assert.equal(selectionADuplicate.editorType, "word", `A duplicate selection 应来自 word editor: ${JSON.stringify(selectionADuplicate)}`); + assert.equal(selectionB.editorType, "word", `B selection 应来自 word editor: ${JSON.stringify(selectionB)}`); + + const forbiddenBFromScopeA = await postToolCall( + context, + bridgeToolPayload(stateB.debug.bridgeSessionId, assetB, [assetA, `resource:onlyoffice:${stateA.debug.documentId}:${assetA}`]), + ); + assert.equal(forbiddenBFromScopeA.status, 403, JSON.stringify(forbiddenBFromScopeA)); + assert.equal(forbiddenBFromScopeA.headers["x-error-code"], "mnote_onlyoffice_resource_scope_forbidden"); + + const allowedBDryRun = await postToolCall( + context, + bridgeToolPayload(stateB.debug.bridgeSessionId, assetB, [assetB, `resource:onlyoffice:${stateB.debug.documentId}:${assetB}`]), + ); + assert.equal(allowedBDryRun.status, 200, JSON.stringify(allowedBDryRun)); + assert.equal(allowedBDryRun.body.result.schema, "mnote.onlyoffice.action_plan.v1"); + assert.equal(allowedBDryRun.body.result.sessionId, stateB.debug.bridgeSessionId); + + const writeMarker = `task518-write-${Date.now()}-${Math.random().toString(36).slice(2)}`; + const allowedBWrite = await postToolCall( + context, + bridgeToolPayload( + stateB.debug.bridgeSessionId, + assetB, + [assetB, `resource:onlyoffice:${stateB.debug.documentId}:${assetB}`], + "mnote.onlyoffice.document.insert_text", + { dryRun: false, text: writeMarker }, + ), + ); + assert.equal(allowedBWrite.status, 200, JSON.stringify(allowedBWrite)); + assert.equal(allowedBWrite.body.result.schema, "mnote.onlyoffice.action_result.v1"); + assert.equal(allowedBWrite.body.result.sessionId, stateB.debug.bridgeSessionId); + await pageA.screenshot({ path: path.join(SCREENSHOT_DIR, "office-a-after-write.png"), fullPage: true }); + await pageB.screenshot({ path: path.join(SCREENSHOT_DIR, "office-b-after-write.png"), fullPage: true }); + result.screenshots.push(path.join(SCREENSHOT_DIR, "office-a-after-write.png")); + result.screenshots.push(path.join(SCREENSHOT_DIR, "office-b-after-write.png")); + + const exportAAfterWrite = await runBridgeExport(pageA, "html"); + const exportBAfterWrite = await runBridgeExport(pageB, "html"); + const exportAText = String(exportAAfterWrite?.content || exportAAfterWrite?.result?.content || ""); + const exportBText = String(exportBAfterWrite?.content || exportBAfterWrite?.result?.content || ""); + assert( + exportBText.includes(writeMarker), + `B 导出内容应包含写入标记 ${writeMarker}: ${JSON.stringify(exportBAfterWrite).slice(0, 1000)}`, + ); + assert( + !exportAText.includes(writeMarker), + `A 导出内容不应包含 B 写入标记 ${writeMarker}: ${JSON.stringify(exportAAfterWrite).slice(0, 1000)}`, + ); + + const scopeChecks = { + forbiddenBFromScopeA: { + status: forbiddenBFromScopeA.status, + code: forbiddenBFromScopeA.headers["x-error-code"], + }, + allowedBDryRun: { + status: allowedBDryRun.status, + schema: allowedBDryRun.body.result.schema, + sessionId: allowedBDryRun.body.result.sessionId, + }, + allowedBWrite: { + status: allowedBWrite.status, + schema: allowedBWrite.body.result.schema, + sessionId: allowedBWrite.body.result.sessionId, + commandId: allowedBWrite.body.result.commandId, + }, + exportAfterWrite: { + writeMarker, + aContainsMarker: exportAText.includes(writeMarker), + bContainsMarker: exportBText.includes(writeMarker), + }, + }; + result.bridgeSessionsFinal = await readBridgeSessions(context); + const errorClassification = classifyOnlyOfficeSignals( + result, + [stateA, stateADuplicate, stateB], + [selectionA, selectionADuplicate, selectionB], + scopeChecks, + ); + assert.equal( + errorClassification.mnoteBridgePlugin.httpErrors.length, + 0, + `MNote bridge 插件 config/index 失败不能归为普通噪音: ${JSON.stringify(errorClassification.mnoteBridgePlugin.httpErrors)}`, + ); + assert.equal( + errorClassification.mnoteBridgePlugin.sessionsRegistered, + true, + `MNote bridge 插件应完成 session 注册: ${JSON.stringify(errorClassification.mnoteBridgePlugin)}`, + ); + assert.equal( + errorClassification.mnoteBridgePlugin.commandLoopOk, + true, + `MNote bridge 插件 command loop 应可用: ${JSON.stringify(errorClassification.mnoteBridgePlugin)}`, + ); + assert.equal( + errorClassification.mainDocument.errorCodeMinus18, + false, + `errorCode=-18 必须作为主文档失败暴露: ${JSON.stringify(errorClassification.mainDocument)}`, + ); + assert.equal( + errorClassification.mainDocument.ready, + true, + `主文档失败不能被插件噪音吞掉: ${JSON.stringify(errorClassification.mainDocument)}`, + ); + + Object.assign(result, { + ok: true, + errorClassification, + officeA: { + sessionId: stateA.debug.bridgeSessionId, + assetId: stateA.debug.assetId, + docKey: stateA.debug.docKey, + frameCount: stateA.frameCount, + selection: selectionA, + }, + officeADuplicate: { + sessionId: stateADuplicate.debug.bridgeSessionId, + assetId: stateADuplicate.debug.assetId, + docKey: stateADuplicate.debug.docKey, + frameCount: stateADuplicate.frameCount, + selection: selectionADuplicate, + }, + officeB: { + sessionId: stateB.debug.bridgeSessionId, + assetId: stateB.debug.assetId, + docKey: stateB.debug.docKey, + frameCount: stateB.frameCount, + selection: selectionB, + }, + scopeChecks, + }); + fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + console.log(JSON.stringify(result, null, 2)); + } catch (error) { + result.error = error && error.stack ? error.stack : String(error); + fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + throw error; + } finally { + await context.close().catch(() => undefined); + await browser.close().catch(() => undefined); + } +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task520-page-ai-raw-resource-target-smoke.js b/scripts/task520-page-ai-raw-resource-target-smoke.js new file mode 100644 index 00000000..276b952b --- /dev/null +++ b/scripts/task520-page-ai-raw-resource-target-smoke.js @@ -0,0 +1,296 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { chromium } = require("playwright"); +const { + BASE_URL, + UI_TIMEOUT_MS, + ensureAuthenticated, +} = require("./tree-shell-smoke-helpers"); + +const TASK = "task520-page-ai-raw-resource-target-smoke"; +const OUT_DIR = path.join(process.cwd(), "tmp", TASK); +const RESULT_PATH = path.join(OUT_DIR, "result.json"); +const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH + || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium", "/usr/bin/chromium"] + .find((candidate) => fs.existsSync(candidate)); + +function fileUrl(localPath) { + return `file://${localPath}`; +} + +function localMdDocumentId(relativePath) { + return `local-md:${relativePath.replaceAll("/", "~2F")}`; +} + +function writeWorkspaceManifest(root, ownerId, workspaceId) { + fs.mkdirSync(path.join(root, ".mnote"), { recursive: true }); + fs.writeFileSync( + path.join(root, ".mnote", "workspace.json"), + `${JSON.stringify({ + workspaceId, + ownerId, + createdAt: new Date().toISOString(), + capabilities: ["local_files", "ai_sessions", "markdown_edit"], + }, null, 2)}\n`, + "utf8", + ); +} + +function documentUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`); + url.searchParams.set("sourceKind", "local_folder"); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("treeView", "filetree"); + return url.toString(); +} + +async function quickLogin(page, request) { + await ensureAuthenticated(page, request); +} + +async function waitFiletreeRow(page, relativePath) { + return await page.waitForFunction( + (expectedRelativePath) => Array.from(document.querySelectorAll("#sidebar-file-tree-root .tree-row[data-shell-mode=\"filetree\"]")) + .some((row) => row.getAttribute("data-local-relative-path") === expectedRelativePath), + relativePath, + { timeout: UI_TIMEOUT_MS }, + ); +} + +async function clickFiletreeOpen(page, relativePath) { + const handle = await page.waitForFunction( + (expectedRelativePath) => { + const row = Array.from(document.querySelectorAll("#sidebar-file-tree-root .tree-row[data-shell-mode=\"filetree\"]")) + .find((candidate) => candidate.getAttribute("data-local-relative-path") === expectedRelativePath); + return row?.querySelector('[data-rust-action="open"], .tree-link') || null; + }, + relativePath, + { timeout: UI_TIMEOUT_MS }, + ); + await handle.asElement().click(); +} + +async function expandFiletreeFolder(page, relativePath) { + await waitFiletreeRow(page, relativePath); + const expanded = await page.evaluate((expectedRelativePath) => { + const row = Array.from(document.querySelectorAll("#sidebar-file-tree-root .tree-row[data-shell-mode=\"filetree\"]")) + .find((candidate) => candidate.getAttribute("data-local-relative-path") === expectedRelativePath); + return row?.getAttribute("aria-expanded") === "true"; + }, relativePath); + if (!expanded) { + await clickFiletreeOpen(page, relativePath); + } + await page.waitForFunction( + (expectedRelativePath) => { + const row = Array.from(document.querySelectorAll("#sidebar-file-tree-root .tree-row[data-shell-mode=\"filetree\"]")) + .find((candidate) => candidate.getAttribute("data-local-relative-path") === expectedRelativePath); + return row?.getAttribute("aria-expanded") === "true"; + }, + relativePath, + { timeout: UI_TIMEOUT_MS }, + ); +} + +async function saveScreenshot(page, name) { + const target = path.join(OUT_DIR, `${name}.png`); + await page.screenshot({ path: target, fullPage: false }); + return target; +} + +async function main() { + fs.mkdirSync(OUT_DIR, { recursive: true }); + const actorId = "mnote-e2e"; + const workspaceId = `local-ws:${actorId}:task520`; + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task520-raw-target-")); + const rootUri = fileUrl(root); + const pagePath = "Page.md"; + const rawPath = "Page/notes.txt"; + const documentId = localMdDocumentId(pagePath); + const captured = []; + let caughtError = null; + + fs.mkdirSync(path.join(root, "Page"), { recursive: true }); + writeWorkspaceManifest(root, actorId, workspaceId); + fs.writeFileSync(path.join(root, pagePath), "# Page\n\nTask520 page\n", "utf8"); + fs.writeFileSync(path.join(root, rawPath), "Task520 raw resource target\n", "utf8"); + + const browser = await chromium.launch({ + headless: process.env.HEADFUL !== "1", + ...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}), + }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 960 }, + locale: "zh-CN", + extraHTTPHeaders: { + "x-mnote-actor-id": actorId, + "x-mnote-actor-type": "user", + }, + }); + const page = await context.newPage(); + + try { + await page.route("**/api/user/access-policy**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + grants: [{ + id: "grant_task520", + userId: actorId, + workspaceId, + rootUri, + rootPath: root, + permission: "write", + recursive: true, + capabilities: ["ai", "markdown_edit"], + source: "user", + status: "active", + }], + }), + }); + }); + await page.route("**/api/ui/preferences**", async (route) => { + captured.push({ kind: "ui-preferences", method: route.request().method(), body: route.request().postData() || "" }); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, owner: "mnote-web", result: {} }), + }); + }); + await page.route("**/api/hermes/client/gateway/health**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, gateway: { ok: true }, profile: { name: "reasonix", modelConfigured: true, apiKeyConfigured: true } }), + }); + }); + await page.route("**/api/hermes/client/tools**", async (route) => { + await route.fulfill({ status: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ ok: true, tools: [] }) }); + }); + await page.route("**/api/ai/agent-profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, agentId: "reasonix", profiles: [] }), + }); + }); + await page.route("**/api/hermes/client/profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, active: "mnoteai", profiles: [{ name: "mnoteai", label: "MNote AI", modelConfigured: true, apiKeyConfigured: true }] }), + }); + }); + await page.route("**/api/hermes/client/skills**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, categories: [], archived: [] }), + }); + }); + await page.route("**/api/hermes/client/sessions", async (route) => { + captured.push({ kind: "session", method: route.request().method(), body: route.request().postData() || "" }); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, sessionId: "mnote_task520", title: "task520", traceId: "trace_task520" }), + }); + }); + await page.route("**/api/hermes/client/runs", async (route) => { + captured.push({ kind: "run", method: route.request().method(), body: route.request().postData() || "" }); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, sessionId: "mnote_task520", runId: "run_task520", events: [], traceId: "trace_run_task520" }), + }); + }); + await page.route("**/api/hermes/client/events/*", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "text/event-stream; charset=utf-8" }, + body: `data: ${JSON.stringify({ event: "message.delta", run_id: "run_task520", delta: "Task520 response" })}\n\n` + + `data: ${JSON.stringify({ event: "run.completed", run_id: "run_task520", output: "Task520 response" })}\n\n`, + }); + }); + + await quickLogin(page, context.request); + const response = await page.goto(documentUrl(root, pagePath), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + assert(response && response.status() === 200, `文档页状态码异常: ${response && response.status()}`); + await page.locator(".document-pane[data-pane-role=\"primary\"] .editor-surface .ProseMirror").first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + await expandFiletreeFolder(page, "Page"); + await waitFiletreeRow(page, rawPath); + await clickFiletreeOpen(page, rawPath); + await page.waitForFunction( + (expectedPath) => { + const snapshot = window.__mnoteDocumentPaneRuntime?.getOpenEditorsSnapshot?.() || window.__mnoteOpenEditorsSnapshot || null; + return (snapshot?.resourceEditors || []).some((entry) => entry?.path === expectedPath && entry?.active === true); + }, + rawPath, + { timeout: UI_TIMEOUT_MS }, + ); + + await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-testid="wolai-page-ai-drawer"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const targetChip = page.locator("[data-page-ai-target-chip]"); + await targetChip.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const chipText = (await targetChip.innerText({ timeout: UI_TIMEOUT_MS })).trim(); + assert(chipText.includes("notes.txt"), `raw resource 打开后 target chip 应指向 notes.txt: ${chipText}`); + + await page.locator("[data-page-ai-input]").fill("Task520 raw target", { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => (document.querySelector('[data-testid="wolai-page-ai-drawer"]')?.textContent || "").includes("Task520 response"), + null, + { timeout: UI_TIMEOUT_MS }, + ); + + const runs = captured.filter((item) => item.kind === "run"); + assert(runs.length >= 1, "未捕获 Page AI run payload"); + const runBody = JSON.parse(runs[runs.length - 1].body || "{}"); + const activeEditorRef = runBody.contextRefs?.find((item) => item.kind === "active_editor"); + assert(activeEditorRef, `run payload 应包含 active_editor contextRef: ${JSON.stringify(runBody.contextRefs)}`); + assert.strictEqual(activeEditorRef.relativePath, rawPath, `active_editor 应指向 raw resource: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(activeEditorRef.resourceKind, "attachment", `raw resource contextRef 应保留 attachment resourceKind: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(activeEditorRef.objectIdentity, "resource:file:" + rootUri + ":" + rawPath, `active_editor objectIdentity 不应退化为 [object Object]: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(runBody.targetPackage?.schema, "mnote.agent_target_package.v1", "run payload 应携带 targetPackage"); + assert.strictEqual(runBody.targetPackage?.primaryTargetId, "resource:file:" + rootUri + ":" + rawPath, `targetPackage 应冻结 raw resource objectIdentity: ${JSON.stringify(runBody.targetPackage)}`); + assert.strictEqual(runBody.targetPackage?.objectIdentity, "resource:file:" + rootUri + ":" + rawPath, `targetPackage objectIdentity 不应退化为 [object Object]: ${JSON.stringify(runBody.targetPackage)}`); + assert.strictEqual(runBody.targetPackage?.resourceKind, "attachment", `targetPackage 应保留 raw resource kind: ${JSON.stringify(runBody.targetPackage)}`); + assert.strictEqual(runBody.targetPackage?.currentFile?.relativePath, rawPath, `targetPackage currentFile 应指向 raw resource: ${JSON.stringify(runBody.targetPackage)}`); + assert.strictEqual(runBody.targetPackage?.currentFile?.objectIdentity, "resource:file:" + rootUri + ":" + rawPath, `currentFile objectIdentity 不应退化为 [object Object]: ${JSON.stringify(runBody.targetPackage?.currentFile)}`); + assert(runBody.targetPackage?.allowedFiles?.includes(rawPath), `allowedFiles 应只包含 raw resource: ${JSON.stringify(runBody.targetPackage?.allowedFiles)}`); + + const screenshot = await saveScreenshot(page, "01-raw-resource-target"); + const result = { ok: true, task: TASK, baseUrl: BASE_URL, root, rootUri, documentId, rawPath, screenshot, captured }; + fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + console.log(JSON.stringify(result, null, 2)); + } catch (error) { + caughtError = error; + await saveScreenshot(page, "failure").catch(() => undefined); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + await page.close().catch(() => undefined); + await context.close().catch(() => undefined); + await browser.close().catch(() => undefined); + } + + if (caughtError) { + throw caughtError; + } +} + +if (require.main === module) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); +} diff --git a/scripts/task522-page-aggregate-compat-fallback-contract.js b/scripts/task522-page-aggregate-compat-fallback-contract.js new file mode 100644 index 00000000..e135835c --- /dev/null +++ b/scripts/task522-page-aggregate-compat-fallback-contract.js @@ -0,0 +1,99 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const vm = require("node:vm"); + +const RUNTIME_PATH = path.join( + process.cwd(), + "rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js", +); + +function loadConversionRuntime() { + const source = fs + .readFileSync(RUNTIME_PATH, "utf8") + .replace(/\bexport const\s+([A-Za-z0-9_]+)\s*=/g, "const $1 ="); + const sandbox = { console }; + vm.runInNewContext( + `${source}\n;globalThis.__mnoteExports = { pageBodyTiptapDocumentSource, pageBodyTiptapDocument };`, + sandbox, + { filename: RUNTIME_PATH }, + ); + return sandbox.__mnoteExports; +} + +function paragraphText(doc) { + return doc?.content?.[0]?.content?.[0]?.text || ""; +} + +const { + pageBodyTiptapDocumentSource, + pageBodyTiptapDocument, +} = loadConversionRuntime(); + +const blockDocument = { + type: "doc", + content: [{ + type: "paragraph", + attrs: { blockId: "block-truth" }, + content: [{ type: "text", text: "Block truth" }], + }], +}; + +const legacyContent = [{ + id: "legacy-1", + type: "paragraph", + content: [{ type: "text", text: "Legacy fallback" }], +}]; + +const localWithBlockAndLegacy = { + projectionSource: "local_markdown.content", + blockDocument, + content: legacyContent, +}; +assert.equal( + pageBodyTiptapDocumentSource(localWithBlockAndLegacy), + "page_aggregate.block_document", +); +assert.equal( + paragraphText(pageBodyTiptapDocument(localWithBlockAndLegacy)), + "Block truth", +); + +const localLegacyOnly = { + projectionSource: "local_markdown.content", + content: legacyContent, +}; +assert.equal( + pageBodyTiptapDocumentSource(localLegacyOnly), + "local_markdown.content", +); +assert.equal( + paragraphText(pageBodyTiptapDocument(localLegacyOnly)), + "Legacy fallback", +); + +const compatLegacyOnly = { + content: legacyContent, +}; +assert.equal( + pageBodyTiptapDocumentSource(compatLegacyOnly), + "compat.legacy_content", +); +assert.equal( + paragraphText(pageBodyTiptapDocument(compatLegacyOnly)), + "Legacy fallback", +); + +assert.equal( + pageBodyTiptapDocumentSource({}, "Fallback text"), + "degraded.fallback_text", +); +assert.equal( + paragraphText(pageBodyTiptapDocument({}, "Fallback text")), + "Fallback text", +); + +console.log("task522 page aggregate compat fallback contract passed"); diff --git a/scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js b/scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js new file mode 100644 index 00000000..285e088d --- /dev/null +++ b/scripts/task523-page-ai-onlyoffice-real-target-session-smoke.js @@ -0,0 +1,416 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { chromium } = require("playwright"); +const { + BASE_URL, + UI_TIMEOUT_MS, + ensureAuthenticated, +} = require("./tree-shell-smoke-helpers"); + +const TASK = "task523-page-ai-onlyoffice-real-target-session-smoke"; +const OUT_DIR = path.join(process.cwd(), "tmp", TASK); +const SCREENSHOT_DIR = path.join(OUT_DIR, "screenshots"); +const RESULT_PATH = path.join(OUT_DIR, "result.json"); +const PROBE_DOCX_PATH = process.env.MNOTE_ONLYOFFICE_PROBE_DOCX + || "/tmp/mnote-onlyoffice-manual/mnote-onlyoffice-upload-test.docx"; +const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH + || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium", "/usr/bin/chromium"] + .find((candidate) => fs.existsSync(candidate)); + +function fileUrl(localPath) { + return `file://${localPath}`; +} + +function localMdDocumentId(relativePath) { + return `local-md:${relativePath.replaceAll("/", "~2F")}`; +} + +function writeWorkspaceManifest(root, ownerId, workspaceId) { + fs.mkdirSync(path.join(root, ".mnote"), { recursive: true }); + fs.writeFileSync( + path.join(root, ".mnote", "workspace.json"), + `${JSON.stringify({ + workspaceId, + ownerId, + createdAt: new Date().toISOString(), + capabilities: ["local_files", "ai_sessions", "markdown_edit"], + }, null, 2)}\n`, + "utf8", + ); +} + +function documentUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`); + url.searchParams.set("sourceKind", "local_folder"); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("treeView", "filetree"); + return url.toString(); +} + +function localOfficeFileUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/api/local-folder/files/open`); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("path", relativePath); + return url.toString(); +} + +function onlyofficeUrl(root, pageRelativePath, officeRelativePath, assetId) { + const url = new URL(`${BASE_URL}/onlyoffice`); + url.searchParams.set("fileUrl", localOfficeFileUrl(root, officeRelativePath)); + url.searchParams.set("fileName", path.basename(officeRelativePath)); + url.searchParams.set("fileType", "docx"); + url.searchParams.set("assetId", assetId); + url.searchParams.set("documentId", localMdDocumentId(pageRelativePath)); + url.searchParams.set("mode", "edit"); + return url.toString(); +} + +async function saveScreenshot(page, name) { + const target = path.join(SCREENSHOT_DIR, `${name}.png`); + await page.screenshot({ path: target, fullPage: true }); + return target; +} + +async function waitForOfficeSnapshot(page, objectIdentity) { + return await page.waitForFunction( + (targetId) => { + const runtime = window.__mnoteDocumentPaneRuntime; + if (!runtime || typeof runtime.getOpenEditorsSnapshot !== "function") return null; + const snapshot = runtime.getOpenEditorsSnapshot(); + const resources = Array.isArray(snapshot && snapshot.resourceEditors) ? snapshot.resourceEditors : []; + const entry = resources.find((item) => item && item.objectIdentity === targetId); + if (!entry || !entry.bridgeSessionReady || !(entry.onlyofficeSessionId || entry.bridgeSessionId)) return null; + return entry; + }, + objectIdentity, + { timeout: UI_TIMEOUT_MS }, + ); +} + +async function main() { + assert(fs.existsSync(PROBE_DOCX_PATH), `缺少探测 docx: ${PROBE_DOCX_PATH}`); + fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); + + const suffix = Date.now().toString(36); + const actorId = "mnote-e2e"; + const workspaceId = `local-ws:${actorId}:task523`; + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task523-onlyoffice-target-")); + const rootUri = fileUrl(root); + const pageRelativePath = "Page/Page.md"; + const officeRelativePath = "Page/office-a.docx"; + const documentId = localMdDocumentId(pageRelativePath); + const assetId = `local-file:${officeRelativePath}`; + const objectIdentity = `resource:office:${documentId}:${assetId}`; + const captured = []; + const consoleErrors = []; + const networkFailures = []; + const httpErrors = []; + const result = { + ok: false, + task: TASK, + baseUrl: BASE_URL, + root, + documentId, + officeRelativePath, + objectIdentity, + screenshots: [], + consoleErrors, + networkFailures, + httpErrors, + }; + + fs.mkdirSync(path.join(root, "Page"), { recursive: true }); + writeWorkspaceManifest(root, actorId, workspaceId); + fs.writeFileSync( + path.join(root, pageRelativePath), + ["# Page", "", `Task523 ${suffix}`, ""].join("\n"), + "utf8", + ); + fs.copyFileSync(PROBE_DOCX_PATH, path.join(root, officeRelativePath)); + + const browser = await chromium.launch({ + headless: process.env.HEADFUL !== "1", + ...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}), + }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 960 }, + locale: "zh-CN", + extraHTTPHeaders: { + "x-mnote-actor-id": actorId, + "x-mnote-actor-type": "user", + }, + }); + const page = await context.newPage(); + + page.on("console", (message) => { + if (message.type() === "error") consoleErrors.push(message.text()); + }); + page.on("requestfailed", (request) => { + networkFailures.push({ url: request.url(), failure: request.failure()?.errorText || "" }); + }); + page.on("response", (response) => { + if (response.status() >= 400) { + httpErrors.push({ url: response.url(), status: response.status() }); + } + }); + + try { + await page.route("**/api/user/access-policy**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + controlPlane: "sqlite", + grants: [{ + id: `grant_task523_${suffix}`, + userId: actorId, + workspaceId, + rootUri, + rootPath: root, + permission: "write", + recursive: true, + capabilities: ["ai", "markdown_edit", "office.write"], + source: "user", + status: "active", + }], + }), + }); + }); + await page.route("**/api/ui/preferences**", async (route) => { + captured.push({ kind: "ui-preferences", method: route.request().method(), body: route.request().postData() || "" }); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, owner: "mnote-web", result: {} }), + }); + }); + await page.route("**/api/hermes/client/gateway/health**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + gateway: { ok: true, status: "mocked" }, + profile: { name: "reasonix", modelConfigured: true, apiKeyConfigured: true }, + suggestions: [], + }), + }); + }); + await page.route("**/api/hermes/client/tools**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, tools: [] }), + }); + }); + await page.route("**/api/ai/agent-profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + agentId: "reasonix", + profiles: [ + { profileId: "shared_doubao_chat", kind: "shared", displayName: "豆包 Chat", baseProfile: "doubao-chat", isolatedProfile: "openclaw-doubao-chat", canRun: true, canManageSkills: false, canManageConfig: false, readonly: true }, + { profileId: "usr_task523_default", kind: "personal", displayName: "我的 Hermes", baseProfile: "default", isolatedProfile: "mnote-u-task523-default", canRun: true, canManageSkills: true, canManageConfig: true }, + ], + }), + }); + }); + await page.route("**/api/hermes/client/profiles", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + active: "mnoteai", + profiles: [{ name: "mnoteai", label: "MNote AI", modelConfigured: true, apiKeyConfigured: true }], + }), + }); + }); + await page.route("**/api/hermes/client/skills**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, categories: [], archived: [] }), + }); + }); + await page.route("**/api/hermes/client/sessions", async (route) => { + captured.push({ kind: "session", method: route.request().method(), body: route.request().postData() || "" }); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + sessionId: `mnote_task523_${suffix}`, + title: "task523", + traceId: `trace_task523_session_${suffix}`, + persistence: "local_ai_session_jsonl", + sessionStorage: "local_private", + }), + }); + }); + await page.route("**/api/hermes/client/runs", async (route) => { + captured.push({ kind: "run", method: route.request().method(), body: route.request().postData() || "" }); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + sessionId: `mnote_task523_${suffix}`, + runId: `run_task523_${suffix}`, + events: [], + traceId: `trace_task523_run_${suffix}`, + }), + }); + }); + await page.route("**/api/hermes/client/events/*", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "text/event-stream; charset=utf-8" }, + body: + `data: ${JSON.stringify({ event: "message.delta", run_id: `run_task523_${suffix}`, delta: "Task523 response" })}\n\n` + + `data: ${JSON.stringify({ event: "run.completed", run_id: `run_task523_${suffix}`, output: "Task523 response" })}\n\n`, + }); + }); + + await ensureAuthenticated(page, context.request); + const response = await page.goto(documentUrl(root, pageRelativePath), { + waitUntil: "domcontentloaded", + timeout: UI_TIMEOUT_MS, + }); + assert(response && response.status() === 200, `文档页状态码异常: ${response && response.status()}`); + await page.locator('[data-testid="mnote-leptos-tiptap-island-editor-root"] .ProseMirror').first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + + const officeHref = onlyofficeUrl(root, pageRelativePath, officeRelativePath, assetId); + const openResult = await page.evaluate(async ({ objectIdentity, assetId, documentId, workspaceId, rootUri, officeRelativePath, officeHref }) => { + const runtime = window.__mnoteDocumentPaneRuntime; + if (!runtime || typeof runtime.openResourceInActiveTab !== "function") { + throw new Error("缺少 openResourceInActiveTab runtime"); + } + return await runtime.openResourceInActiveTab({ + objectIdentity, + assetId, + title: "Task523 Office", + fileName: "office-a.docx", + kind: "office", + editorKind: "office", + href: officeHref, + officeUrl: officeHref, + documentId, + workspaceId, + rootUri, + sourceKind: "local_folder", + path: officeRelativePath, + workspacePath: { + schema: "mnote.workspace_path.v1", + workspaceId, + sourceKind: "local_folder", + rootUri, + relativePath: officeRelativePath, + documentId, + objectIdentity, + assetId, + resourceKind: "only_office", + }, + paneRole: "primary", + }); + }, { objectIdentity, assetId, documentId, workspaceId, rootUri, officeRelativePath, officeHref }); + assert.strictEqual(openResult, true, "openResourceInActiveTab 应成功打开 Office resource tab"); + + const officeHandle = await waitForOfficeSnapshot(page, objectIdentity); + const officeSnapshot = await officeHandle.jsonValue(); + const onlyofficeSessionId = String(officeSnapshot.onlyofficeSessionId || officeSnapshot.bridgeSessionId || "").trim(); + assert(onlyofficeSessionId, `Office snapshot 必须携带 bridge session: ${JSON.stringify(officeSnapshot)}`); + assert.strictEqual(officeSnapshot.bridgeSessionReady, true, "Office snapshot 应标记 bridgeSessionReady"); + assert.strictEqual(officeSnapshot.assetId, assetId, `Office snapshot assetId 不匹配: ${JSON.stringify(officeSnapshot)}`); + assert.strictEqual(officeSnapshot.bridgeAssetId, assetId, `Office iframe debug assetId 应透传到 snapshot: ${JSON.stringify(officeSnapshot)}`); + result.officeSnapshot = officeSnapshot; + result.screenshots.push(await saveScreenshot(page, "00-office-resource-tab-ready")); + + await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-testid="wolai-page-ai-drawer"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + + const targetButton = page.locator("[data-page-ai-target-button]"); + await targetButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await targetButton.click({ timeout: UI_TIMEOUT_MS }); + const targetPopover = page.locator("[data-page-ai-target-popover]"); + await targetPopover.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await targetPopover.locator(`[data-page-ai-target-option="${objectIdentity}"]`).click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => (document.querySelector("[data-page-ai-target-chip]")?.textContent || "").includes("Task523 Office"), + null, + { timeout: UI_TIMEOUT_MS }, + ); + result.screenshots.push(await saveScreenshot(page, "01-page-ai-office-target-selected")); + + await page.locator("[data-page-ai-input]").fill(`Task523 OnlyOffice target ${suffix}`, { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => (document.querySelector('[data-testid="wolai-page-ai-drawer"]')?.textContent || "").includes("Task523 response"), + null, + { timeout: UI_TIMEOUT_MS }, + ); + + const runPayloads = captured.filter((item) => item.kind === "run"); + assert(runPayloads.length >= 1, "未捕获 Page AI run payload"); + const runBody = JSON.parse(runPayloads[runPayloads.length - 1].body); + const targetPackage = runBody.targetPackage || {}; + const packageTarget = Array.isArray(targetPackage.targets) + ? targetPackage.targets.find((target) => target.targetId === objectIdentity) + : null; + assert.strictEqual(runBody.editorTarget?.targetId, objectIdentity, `editorTarget 应冻结 Office target: ${JSON.stringify(runBody.editorTarget)}`); + assert.strictEqual(runBody.editorTarget?.resourceKind, "only_office", `editorTarget 应归一为 only_office: ${JSON.stringify(runBody.editorTarget)}`); + assert.strictEqual(runBody.editorTarget?.onlyofficeSessionId, onlyofficeSessionId, `editorTarget 应携带 iframe live session: ${JSON.stringify(runBody.editorTarget)}`); + assert.strictEqual(targetPackage.schema, "mnote.agent_target_package.v1", "targetPackage schema 不匹配"); + assert.strictEqual(targetPackage.primaryTargetId, objectIdentity, `targetPackage 应冻结 Office target: ${JSON.stringify(targetPackage)}`); + assert.strictEqual(targetPackage.onlyofficeSessionId, onlyofficeSessionId, `targetPackage 顶层应携带 live session: ${JSON.stringify(targetPackage)}`); + assert(packageTarget, `targetPackage.targets 应包含 Office target: ${JSON.stringify(targetPackage)}`); + assert.strictEqual(packageTarget.resourceKind, "only_office", `targetPackage target 应归一为 only_office: ${JSON.stringify(packageTarget)}`); + assert.strictEqual(packageTarget.relativePath, officeRelativePath, `targetPackage target 应携带 Office 相对路径: ${JSON.stringify(packageTarget)}`); + assert.strictEqual(packageTarget.assetId, assetId, `targetPackage target 应携带 assetId: ${JSON.stringify(packageTarget)}`); + assert.strictEqual(packageTarget.onlyofficeSessionId, onlyofficeSessionId, `targetPackage target 应携带 live session: ${JSON.stringify(packageTarget)}`); + assert( + Array.isArray(targetPackage.allowedFiles) && targetPackage.allowedFiles.includes(officeRelativePath), + `targetPackage.allowedFiles 应只按选中 Office target 授权: ${JSON.stringify(targetPackage)}`, + ); + assert.strictEqual( + httpErrors.filter((item) => item.url.includes("/api/documents/buffer-state")).length, + 0, + `Office target 不应触发 Markdown buffer-state 查询: ${JSON.stringify(httpErrors)}`, + ); + + Object.assign(result, { + ok: true, + onlyofficeSessionId, + runBody: { + agentId: runBody.agentId, + editorTarget: runBody.editorTarget, + targetPackage, + }, + }); + fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + console.log(JSON.stringify(result, null, 2)); + } catch (error) { + result.error = error && error.stack ? error.stack : String(error); + fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + throw error; + } finally { + await context.close().catch(() => undefined); + await browser.close().catch(() => undefined); + } +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task524-workspace-object-identity-matrix-smoke.js b/scripts/task524-workspace-object-identity-matrix-smoke.js new file mode 100644 index 00000000..cd4397c0 --- /dev/null +++ b/scripts/task524-workspace-object-identity-matrix-smoke.js @@ -0,0 +1,331 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { chromium } = require("playwright"); + +const BASE_URL = (process.env.MNOTE_UI_BASE_URL || process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); +const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000); +const PROBE_DOCX_PATH = process.env.MNOTE_ONLYOFFICE_PROBE_DOCX + || "/tmp/mnote-onlyoffice-manual/mnote-onlyoffice-upload-test.docx"; +const OUT_DIR = path.join(process.cwd(), "tmp", "task524-workspace-object-identity-matrix-smoke"); +const RESULT_PATH = path.join(OUT_DIR, "result.json"); +const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH + || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium", "/usr/bin/chromium"] + .find((candidate) => fs.existsSync(candidate)); + +function fileUrl(localPath) { + return `file://${localPath}`; +} + +function localMdDocumentId(relativePath) { + return `local-md:${relativePath.replaceAll("/", "~2F")}`; +} + +function documentUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`); + url.searchParams.set("sourceKind", "local_folder"); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("treeView", "filetree"); + return url.toString(); +} + +function writeWorkspaceManifest(root, ownerId, workspaceId) { + fs.mkdirSync(path.join(root, ".mnote"), { recursive: true }); + fs.writeFileSync( + path.join(root, ".mnote", "workspace.json"), + `${JSON.stringify({ + workspaceId, + ownerId, + createdAt: new Date().toISOString(), + capabilities: ["local_files", "markdown_edit", "asset_upload"], + }, null, 2)}\n`, + "utf8", + ); +} + +async function quickLogin(page) { + await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" }); + if (await quickLoginButton.count()) { + await quickLoginButton.click({ timeout: UI_TIMEOUT_MS }); + await page.waitForURL((url) => url.pathname !== "/auth", { timeout: UI_TIMEOUT_MS }).catch(() => undefined); + } +} + +async function filetreeRowState(page, relativePath) { + return await page.evaluate((targetRelativePath) => { + const row = Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]')) + .find((candidate) => candidate instanceof HTMLElement && candidate.getAttribute("data-local-relative-path") === targetRelativePath); + if (!(row instanceof HTMLElement)) { + return null; + } + const params = new URL(window.location.href).searchParams; + const workspacePath = window.__mnoteFileTreeRuntime?.readWorkspacePathFromRow?.(row, { + currentSourceKind: () => params.get("sourceKind") || document.body.dataset.mnoteSourceKind || "local_folder", + currentRootUri: () => params.get("rootUri") || document.body.dataset.mnoteRootUri || "", + resolveWorkspaceId: () => document.body.dataset.workspaceId || "", + rowTitle: (candidate) => candidate.textContent || "", + }) || null; + const objectIdentityRaw = row.getAttribute("data-object-identity") || ""; + let objectIdentity = null; + try { + objectIdentity = objectIdentityRaw ? JSON.parse(objectIdentityRaw) : null; + } catch { + objectIdentity = objectIdentityRaw; + } + return { + rowId: row.getAttribute("data-row-id") || "", + rowKind: row.getAttribute("data-row-kind") || "", + documentId: row.getAttribute("data-document-id") || "", + assetId: row.getAttribute("data-asset-id") || "", + relativePath: row.getAttribute("data-local-relative-path") || "", + objectIdentity, + workspacePath, + expanded: row.getAttribute("aria-expanded") || "", + }; + }, relativePath); +} + +async function clickFiletreeOpen(page, relativePath) { + const row = page.locator(`#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="${relativePath.replace(/"/g, '\\"')}"]`).first(); + await row.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await row.locator('[data-rust-action="open"]').click({ timeout: UI_TIMEOUT_MS }); +} + +async function waitFiletreeRow(page, relativePath) { + const row = page.locator(`#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="${relativePath.replace(/"/g, '\\"')}"]`).first(); + await row.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); +} + +async function expandFiletreeFolder(page, relativePath) { + const row = page.locator(`#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="${relativePath.replace(/"/g, '\\"')}"]`).first(); + await row.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const expanded = await row.getAttribute("aria-expanded"); + if (expanded === "true") return; + await row.locator('[data-rust-action="open"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + (targetRelativePath) => { + const candidate = Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]')) + .find((row) => row instanceof HTMLElement && row.getAttribute("data-local-relative-path") === targetRelativePath); + return candidate && candidate.getAttribute("aria-expanded") === "true"; + }, + relativePath, + { timeout: UI_TIMEOUT_MS }, + ); +} + +async function activeSnapshotEntry(page, expectedRelativePath, expectedKind) { + return await page.waitForFunction( + ({ expectedRelativePath, expectedKind }) => { + const snapshot = window.__mnoteOpenEditorsSnapshot || null; + const liveSnapshot = window.__mnoteDocumentPaneRuntime?.getOpenEditorsSnapshot?.() || snapshot; + const resources = Array.isArray(liveSnapshot?.resourceEditors) ? liveSnapshot.resourceEditors : []; + const entries = Array.isArray(liveSnapshot?.editors) ? liveSnapshot.editors.concat(resources) : resources; + const activeObjectIdentity = String(liveSnapshot?.activeObjectIdentity || ""); + const active = entries.find((entry) => + entry + && (entry.active === true || entry.objectIdentity === activeObjectIdentity) + && (!expectedRelativePath || entry.workspacePath?.relativePath === expectedRelativePath || entry.path === expectedRelativePath) + && (!expectedKind || entry.kind === expectedKind || entry.editorKind === expectedKind || entry.workspacePath?.resourceKind === expectedKind) + ); + return active || null; + }, + { expectedRelativePath, expectedKind }, + { timeout: UI_TIMEOUT_MS }, + ); +} + +function assertWorkspacePathMatchesRow(label, rowState, snapshotEntry, expected) { + assert(rowState, `${label}: 缺少 FileTree row state`); + assert(rowState.workspacePath, `${label}: FileTree row 缺少 workspacePath: ${JSON.stringify(rowState)}`); + assert(snapshotEntry, `${label}: 缺少 OpenEditorsSnapshot entry`); + assert.equal(rowState.workspacePath.schema, "mnote.workspace_path.v1", `${label}: row workspacePath schema`); + assert.equal(snapshotEntry.workspacePath?.schema, "mnote.workspace_path.v1", `${label}: snapshot workspacePath schema`); + assert.equal(rowState.workspacePath.relativePath, expected.relativePath, `${label}: row relativePath`); + assert.equal(snapshotEntry.workspacePath?.relativePath, expected.relativePath, `${label}: snapshot relativePath`); + assert.equal(snapshotEntry.workspacePath?.sourceKind, rowState.workspacePath.sourceKind, `${label}: sourceKind 应一致`); + assert.equal(snapshotEntry.workspacePath?.rootUri, rowState.workspacePath.rootUri, `${label}: rootUri 应一致`); + assert.equal(snapshotEntry.workspacePath?.documentId, rowState.workspacePath.documentId, `${label}: documentId 应一致`); + if (expected.assetId) { + assert.equal(rowState.workspacePath.assetId, expected.assetId, `${label}: row assetId`); + assert.equal(snapshotEntry.workspacePath?.assetId, expected.assetId, `${label}: snapshot assetId`); + } + if (expected.resourceKind) { + assert.equal(rowState.workspacePath.resourceKind, expected.resourceKind, `${label}: row resourceKind`); + assert.equal(snapshotEntry.workspacePath?.resourceKind, expected.resourceKind, `${label}: resourceKind`); + } + if (expected.objectKind) { + assert.equal(rowState.workspacePath.objectIdentity?.objectKind, expected.objectKind, `${label}: row objectKind`); + assert.equal(snapshotEntry.workspacePath?.objectIdentity?.objectKind, expected.objectKind, `${label}: snapshot objectKind`); + } + if (rowState.workspacePath.objectIdentity && typeof rowState.workspacePath.objectIdentity === "object" + && snapshotEntry.workspacePath?.objectIdentity && typeof snapshotEntry.workspacePath.objectIdentity === "object") { + assert.deepStrictEqual( + snapshotEntry.workspacePath.objectIdentity, + rowState.workspacePath.objectIdentity, + `${label}: snapshot objectIdentity 应与 row objectIdentity 对齐`, + ); + } +} + +async function main() { + fs.mkdirSync(OUT_DIR, { recursive: true }); + const actorId = "mnote-e2e"; + const workspaceId = `local-ws:${actorId}:task524`; + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task524-identity-")); + const rootUri = fileUrl(root); + fs.mkdirSync(path.join(root, "Page"), { recursive: true }); + fs.mkdirSync(path.join(root, "docs"), { recursive: true }); + writeWorkspaceManifest(root, actorId, workspaceId); + fs.writeFileSync(path.join(root, "Page.md"), "# Page\n\nTask524 page\n", "utf8"); + fs.writeFileSync(path.join(root, "docs", "Plan.md"), "# Plan\n\nTask524 plan\n", "utf8"); + fs.writeFileSync(path.join(root, "Page", "notes.txt"), "Task524 text resource\n", "utf8"); + fs.writeFileSync(path.join(root, "Page", "map.mindmap.json"), JSON.stringify({ + root: { data: { text: "Task524 Mindmap" }, children: [] }, + }, null, 2), "utf8"); + if (fs.existsSync(PROBE_DOCX_PATH)) { + fs.copyFileSync(PROBE_DOCX_PATH, path.join(root, "Page", "office.docx")); + } else { + fs.writeFileSync(path.join(root, "Page", "office.docx"), "task524 office probe\n", "utf8"); + } + + const browser = await chromium.launch({ + headless: process.env.HEADFUL !== "1", + ...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}), + }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 960 }, + extraHTTPHeaders: { + "x-mnote-actor-id": actorId, + "x-mnote-actor-type": "user", + }, + }); + const page = await context.newPage(); + const result = { + ok: false, + task: "task524-workspace-object-identity-matrix-smoke", + baseUrl: BASE_URL, + root, + rootUri, + checks: [], + }; + + try { + await quickLogin(page); + await page.goto(documentUrl(root, "Page.md"), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + await page.locator(".document-pane[data-pane-role=\"primary\"] .editor-surface .ProseMirror").first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + await page.waitForFunction(() => Boolean(window.__mnoteFileTreeRuntime?.readWorkspacePathFromRow), null, { + timeout: UI_TIMEOUT_MS, + }); + await page.waitForFunction(() => Boolean(window.__mnoteOpenEditorsSnapshot), null, { + timeout: UI_TIMEOUT_MS, + }); + await page.evaluate(() => { + window.__task524IdentityEvents = []; + window.addEventListener("tree.filetree.open", (event) => { + window.__task524IdentityEvents.push({ type: "tree.filetree.open", detail: event.detail || null }); + }); + window.addEventListener("tree.asset.open", (event) => { + window.__task524IdentityEvents.push({ type: "tree.asset.open", detail: event.detail || null }); + }); + }); + + const pageRow = await filetreeRowState(page, "Page.md"); + const pageEntry = await page.evaluate(() => { + const snapshot = window.__mnoteDocumentPaneRuntime?.getOpenEditorsSnapshot?.() || window.__mnoteOpenEditorsSnapshot || null; + return snapshot?.groups?.primary?.editors?.find((entry) => entry.kind === "page" && entry.documentId === "local-md:Page.md") || null; + }); + assertWorkspacePathMatchesRow("page", pageRow, pageEntry, { + relativePath: "Page.md", + resourceKind: "page", + objectKind: "page", + }); + result.checks.push({ kind: "page", row: pageRow, snapshot: pageEntry }); + + const folderRow = await filetreeRowState(page, "docs"); + assert(folderRow, "folder: 缺少 docs row"); + assert.equal(folderRow.workspacePath?.schema, "mnote.workspace_path.v1", "folder row workspacePath schema"); + assert.equal(folderRow.workspacePath?.relativePath, "docs", "folder row relativePath"); + assert.equal(folderRow.workspacePath?.documentId, "local-dir:docs", "folder row documentId"); + assert.equal(folderRow.workspacePath?.objectIdentity?.objectKind, "index", "folder row objectKind"); + const beforeFolderUrl = page.url(); + await clickFiletreeOpen(page, "docs"); + await page.waitForFunction(() => { + const row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="docs"]'); + return row && row.getAttribute("aria-expanded") === "true"; + }, null, { timeout: UI_TIMEOUT_MS }); + assert.equal(page.url(), beforeFolderUrl, "FileTree folder open 只应展开,不应改 URL"); + result.checks.push({ kind: "folder", row: folderRow }); + + await expandFiletreeFolder(page, "Page"); + + const resources = [ + { label: "text", relativePath: "Page/notes.txt", kind: "text", resourceKind: "attachment", objectKind: "attachment" }, + { label: "mindmap", relativePath: "Page/map.mindmap.json", kind: "mindmap", resourceKind: "mindmap", objectKind: "mindmap" }, + { label: "office", relativePath: "Page/office.docx", kind: "office", resourceKind: "only_office", objectKind: "only_office" }, + ]; + + for (const resource of resources) { + await waitFiletreeRow(page, resource.relativePath); + const rowState = await filetreeRowState(page, resource.relativePath); + assert(rowState, `${resource.label}: 缺少 FileTree row`); + await clickFiletreeOpen(page, resource.relativePath); + const entryHandle = await activeSnapshotEntry(page, resource.relativePath, resource.kind); + const entry = await entryHandle.jsonValue(); + assertWorkspacePathMatchesRow(resource.label, rowState, entry, { + relativePath: resource.relativePath, + assetId: rowState.workspacePath.assetId, + resourceKind: resource.resourceKind, + objectKind: resource.objectKind, + }); + assert.equal(entry.kind, resource.kind, `${resource.label}: editor kind`); + const eventDetail = await page.evaluate((relativePath) => { + const events = Array.isArray(window.__task524IdentityEvents) ? window.__task524IdentityEvents : []; + return [...events].reverse().find((event) => event?.detail?.workspacePath?.relativePath === relativePath) || null; + }, resource.relativePath); + assert(eventDetail, `${resource.label}: 应捕获真实 tree.asset.open 事件`); + assert.equal(eventDetail.detail.workspacePath?.sourceKind, rowState.workspacePath.sourceKind, `${resource.label}: event sourceKind`); + assert.equal(eventDetail.detail.workspacePath?.rootUri, rowState.workspacePath.rootUri, `${resource.label}: event rootUri`); + assert.equal(eventDetail.detail.workspacePath?.documentId, rowState.workspacePath.documentId, `${resource.label}: event documentId`); + assert.equal(eventDetail.detail.workspacePath?.assetId, rowState.workspacePath.assetId, `${resource.label}: event assetId`); + assert.deepStrictEqual(eventDetail.detail.workspacePath?.objectIdentity, rowState.workspacePath.objectIdentity, `${resource.label}: event objectIdentity`); + const url = new URL(page.url()); + const resourceTab = url.searchParams.get("resourceTab") || ""; + assert( + resourceTab === `primary::${entry.objectIdentity}`, + `${resource.label}: URL resourceTab 应等于 active snapshot objectIdentity: ${JSON.stringify({ resourceTab, entry })}`, + ); + const activeTabIdentity = await page.evaluate(() => { + const activeTab = document.querySelector(".mnote-main-tab.is-active"); + return activeTab?.getAttribute("data-mnote-object-identity") || ""; + }); + if (activeTabIdentity) { + assert.equal(activeTabIdentity, entry.objectIdentity, `${resource.label}: active tab identity`); + } + result.checks.push({ kind: resource.label, row: rowState, snapshot: entry, resourceTab, activeTabIdentity }); + } + + result.ok = true; + fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + console.log(JSON.stringify(result, null, 2)); + } catch (error) { + result.error = error && error.stack ? error.stack : String(error); + fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + throw error; + } finally { + await context.close().catch(() => undefined); + await browser.close().catch(() => undefined); + } +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +}); diff --git a/scripts/task525-page-ai-mindmap-resource-target-smoke.js b/scripts/task525-page-ai-mindmap-resource-target-smoke.js new file mode 100644 index 00000000..e713f933 --- /dev/null +++ b/scripts/task525-page-ai-mindmap-resource-target-smoke.js @@ -0,0 +1,299 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { chromium } = require("playwright"); +const { + BASE_URL, + UI_TIMEOUT_MS, + ensureAuthenticated, +} = require("./tree-shell-smoke-helpers"); + +const TASK = "task525-page-ai-mindmap-resource-target-smoke"; +const OUT_DIR = path.join(process.cwd(), "tmp", TASK); +const RESULT_PATH = path.join(OUT_DIR, "result.json"); +const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH + || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium", "/usr/bin/chromium"] + .find((candidate) => fs.existsSync(candidate)); + +function fileUrl(localPath) { + return `file://${localPath}`; +} + +function localMdDocumentId(relativePath) { + return `local-md:${relativePath.replaceAll("/", "~2F")}`; +} + +function writeWorkspaceManifest(root, ownerId, workspaceId) { + fs.mkdirSync(path.join(root, ".mnote"), { recursive: true }); + fs.writeFileSync( + path.join(root, ".mnote", "workspace.json"), + `${JSON.stringify({ + workspaceId, + ownerId, + createdAt: new Date().toISOString(), + capabilities: ["local_files", "ai_sessions", "markdown_edit"], + }, null, 2)}\n`, + "utf8", + ); +} + +function documentUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`); + url.searchParams.set("sourceKind", "local_folder"); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("treeView", "filetree"); + return url.toString(); +} + +async function waitFiletreeRow(page, relativePath) { + await page.waitForFunction( + (expectedRelativePath) => Array.from(document.querySelectorAll("#sidebar-file-tree-root .tree-row[data-shell-mode=\"filetree\"]")) + .some((row) => row.getAttribute("data-local-relative-path") === expectedRelativePath), + relativePath, + { timeout: UI_TIMEOUT_MS }, + ); +} + +async function clickFiletreeOpen(page, relativePath) { + const handle = await page.waitForFunction( + (expectedRelativePath) => { + const row = Array.from(document.querySelectorAll("#sidebar-file-tree-root .tree-row[data-shell-mode=\"filetree\"]")) + .find((candidate) => candidate.getAttribute("data-local-relative-path") === expectedRelativePath); + return row?.querySelector('[data-rust-action="open"], .tree-link') || null; + }, + relativePath, + { timeout: UI_TIMEOUT_MS }, + ); + await handle.asElement().click(); +} + +async function expandFiletreeFolder(page, relativePath) { + await waitFiletreeRow(page, relativePath); + const expanded = await page.evaluate((expectedRelativePath) => { + const row = Array.from(document.querySelectorAll("#sidebar-file-tree-root .tree-row[data-shell-mode=\"filetree\"]")) + .find((candidate) => candidate.getAttribute("data-local-relative-path") === expectedRelativePath); + return row?.getAttribute("aria-expanded") === "true"; + }, relativePath); + if (!expanded) { + await clickFiletreeOpen(page, relativePath); + } + await page.waitForFunction( + (expectedRelativePath) => { + const row = Array.from(document.querySelectorAll("#sidebar-file-tree-root .tree-row[data-shell-mode=\"filetree\"]")) + .find((candidate) => candidate.getAttribute("data-local-relative-path") === expectedRelativePath); + return row?.getAttribute("aria-expanded") === "true"; + }, + relativePath, + { timeout: UI_TIMEOUT_MS }, + ); +} + +async function saveScreenshot(page, name) { + const target = path.join(OUT_DIR, `${name}.png`); + await page.screenshot({ path: target, fullPage: false }); + return target; +} + +async function main() { + fs.mkdirSync(OUT_DIR, { recursive: true }); + const actorId = "mnote-e2e"; + const workspaceId = `local-ws:${actorId}:task525`; + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task525-mindmap-target-")); + const rootUri = fileUrl(root); + const pagePath = "Page/Page.md"; + const mindmapPath = "Page/map.mindmap.json"; + const documentId = localMdDocumentId(pagePath); + const assetId = `local-file:${mindmapPath}`; + const expectedTargetId = `resource:mindmap:${documentId}:${assetId}`; + const captured = []; + let caughtError = null; + + fs.mkdirSync(path.join(root, "Page"), { recursive: true }); + writeWorkspaceManifest(root, actorId, workspaceId); + fs.writeFileSync(path.join(root, pagePath), "# Page\n\n[思维导图](map.mindmap.json)\n", "utf8"); + fs.writeFileSync( + path.join(root, mindmapPath), + `${JSON.stringify({ data: { text: "Task525 mindmap" }, children: [] }, null, 2)}\n`, + "utf8", + ); + + const browser = await chromium.launch({ + headless: process.env.HEADFUL !== "1", + ...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}), + }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 960 }, + locale: "zh-CN", + extraHTTPHeaders: { + "x-mnote-actor-id": actorId, + "x-mnote-actor-type": "user", + }, + }); + const page = await context.newPage(); + + try { + await page.route("**/api/user/access-policy**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + ok: true, + grants: [{ + id: "grant_task525", + userId: actorId, + workspaceId, + rootUri, + rootPath: root, + permission: "write", + recursive: true, + capabilities: ["ai", "markdown_edit"], + source: "user", + status: "active", + }], + }), + }); + }); + await page.route("**/api/ui/preferences**", async (route) => { + captured.push({ kind: "ui-preferences", method: route.request().method(), body: route.request().postData() || "" }); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, owner: "mnote-web", result: {} }), + }); + }); + await page.route("**/api/hermes/client/gateway/health**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, gateway: { ok: true }, profile: { name: "reasonix", modelConfigured: true, apiKeyConfigured: true } }), + }); + }); + await page.route("**/api/hermes/client/tools**", async (route) => { + await route.fulfill({ status: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ ok: true, tools: [] }) }); + }); + await page.route("**/api/ai/agent-profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, agentId: "reasonix", profiles: [] }), + }); + }); + await page.route("**/api/hermes/client/profiles**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, active: "mnoteai", profiles: [{ name: "mnoteai", label: "MNote AI", modelConfigured: true, apiKeyConfigured: true }] }), + }); + }); + await page.route("**/api/hermes/client/skills**", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, categories: [], archived: [] }), + }); + }); + await page.route("**/api/hermes/client/sessions", async (route) => { + captured.push({ kind: "session", method: route.request().method(), body: route.request().postData() || "" }); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, sessionId: "mnote_task525", title: "task525", traceId: "trace_task525" }), + }); + }); + await page.route("**/api/hermes/client/runs", async (route) => { + captured.push({ kind: "run", method: route.request().method(), body: route.request().postData() || "" }); + await route.fulfill({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ok: true, sessionId: "mnote_task525", runId: "run_task525", events: [], traceId: "trace_run_task525" }), + }); + }); + await page.route("**/api/hermes/client/events/*", async (route) => { + await route.fulfill({ + status: 200, + headers: { "content-type": "text/event-stream; charset=utf-8" }, + body: `data: ${JSON.stringify({ event: "message.delta", run_id: "run_task525", delta: "Task525 response" })}\n\n` + + `data: ${JSON.stringify({ event: "run.completed", run_id: "run_task525", output: "Task525 response" })}\n\n`, + }); + }); + + await ensureAuthenticated(page, context.request); + const response = await page.goto(documentUrl(root, pagePath), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + assert(response && response.status() === 200, `文档页状态码异常: ${response && response.status()}`); + await page.locator(".document-pane[data-pane-role=\"primary\"] .editor-surface .ProseMirror").first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + await expandFiletreeFolder(page, "Page"); + await waitFiletreeRow(page, mindmapPath); + await clickFiletreeOpen(page, mindmapPath); + await page.waitForFunction( + () => { + return Boolean(document.querySelector('[data-testid="mnote-mindmap-editor-root"]')) + && (document.body?.innerText || "").includes("map.mindmap.json"); + }, + null, + { timeout: UI_TIMEOUT_MS }, + ); + + await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS }); + await page.locator('[data-testid="wolai-page-ai-drawer"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const targetChip = page.locator("[data-page-ai-target-chip]"); + await targetChip.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const chipText = (await targetChip.innerText({ timeout: UI_TIMEOUT_MS })).trim(); + assert(chipText.includes("map.mindmap.json"), `mindmap resource 打开后 target chip 应指向 map.mindmap.json: ${chipText}`); + + await page.locator("[data-page-ai-input]").fill("Task525 mindmap target", { timeout: UI_TIMEOUT_MS }); + await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS }); + await page.waitForFunction( + () => (document.querySelector('[data-testid="wolai-page-ai-drawer"]')?.textContent || "").includes("Task525 response"), + null, + { timeout: UI_TIMEOUT_MS }, + ); + + const runs = captured.filter((item) => item.kind === "run"); + assert(runs.length >= 1, "未捕获 Page AI run payload"); + const runBody = JSON.parse(runs[runs.length - 1].body || "{}"); + const activeEditorRef = runBody.contextRefs?.find((item) => item.kind === "active_editor"); + assert(activeEditorRef, `run payload 应包含 active_editor contextRef: ${JSON.stringify(runBody.contextRefs)}`); + assert.strictEqual(activeEditorRef.resourceKind, "mindmap", `active_editor 应标记 mindmap resourceKind: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(activeEditorRef.assetId, assetId, `active_editor 应携带 mindmap assetId: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(activeEditorRef.objectIdentity, expectedTargetId, `active_editor objectIdentity 不应退化: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(activeEditorRef.relativePath, mindmapPath, `active_editor 应携带 mindmap relativePath: ${JSON.stringify(activeEditorRef)}`); + assert.strictEqual(runBody.targetPackage?.schema, "mnote.agent_target_package.v1", "run payload 应携带 targetPackage"); + assert.strictEqual(runBody.targetPackage?.primaryTargetId, expectedTargetId, `targetPackage 应冻结 mindmap target: ${JSON.stringify(runBody.targetPackage)}`); + assert.strictEqual(runBody.targetPackage?.objectIdentity, expectedTargetId, `targetPackage objectIdentity 不应退化: ${JSON.stringify(runBody.targetPackage)}`); + assert.strictEqual(runBody.targetPackage?.resourceKind, "mindmap", `targetPackage 应保留 mindmap resourceKind: ${JSON.stringify(runBody.targetPackage)}`); + assert.strictEqual(runBody.targetPackage?.currentFile?.relativePath, mindmapPath, `targetPackage currentFile 应指向 mindmap resource: ${JSON.stringify(runBody.targetPackage)}`); + assert.strictEqual(runBody.targetPackage?.currentFile?.objectIdentity, expectedTargetId, `currentFile objectIdentity 不应退化: ${JSON.stringify(runBody.targetPackage?.currentFile)}`); + assert(runBody.targetPackage?.allowedFiles?.includes(mindmapPath), `allowedFiles 应包含 mindmap resource: ${JSON.stringify(runBody.targetPackage?.allowedFiles)}`); + + const screenshot = await saveScreenshot(page, "01-mindmap-resource-target"); + const result = { ok: true, task: TASK, baseUrl: BASE_URL, root, rootUri, documentId, mindmapPath, assetId, expectedTargetId, screenshot, captured }; + fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + console.log(JSON.stringify(result, null, 2)); + } catch (error) { + caughtError = error; + await saveScreenshot(page, "failure").catch(() => undefined); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + await page.close().catch(() => undefined); + await context.close().catch(() => undefined); + await browser.close().catch(() => undefined); + } + + if (caughtError) { + throw caughtError; + } +} + +if (require.main === module) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); +} diff --git a/scripts/task526-local-folder-ocr-api-smoke.js b/scripts/task526-local-folder-ocr-api-smoke.js new file mode 100644 index 00000000..c86d2d7e --- /dev/null +++ b/scripts/task526-local-folder-ocr-api-smoke.js @@ -0,0 +1,262 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { chromium } = require("playwright"); +const { ensureAuthenticated, UI_TIMEOUT_MS } = require("./tree-shell-smoke-helpers"); + +const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); +const TASK = "task526-local-folder-ocr-api-smoke"; +const OUTPUT_DIR = path.join(process.cwd(), "tmp", TASK); +const RESULT_PATH = path.join(OUTPUT_DIR, "result.json"); + +function fileUrl(localPath) { + return `file://${localPath.split(path.sep).map((part, index) => ( + index === 0 ? "" : encodeURIComponent(part) + )).join("/")}`; +} + +function localMdDocumentId(relativePath) { + return `local-md:${Buffer.from(relativePath, "utf8") + .toString("hex") + .replace(/../g, (hex) => { + const code = Number.parseInt(hex, 16); + const ch = String.fromCharCode(code); + return /[A-Za-z0-9._-]/.test(ch) ? ch : `~${hex.toUpperCase()}`; + })}`; +} + +function documentUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`); + url.searchParams.set("sourceKind", "local_folder"); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("treeView", "filetree"); + return url.toString(); +} + +function writeWorkspaceManifest(root, ownerId) { + const metadataDir = path.join(root, ".mnote"); + fs.mkdirSync(metadataDir, { recursive: true }); + fs.writeFileSync( + path.join(metadataDir, "workspace.json"), + `${JSON.stringify({ + workspaceId: `local-ws:${ownerId}:task526`, + ownerId, + createdAt: new Date().toISOString(), + capabilities: ["local_files", "markdown_edit", "ocr"], + }, null, 2)}\n`, + "utf8", + ); +} + +async function writeResult(payload) { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + fs.writeFileSync(RESULT_PATH, `${JSON.stringify({ ...payload, resultPath: RESULT_PATH }, null, 2)}\n`, "utf8"); +} + +async function ensureDocumentVisible(page, root, relativePath) { + await page.goto(documentUrl(root, relativePath), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + if (new URL(page.url()).pathname === "/auth") { + const quickLogin = page.getByRole("button", { name: "测试账号快速登录" }); + await quickLogin.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await quickLogin.click({ timeout: UI_TIMEOUT_MS }); + await page.waitForURL((url) => url.pathname !== "/auth", { timeout: UI_TIMEOUT_MS }).catch(() => undefined); + await page.goto(documentUrl(root, relativePath), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + } + await page.locator('[data-testid="mnote-leptos-tiptap-island-editor-root"]').first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); +} + +async function main() { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task526-ocr-")); + const actorId = "mnote-e2e"; + const workspaceId = `local-ws:${actorId}:task526`; + const relativePath = "docs/Page.md"; + const documentId = localMdDocumentId(relativePath); + const sourceRootRelativePath = "docs/Page.assets/photo.png"; + const ocrToken = "TASK526_OCR_TOKEN"; + writeWorkspaceManifest(root, actorId); + fs.mkdirSync(path.join(root, "docs", "Page.assets"), { recursive: true }); + fs.writeFileSync(path.join(root, relativePath), "# OCR Page\n\n![photo](./Page.assets/photo.png)\n", "utf8"); + fs.writeFileSync(path.join(root, sourceRootRelativePath), Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a])); + + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext(); + const page = await context.newPage(); + const screenshots = {}; + try { + await ensureAuthenticated(page, context.request); + await ensureDocumentVisible(page, root, relativePath); + screenshots.page = path.join(OUTPUT_DIR, "01-page.png"); + await page.screenshot({ path: screenshots.page, fullPage: true }); + + const result = await page.evaluate(async ({ rootUri, documentId, sourceRootRelativePath, workspaceId, ocrToken }) => { + async function jsonFetch(pathname, init = {}) { + const response = await fetch(pathname, { + ...init, + headers: { + "content-type": "application/json", + ...(init.headers || {}), + }, + }); + const payload = await response.json().catch(() => null); + return { status: response.status, payload }; + } + const create = await jsonFetch("/api/local-folder/ocr/jobs", { + method: "POST", + body: JSON.stringify({ + rootUri, + documentId, + sourceRootRelativePath, + provider: "mock", + mockMarkdown: `# OCR Result\n\n${ocrToken} browser smoke text`, + }), + }); + const ocrRootRelativePath = create.payload?.job?.ocrRootRelativePath || ""; + const read = await jsonFetch(`/api/local-folder/ocr/read?rootUri=${encodeURIComponent(rootUri)}&ocrRootRelativePath=${encodeURIComponent(ocrRootRelativePath)}`, { + method: "GET", + headers: {}, + }); + const status = await jsonFetch(`/api/local-folder/ocr/status?rootUri=${encodeURIComponent(rootUri)}&sourceRootRelativePath=${encodeURIComponent(sourceRootRelativePath)}`, { + method: "GET", + headers: {}, + }); + const jobs = await jsonFetch(`/api/local-folder/ocr/jobs?rootUri=${encodeURIComponent(rootUri)}`, { + method: "GET", + headers: {}, + }); + const withoutOcr = await jsonFetch("/api/search/documents", { + method: "POST", + body: JSON.stringify({ + workspaceId, + sourceKind: "local_folder", + rootUri, + query: ocrToken, + limit: 5, + filters: { includeOcr: false }, + }), + }); + const withOcr = await jsonFetch("/api/search/documents", { + method: "POST", + body: JSON.stringify({ + workspaceId, + sourceKind: "local_folder", + rootUri, + query: ocrToken, + limit: 5, + filters: { includeOcr: true }, + }), + }); + const insert = await jsonFetch("/api/local-folder/ocr/insert", { + method: "POST", + body: JSON.stringify({ + rootUri, + documentId, + ocrRootRelativePath, + mode: "link", + }), + }); + return { create, read, status, jobs, withoutOcr, withOcr, insert, ocrRootRelativePath }; + }, { + rootUri: fileUrl(root), + documentId, + sourceRootRelativePath, + workspaceId, + ocrToken, + }); + + assert.equal(result.create.status, 200, `OCR create failed: ${JSON.stringify(result.create)}`); + assert.equal(result.create.payload.job.status, "done", `OCR job should be done: ${JSON.stringify(result.create.payload)}`); + assert(result.ocrRootRelativePath.endsWith(".ocr.md"), `OCR path invalid: ${result.ocrRootRelativePath}`); + assert.equal(result.read.status, 200, `OCR read failed: ${JSON.stringify(result.read)}`); + assert(result.read.payload.markdown.includes(ocrToken), "OCR read should include mock OCR text"); + assert.equal(result.status.payload.job.ocrRootRelativePath, result.ocrRootRelativePath, "status should return OCR sidecar path"); + assert(result.jobs.payload.jobs.some((job) => job.ocrRootRelativePath === result.ocrRootRelativePath), "jobs list should include OCR job"); + assert.equal(result.withoutOcr.payload.results.length, 0, `includeOcr=false should not match OCR text: ${JSON.stringify(result.withoutOcr.payload)}`); + const ocrResult = result.withOcr.payload.results.find((item) => item.hasOcr === true); + assert(ocrResult, `includeOcr=true should return owner page OCR result: ${JSON.stringify(result.withOcr.payload)}`); + assert.equal(ocrResult.documentId, documentId, "OCR search result should point to owner document"); + assert.equal(ocrResult.ocrEvidence.ocrRootRelativePath, result.ocrRootRelativePath, "OCR evidence should include sidecar path"); + assert.equal(result.insert.status, 200, `OCR insert failed: ${JSON.stringify(result.insert)}`); + const ownerMarkdown = fs.readFileSync(path.join(root, relativePath), "utf8"); + assert(ownerMarkdown.includes(`[OCR:photo.png](`), `owner markdown should include explicit OCR link:\n${ownerMarkdown}`); + + await page.waitForFunction(() => Boolean(window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab), null, { + timeout: UI_TIMEOUT_MS, + }); + const openResourceResult = await page.evaluate(async ({ rootUri, documentId, ocrRootRelativePath, workspaceId }) => { + const runtime = window.__mnoteDocumentPaneRuntime; + if (!runtime || typeof runtime.openResourceInActiveTab !== "function") { + throw new Error("缺少 openResourceInActiveTab runtime"); + } + const title = ocrRootRelativePath.split("/").filter(Boolean).pop() || "OCR"; + return await runtime.openResourceInActiveTab({ + kind: "markdown", + title, + path: ocrRootRelativePath, + objectIdentity: `local-ocr:${ocrRootRelativePath}`, + assetId: `local-ocr:${ocrRootRelativePath}`, + documentId, + ownerDocumentId: documentId, + workspaceId, + sourceKind: "local_folder", + rootUri, + resourceKind: "markdown", + }); + }, { + rootUri: fileUrl(root), + documentId, + ocrRootRelativePath: result.ocrRootRelativePath, + workspaceId, + }); + assert.equal(openResourceResult, true, "OCR sidecar resource tab should open"); + await page.locator('[data-mnote-resource-tab-panel][data-resource-kind="markdown"] [data-testid="mnote-leptos-tiptap-island-editor-root"]').first().waitFor({ + state: "visible", + timeout: UI_TIMEOUT_MS, + }); + screenshots.ocrResource = path.join(OUTPUT_DIR, "02-ocr-resource-tab.png"); + await page.screenshot({ path: screenshots.ocrResource, fullPage: true }); + + await writeResult({ + ok: true, + task: TASK, + root, + documentId, + ocrRootRelativePath: result.ocrRootRelativePath, + screenshots, + }); + console.log(JSON.stringify({ + ok: true, + task: TASK, + root, + documentId, + ocrRootRelativePath: result.ocrRootRelativePath, + screenshots, + }, null, 2)); + } catch (error) { + screenshots.failure = path.join(OUTPUT_DIR, "failure.png"); + await page.screenshot({ path: screenshots.failure, fullPage: true }).catch(() => undefined); + await writeResult({ + ok: false, + task: TASK, + error: error instanceof Error ? error.message : String(error), + root, + documentId, + screenshots, + }); + throw error; + } finally { + await browser.close(); + } +} + +main().catch((error) => { + console.error(error instanceof Error ? error.stack || error.message : String(error)); + process.exit(1); +}); diff --git a/skills/mnote-mindmap/SKILL.md b/skills/mnote-mindmap/SKILL.md new file mode 100644 index 00000000..67d86a3e --- /dev/null +++ b/skills/mnote-mindmap/SKILL.md @@ -0,0 +1,47 @@ +# MNote mindmap editing + +Use this skill only when the user asks to read, summarize, update, or create an MNote mindmap resource. + +Typical triggers: +- The user mentions 思维导图, 脑图, mindmap, KMind, or `.mindmap.json`. +- The current Page AI target is a mindmap resource tab. +- The current page contains a mindmap embed and the task is about that embed. +- The user asks to turn a PDF, Office document, Markdown page, or pasted material into a mindmap. + +Rules: +- First call `mnote.context.snapshot` or `mnote.context.resolve_target` when target metadata is needed. +- Prefer the current mindmap resource tab over page embeds when both are present. +- For existing mindmaps, call `mnote.mindmap.fetch` before proposing or applying changes. +- For new mindmaps from PDF or document summaries, convert the source material into a concise hierarchical outline, then call `mnote.mindmap.create_from_outline`. +- Do not write a naked simple-mind-map tree as the final file. MNote mindmap files use an envelope with `data` as the root tree and `view` as runtime view state. +- Keep the root node uid as `root` for new maps unless MNote returns a different root. +- Let MNote generate child node uid values; do not invent duplicate ids. +- Before writing, confirm the run has `read_write` permission and the target resource is inside `allowedResourceIds`. +- After writing or creating a mindmap, read it back with `mnote.mindmap.fetch` and report the changed `.mindmap.json` path. +- If authorization is missing, ask the user to grant access instead of guessing a path. + +Current apply_ops contract: +- Supported operations are `updateText` / `updateNode`, `insertChild` / `addChild`, and `deleteNode`. +- `updateText` / `updateNode` require `nodeId` (or `node_id` / `id`) and `text` (or `title`). +- `insertChild` / `addChild` require `parentId` (or `parent_id` / `nodeId`) and a child node payload. +- `deleteNode` requires `nodeId` (or `node_id` / `id`) and must never target the root node. +- Unknown operations are rejected; do not assume extra edit verbs exist. +- Prefer small, reviewable edits. Keep node text concise and split long prose into child nodes. +- Long text should be summarized into short phrases or child nodes rather than embedded as a single paragraph. +- Respect `dryRun` and revision checks when the caller provides them. + +Mindmap outline format: +- Use one central root topic as the `title`; it should name the subject, not explain it in a paragraph. +- Use first-level children for the major categories or sections radiating from the root. +- Use concise keywords or short phrases for node text. Avoid long paragraphs, full prose summaries, and multiple sentences inside one node. +- Put one idea in each node. Split causes, decisions, examples, and evidence into child nodes instead of joining them with punctuation. +- Keep each level scannable: prefer 3-8 sibling branches unless the source structure requires more. +- Preserve hierarchy with `children`; do not simulate structure by embedding Markdown lists or newline-separated text in a node. +- Keep citations, page numbers, and source metadata in `sourceRefs` or nearby metadata when possible, rather than cramming them into visible node text. + +PDF or document to mindmap workflow: +1. Identify the source resource and target page. +2. Extract or receive a title plus outline from the source material. +3. Keep the outline small enough to be useful: preserve chapters, decisions, key concepts, and relationships; remove repeated prose. +4. Call `mnote.mindmap.create_from_outline` with `title`, `outline`, `sourceRefs`, and the target resource path when provided. +5. Verify the created resource by fetching it and checking the root title and first-level branches. diff --git a/skills/mnote-onlyoffice-live/SKILL.md b/skills/mnote-onlyoffice-live/SKILL.md new file mode 100644 index 00000000..2ecddc19 --- /dev/null +++ b/skills/mnote-onlyoffice-live/SKILL.md @@ -0,0 +1,68 @@ +# MNote ONLYOFFICE live bridge + +用于操作当前已经打开的 ONLYOFFICE 编辑器会话。 + +## 适用范围 + +- 仅针对当前浏览器里打开的 ONLYOFFICE 文档 +- 支持 Word、Excel、PPT 的基础读写 +- 不用于离线批量修改文件 + +## 可用工具 + +- `mnote.onlyoffice.session.current` +- `mnote.onlyoffice.capabilities` +- `mnote.onlyoffice.selection.get` +- `mnote.onlyoffice.document.insert_text` +- `mnote.onlyoffice.document.replace_selection` +- `mnote.onlyoffice.document.insert_html` +- `mnote.onlyoffice.document.export` +- `mnote.onlyoffice.document.search_replace` +- `mnote.onlyoffice.document.insert_table` +- `mnote.onlyoffice.document.get_comments` +- `mnote.onlyoffice.document.add_comment` +- `mnote.onlyoffice.sheet.get_sheets` +- `mnote.onlyoffice.sheet.add_sheet` +- `mnote.onlyoffice.sheet.rename_sheet` +- `mnote.onlyoffice.sheet.get_range` +- `mnote.onlyoffice.sheet.get_range_values` +- `mnote.onlyoffice.sheet.get_values` +- `mnote.onlyoffice.sheet.set_value` +- `mnote.onlyoffice.sheet.set_formula` +- `mnote.onlyoffice.sheet.batch_set_values` +- `mnote.onlyoffice.sheet.set_range_values` +- `mnote.onlyoffice.sheet.format_range` +- `mnote.onlyoffice.sheet.set_dimensions` +- `mnote.onlyoffice.sheet.sort_range` +- `mnote.onlyoffice.sheet.add_chart` +- `mnote.onlyoffice.presentation.get_slides` +- `mnote.onlyoffice.presentation.get_slide_texts` +- `mnote.onlyoffice.presentation.get_shapes` +- `mnote.onlyoffice.presentation.add_text_slide` +- `mnote.onlyoffice.presentation.replace_text` +- `mnote.onlyoffice.presentation.set_shape_text` +- `mnote.onlyoffice.presentation.delete_slide` +- `mnote.onlyoffice.presentation.add_table` +- `mnote.onlyoffice.presentation.clear_slide` +- `mnote.onlyoffice.presentation.add_shape` + +## 使用原则 + +- 先调用 `mnote.onlyoffice.session.current` 确认当前会话存在,并记录 `sessionId` / `documentId` / `assetId` / `fileType` +- 多个 ONLYOFFICE 标签页同时打开时,写操作必须显式传 `onlyofficeSessionId` +- 写入前优先做最小范围修改,并先用 `dryRun` 检查计划 +- 读取大块表格时优先使用 `sheet.get_values`,写入多格时优先使用 `sheet.batch_set_values` +- 需要按用户可见坐标定位表格时优先使用 A1 地址工具:`sheet.get_range_values` / `sheet.set_range_values` +- `sheet.set_range_values` 只写入 A1 区域与二维 `values` 的交集,调用前应确认尺寸 +- 表格样式只做显式小范围:`sheet.format_range` 支持字体粗斜体、颜色、对齐和数字格式;`sheet.set_dimensions` 支持零基行高/列宽 +- Excel 排序使用 `sheet.sort_range`,先确认 A1 range,`keyColumn` 是 range 内从 1 开始的列号;图表使用 `sheet.add_chart`,默认柱状图 +- `document.export` 仅支持 `markdown` 和 `html` +- Word 精确文本替换优先使用 `document.search_replace` +- Word 插入结构化表格使用 `document.insert_table`;评论读取/添加使用 `document.get_comments` / `document.add_comment` +- `document.add_comment` 默认给当前选区加评论;没有可靠选区时显式传 `target: "document_start"` +- PPT 文本读取优先使用 `presentation.get_slide_texts`;`presentation.replace_text` 只处理文本框纯文本,命中文本框会按纯文本段落重建 +- PPT 精确改某个文本框时先调用 `presentation.get_shapes` 获取 `shapeIndex` / `shapeId`,再调用 `presentation.set_shape_text` +- 删除 PPT 幻灯片前必须先调用 `presentation.get_slides` 或 `presentation.get_slide_texts` 确认 `slideIndex` +- PPT 插入结构化表格使用 `presentation.add_table`,默认居中插入;需要精确位置时传 `xMm` / `yMm` / `widthMm` / `heightMm` +- PPT 清空指定页对象使用 `presentation.clear_slide`,调用前必须确认目标 `slideIndex`;添加基础图形使用 `presentation.add_shape` +- 需要大范围离线处理时,改用 `officecli`