桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载MooTool 的 Compose 桌面版next-compose在其 Vault 体系中持续与 Electron 版对齐行为语义。本文以 DIFF-219《Vault 对话框单段路径与随手记复制前保存》为骨架拆解两处关键行为JSON Vault 新建文件/文件夹对话框在用户只输入snippet.json这类无/单段名称时的落点解析逻辑以及随手记Quick Note复制时先保存脏笔记再读盘复制的守卫流程并结合VaultSelectionPath源码、JsonScreen.kt/QuickNoteScreen.kt的接线实现与VaultSelectionPathTest测试用例给出可验证的底层依据。读完你将掌握 MooTool Compose Vault 的路径解析算法、复制前保存守卫的调用链以及对应的测试与运行验证方式。背景为什么单段输入要落在当前选中目录在 Vault 的新建文件/文件夹对话框中用户习惯只输入snippet.json或一个文件夹名而不输入完整相对路径。此前 Compose 版的这类新建操作总是落在 Vault 根目录与 Electron 版的行为不一致Electron 侧通过selectedDirectory(selectedEntry)取父目录Compose 需要复刻这一语义。DIFF-219 明确了两条目标JSON Vault 新建文件/文件夹对话框若用户只输入snippet.json或文件夹名无/应落在当前选中目录下——这与默认全路径预填、以及 DIFF-202JSON Vault 新建落在当前选中目录的行为保持一致。随手记复制与 JSON 侧 DIFF-218 同理duplicate操作是基于磁盘内容的读盘行为当当前笔记存在未保存修改脏时必须先保存否则复制出来的副本会基于旧内容。核心实现一VaultSelectionPath.resolveEntryPath单段路径解析路径解析的全部算法集中在 VaultSelectionPath.kt它是一个纯函数式工具对象由三个函数组合而成。1.parentDirectory由选中项推导父目录fun parentDirectory(selectedPath: String, entries: ListVaultEntry): String { if (selectedPath.isBlank()) return val entry entries.find { it.relativePath selectedPath } if (entry?.directory true) return selectedPath return selectedPath.substringBeforeLast(/, ) }语义注释中标明对照 Electron 的selectedDirectory(selectedEntry)选中路径为空 → 返回空串即根目录语义选中的条目本身是目录 → 直接返回该目录路径新条目创建在其内部选中的条目是文件 → 返回其所在父目录取最后一个/之前的片段。2.join父路径与名称拼接fun join(parent: String, name: String): String { val trimmed name.trim().removePrefix(/).removeSuffix(/) if (trimmed.isEmpty()) return parent return if (parent.isBlank()) trimmed else $parent/$trimmed }对名称做trim并去掉首尾/避免用户误输入绝对路径前缀或尾部斜杠名称为空时退化为父路径本身父路径为空时直接返回单段名称等价根目录下创建。3.resolveEntryPath对话框输入的统一落点解析DIFF-219 新增fun resolveEntryPath( input: String, vaultSelectedPath: String, currentFile: String, entries: ListVaultEntry, ): String { val trimmed input.trim().removeSuffix(/) if (trimmed.isEmpty()) return if (trimmed.contains(/)) return trimmed val selected vaultSelectedPath.ifBlank { currentFile } return join(parentDirectory(selected, entries), trimmed) }解析规则可归纳为三条输入情形结果说明空白输入视为取消/不可提交由调用方根据dialogCanSubmit等守卫处理含/的完整相对路径如drafts/x.json原样返回用户显式指定了层级尊重完整路径无/的单段名称如new.jsonjoin(parentDirectory(selected), name)落在当前选中目录父目录取vaultSelectedPath若为空则回退currentFile其中父目录基准优先vaultSelectedPathVault 树当前选中项仅在未选中任何目录时回退到currentFile当前打开文件的父目录。这与 DIFF-202 / DIFF-203 中parentDirectory、join的语义完全一致属于复用而非新造。核心实现二JSON 新建对话框提交接线在 JsonScreen.kt 中json-file与json-folder两种对话框模式提交时都调用VaultSelectionPath.resolveEntryPathjson-file - { jsonVaultRequireFlushDirty(container, session, monitor, onConflict) val path VaultSelectionPath.resolveEntryPath( name, session.vaultSelectedPath, session.currentFile, items, ) val body newJsonVaultFileContent(session.editor.text) container.jsonVault.createFile(path, body) loadJsonVaultSnippet(session, path, body) container.recordVaultActivity(VaultGitCheckpointMessages.UPDATE_JSON_SNIPPET, json true) } json-folder - { jsonVaultRequireFlushDirty(container, session, monitor, onConflict) val path VaultSelectionPath.resolveEntryPath( name, session.vaultSelectedPath, session.currentFile, items, ) container.jsonVault.createDirectory(path) session.vaultSelectedPath path }两个值得注意的细节提交前先 flush 脏内容jsonVaultRequireFlushDirty定义于 JsonVaultSession.kt在创建文件/文件夹前强制保存当前编辑器的未保存修改flush 失败则抛出异常并由外层runCatching捕获——与 DIFF-224《json-vault-new-file-save-guard》的保存守卫一脉相承。新建文件夹后同步选中路径json-folder分支将session.vaultSelectedPath更新为新目录保证后续新建操作继续以该目录为基准对齐选中目录节点的行为。duplicateJsonVaultSnippet同在 JsonVaultSession.kt对应 DIFF-218则是 JSON 侧复制前保存的参照实现先jsonVaultFlushDirtyOrNotice静默保存当前片段再container.jsonVault.duplicate(sourcePath)读盘复制成功后loadJsonVaultSnippet同步currentFile、vaultSelectedPath、savedText与编辑器正文并调用monitor.rebaselineAfterLocalCrud()刷新 Vault 变更基线。核心实现三随手记复制前保存守卫随手记的复制入口在 QuickNoteScreen.kt 的duplicateQuickNoteEntryVault 树右键菜单与底栏复制共用private fun duplicateQuickNoteEntry( container: AppContainer, session: QuickNoteSession, vault: NoteVault, monitor: VaultRevisionMonitor?, sourcePath: String, onConflict: (VaultConflictState) - Unit, ): ResultNoteDocument { if (!quickNoteVaultSaveIfNeeded(container, session, vault, monitor, onConflict)) { return Result.failure(IllegalStateException(container.t(quickNote.saveFailed))) } return runCatching { vault.duplicate(sourcePath).also { copy - quickNoteOpenVaultFile(session, vault, copy.relativePath) monitor.rebaselineAfterLocalCrud() container.recordVaultActivity(VaultGitCheckpointMessages.DUPLICATE_QUICK_NOTE) } } }其执行顺序严格符合 DIFF-219 定义saveIfNeeded→duplicate→openFile。quickNoteVaultSaveIfNeeded定义于 QuickNoteVaultContext.kt内部用quickNoteSaveIfNeeded(quickNoteDirty(session))判断脏状态脏时才调用quickNoteSaveCurrent写盘保存失败含冲突场景会调用notifyQuickNoteSaveGuardFailure给出提示并返回false此时复制直接中止并返回Result.failure杜绝基于旧内容复制或复制时丢弃未保存修改。vault.duplicate的读盘语义见 NoteVault.kt 的duplicate(relativePath)——它从磁盘readNote(relativePath)读取源笔记在源文件同目录下生成标题 Copy的新文件标题经NoteFrontmatter.sanitizeName清洗、VaultFiles.uniqueChild保证文件名唯一并以当前时间重置新副本的createdAt/modifiedAt。这正是必须先保存再复制的原因duplicate只认磁盘内容不认编辑器内存。复制成功后打开副本quickNoteOpenVaultFile将编辑器切换到副本写入正文、更新currentFile、vaultSelectedPath、savedText、metadata与语法高亮用户复制后立刻看到新文件内容。Git 变更记录container.recordVaultActivity(VaultGitCheckpointMessages.DUPLICATE_QUICK_NOTE)记录活动文案Duplicate Quick Note见 VaultGitCheckpointMessages.kt与 JSON 侧DUPLICATE_JSON_SNIPPET对称供 Vault Git 检查点功能统计变更。对比参照JSON 侧 JsonVault.kt 的duplicate(relativePath)同样基于Files.copy读盘复制新文件名规则为原名 Copy.json同样经VaultFiles.uniqueChild去重只允许复制文件check(Files.isRegularFile(source))。与相邻 DIFF 的关系一个渐进的行为对齐序列DIFF-219 不是孤立的一次改动而是 Vault 对话框行为对齐序列中的一环读懂关联 DIFF 有助于理解设计脉络DIFF主题与 DIFF-219 的关系DIFF-202JSON Vault 新建文件/文件夹落在当前选中目录引入VaultSelectionPath.parentDirectory/join是路径解析的基础DIFF-203随手记 Vault 新建笔记/文件夹落在当前选中目录复用同一路径工具对话框默认值从固定folder改为空值DIFF-218JSON Vault 复制片段选中与脏保存JSON 侧先保存再复制duplicateJsonVaultSnippet是随手记侧的直接参照DIFF-219Vault 对话框单段路径与随手记复制前保存新增resolveEntryPath统一单段落点随手记复制补上saveIfNeeded守卫DIFF-223随手记新建笔记前保存守卫新建分支校验saveIfNeeded返回值保存失败不切换DIFF-345随手记复制前prepareQuickNoteVaultContext复制入口进一步统一为先打开目标再复制对齐 ElectronselectNode此外QuickNoteVaultImport.kt 中的quickNoteVaultImportTargetDirectory也复用了parentDirectory作为导入/新建场景取目标目录的统一入口印证该路径工具在 Vault 各 CRUD 场景中的通用性。验证与运行DIFF-219 的验证包括两条1. 单元测试VaultSelectionPathTest.resolveSingleSegmentUnderSelectedDirectory测试位于 VaultSelectionPathTest.kt核心断言val entries listOf( VaultEntry(Work, Work, true, 0), VaultEntry(Work/a.json, a.json, false, 0), ) assertEquals( Work/new.json, VaultSelectionPath.resolveEntryPath(new.json, Work, , entries), ) assertEquals(drafts/x.json, VaultSelectionPath.resolveEntryPath(drafts/x.json, Work, , entries))用例覆盖两条关键分支选中目录Work 输入new.json无/→ 解析为Work/new.json验证单段落当前选中目录输入drafts/x.json含/→ 原样返回验证完整路径不重写。同文件的parentFromFileSelection、parentFromDirectorySelection、joinUnderParent分别覆盖文件选中取父目录、目录选中返回自身、空父路径拼接等基础用例构成VaultSelectionPath的完整行为基线。2. 桌面测试套件DIFF-219 文档给出的验证命令为./gradlew :composeApp:desktopTest --offline该命令运行 Compose 桌面测试套件--offline确保不依赖外网构建产物与依赖从本地解析。参考 acceptance.md 中的说明macOS 下若 Gradle daemon 出现 exit 134可追加--no-daemon。项目根目录还提供 gradlewWindows 下对应gradlew.bat作为构建入口。小结DIFF-219 用一次小规模的路径工具扩展与一次复制守卫补全消除了两个行为与 Electron 不一致的边界问题新建落点VaultSelectionPath.resolveEntryPath让 JSON 与随手记的新建对话框统一支持单段名称落当前选中目录、完整路径原样尊重两种输入语义且父目录基准在vaultSelectedPath与currentFile之间正确回退复制安全随手记duplicateQuickNoteEntry补上saveIfNeeded前置守卫与 JSON 侧duplicateJsonVaultSnippet对称保证duplicate读盘操作永远不会基于未保存的编辑器内容。两条改动都有直接的源码位置与单元测试背书属于小而明确的行为对齐也为后续 Vault 对话框的移动、重命名、导入等 CRUD 路径处理提供了统一的父目录推导语义。赞分享桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载相关推荐MooTool Next Compose 随手记工具栏对齐笔记颜色、行距与列表前缀的实现解析DIFF-036MooTool Next Compose 随手记工具栏对齐笔记颜色、行距与列表前缀的实现解析DIFF 036 本篇文章以 MooTool 下一代桌面端桌面应用开发工具MooTool Compose 随手记 250ms 空闲自动保存机制解析DIFF-199 行为对齐与实现原理MooTool Compose 随手记 250ms 空闲自动保存机制解析DIFF 199 行为对齐与实现原理 导读 本篇文章围绕 MooTool 新一代 Co桌面应用开发工具MooTool 随手记删除交互统一改造Compose 端 Action 对话框实现与验证全解析MooTool 随手记删除交互统一改造Compose 端 Action 对话框实现与验证全解析 本篇技术指南聚焦 MooTool Next Compose 桌桌面应用开发工具上一篇NetQuality终极网络质量检测指南3步解决网络卡顿烦恼下一篇笔记散落多个App时SiYuan本地知识库实用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考