十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

tldraw 评论历史机制:CommentingOptions 的 history 与 dragHistory 如何决定评论写入是否可撤销

tldraw 评论历史机制:CommentingOptions 的 history 与 dragHistory 如何决定评论写入是否可撤销 tldraw 评论历史机制CommentingOptions 的 history 与 dragHistory 如何决定评论写入是否可撤销【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw本文基于 tldraw 官方示例collaboration/comment-history展开讲解tldraw/commenting中两个关键配置项history与dragHistory的工作原理它们分别控制评论写入发布、回复、编辑、解决和图钉拖拽重定位是否进入编辑器的撤销栈。读完后你将能根据协作场景正确配置评论的 undo/redo 行为并理解为什么 tldraw 默认让评论写入对撤销不可见。1. 示例要回答的问题评论写入是否进入 undo 栈示例的 READMEREADME.md用一句话点明主题Decide whether comment writes land on the editors undo stack.——即决定评论写入是否落在编辑器的撤销栈上。它的核心结论是CommentingOptions.history治理所有评论写入——发布posting、回复replying、编辑editing、解决resolving、删除deleting——默认值为ignore。在共享文档中一个可撤销的删除会把协作者已经移除的讨论串复活an undoable delete would resurrect a thread a collaborator already removed因此默认不记录。图钉拖拽pin drags是例外重新锚定一条评论本质上是空间编辑合理地应该和形状移动一起被撤销。dragHistory仅针对拖拽覆盖history的取值。示例的操作路径是移动形状、发布一条评论、拖动它的图钉然后按 undo观察计数变化。实现主体是 CommentHistoryExample.tsx配套样式在 comment-history.css。2. 配置项的类型定义与默认值history和dragHistory定义在 options.ts 的CommentingOptions接口中export interface CommentingOptions { // History / undo /** * How comment mutations interact with the editor undo stack. Defaults to ignore — comments * are deliberately not undoable (see TLComment). record is a multiplayer footgun: undoing * a delete resurrects a thread a collaborator already removed. Safe only single-player. */ readonly history: TLHistoryBatchOptions[history] /** * History mode for the pin drag-to-move re-anchor specifically. Unlike posts/edits this is a * spatial edit that may reasonably be undoable alongside a shape move. Defaults to history. */ readonly dragHistory: TLHistoryBatchOptions[history] | undefined // ... }两个要点两者类型都是TLHistoryBatchOptions[history]。该类型定义在 history-types.ts取值为record | record-preserveRedoStack | ignorerecord加入撤销栈并清空重做栈record-preserveRedoStack加入撤销栈但不清空重做栈ignore两个栈都不进。 也就是说history除了记录/不记录之外还有一个更细的选项。dragHistory的类型是... | undefined不设置时回退到history即图钉拖拽默认与评论写入同策略。默认值集中在同文件的 defaultCommentingOptionsexport const defaultCommentingOptions { history: ignore, dragHistory: undefined, enableClustering: true, // ... } as const satisfies CommentingOptions配置方式与ShapeUtil.configure类似通过CommentTool.configure({...})一次性传入静态配置运行期响应式的值如currentUserId则走CommentingContext属性。合并结果由 getCommentingOptions 从已注册的 comment 工具节点上读取未注册时回退到默认值。由于选项在工具注册时即固定运行中无法动态改——这正是示例里切换模式要整体重挂载编辑器的原因见第 5 节。3. 源码中的解析规则三种写入类别各走哪条历史策略真正把选项翻译成行为的代码在 comment-mutations.ts。它首先把所有评论写入分成三种类别L40export type CommentMutationKind delete | drag | mutation然后由historyModeForL50-L62决定每种类别实际使用的历史模式function historyModeFor( options: CommentingOptions, kind: CommentMutationKind ): TLHistoryBatchOptions[history] { switch (kind) { case delete: return ignore case drag: return options.dragHistory ?? options.history case mutation: return options.history } }从源码结构看这里有一个比 README 更精确的细节写入类别触发场景实际历史模式mutation发布、回复、编辑、解决/重开options.historydrag图钉拖拽重定位、区域锚点缩放options.dragHistory ?? options.historydeletedeleteComment/deleteThread恒为ignore与配置无关删除被硬编码为ignore的原因写在源码注释里L33-L36 与 L249-L253tldraw 的删除是软删除置isDeleted标志该标志在服务端是一次写入write-once的撤销去清除这个标志会被服务端否决而不是真正恢复内容。因此与其产生一个撤销后失效的操作不如从一开始就不让它进栈。这也解释了为什么deleteComment/deleteThread调用commitCommentMutation时显式传入deleteL257-L273、L286-L298。所有写入最终都经过统一入口commitCommentMutationL80-L131它按类别解析出历史模式后调用editor.run(fn, { history })把底层store.put/store.remove包在回调提供的 writer 里执行。注释里还解释了一个坑editor.run的 history 选项不是可叠加的——嵌套的 run 会覆盖外层模式——所以构成性记录必须走 writer 而不是自己再开一次 commit否则一次drag写入会被静默变成不可撤销。4. 示例中的三种模式对照示例用一张模式表把上述规则变成可交互的实验台CommentHistoryExample.tsx L29-L47const MODE_TOOLS { ignore: [CommentTool], // 默认 record: [CommentTool.configure({ history: record })], // 全部记录 drag: [CommentTool.configure({ dragHistory: record })], // 仅记录图钉拖拽 } const MODE_LABELS: RecordHistoryMode, string { ignore: Ignore (default), record: Record everything, drag: Record pin drags only, } const MODE_HINTS: RecordHistoryMode, string { ignore: Undo rewinds the shape. Comments and pin positions stay put., record: Undo rewinds comments too — the last thing you did, whatever it was., drag: Undo rewinds the shape and pin drags, but never a posted comment., }模式配置undo 的效果Ignore默认CommentTool只回退形状等画布操作评论、图钉位置不动Record everythingCommentTool.configure({ history: record })你最后做的任何事包括发评论、编辑、解决都会被撤销Record pin drags onlyCommentTool.configure({ dragHistory: record })形状移动和图钉拖拽可撤销但已发布的评论不会示例注释文件底部 [1]-[4] 段落给出了选择建议ignore是默认也是共享文档的正确选择——可撤销的删除会复活协作者已删除的讨论串可撤销的解决会回退对方更新的解决状态record只适合单机或者评论存储不参与同步的场景图钉拖拽则是有趣的例外作为空间编辑可以与形状移动一起撤销而发布保持不记录就是第三种模式。5. 示例的完整装配共享 store、key 重挂载与计数面板要复现这个实验有三个装配细节值得注意1store 跨模式共享只在首次挂载时播种。评论记录就存放在编辑器自己的 store 中示例用useMemo创建一次、始终复用L122-L125const store useMemo( () createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }), [] )handleMount中播种一个Move me矩形并且播种动作本身也演示了history: ignore的另一个常见用法L52-L71editor.run( () { editor.createShapes([ { type: geo, x: 180, y: 180, props: { geo: rectangle, w: 300, h: 200, richText: toRichText(Move me) }, }, ]) }, { history: ignore } // 播种的形状也不应可撤销否则第一次 undo 就删掉了示例的主角 )2用key{mode}重挂载编辑器来切换模式。因为评论选项在工具注册时固定切换模式必须让编辑器带着新配置的工具重新挂载L134-L147Tldraw key{mode} // 切换模式 编辑器重挂载 licenseKey{getLicenseKey()} // Commenting 是许可功能部署环境需要含 commenting 的 license key store{store} // 共享 store 让所有讨论串在切换中存活 onMount{handleMount} tools{MODE_TOOLS[mode]} overrides{[commentToolOverrides]} components{components} 注释里点明了两者的生命周期差异store 里的评论记录会跨模式存活但 undo 栈属于编辑器而不属于 store所以每次切换后撤销栈都是空的。3计数面板是观察实验的仪表。HistoryPanel用useCommentThreads和useComments两个 hook 反应式地读取评论记录把线程数/评论数显示出来同时提供 Undo/Redo 按钮L74-L115const threads useCommentThreads(editor) const comments useComments(editor) const canUndo useValue(can undo, () editor.getCanUndo(), [editor]) const canRedo useValue(can redo, () editor.getCanRedo(), [editor]) // ... span classNamecomment-history-panel__count {threads.length} {threads.length 1 ? thread : threads}, {comments.length}{ } {comments.length 1 ? comment : comments} /span按 undo 时盯着这两个数字就是整个示例的意义所在在ignore模式下它们纹丝不动在record模式下会跟着回退。另外提醒一点Commenting 是受许可的功能本地开发环境默认全部开启但部署上线的应用需要配置包含 commenting 的 license key示例通过getLicenseKey()注入见 dotcom-shared。6. 图钉拖拽在源码中如何走drag通道README 说pin drags are the exception落到实现里就是 thread-pin.tsx。拖拽结束时新的锚点通过commitCommentMutation以drag类别提交L300commitCommentMutation(editor, ({ put }) put([{ ...thread, anchor }]), drag)区域锚点region anchor的角点缩放也走同一条提交路径源码注释明确说明了意图// Same commit path as a pin drag, so the configured dragHistory governs both — going straight to editor.run here would make region resizes silently ignore the option.L326-L337。也就是说仅记录图钉拖拽这一模式实际覆盖的是图钉拖拽 区域缩放两类空间编辑而发布/回复/编辑/解决仍然只受history管。7. 测试用例中的行为验证两条测试直接印证了上述解析规则可作为引用依据options.test.ts 中uses dragHistory for a drag, falling back to history when unsethistory: ignore, dragHistory: record时commitCommentMutation(..., drag)产生的run调用是{ history: record }而dragHistory: undefined时落回{ history: ignore }。options.test.ts 中uses options.history for a mutation and returns the callback resulthistory: record的普通 mutation 以{ history: record }提交。comment-mutations.test.ts 的lets dragHistory govern a drag on its own则验证了history: ignore, dragHistory: record组合下拖拽的记录走 writer 且由dragHistory负责——不会因嵌套 commit 而丢失。8. 实践建议结合本文的源码证据给出几条可直接落地的配置建议多人协作评论存储有同步时保持默认history: ignore即dragHistory也留空。撤销只作用于画布操作避免撤销复活已被协作者删除/解决的讨论串这类冲突。单机或纯本地存储的评论场景可以CommentTool.configure({ history: record })让发布、回复、编辑、解决全部可撤销形成统一的 undo 体验。只希望重定位评论可撤销CommentTool.configure({ dragHistory: record })。图钉拖拽和区域缩放会随形状移动一起进入撤销栈而评论内容写入不受影响——这是示例中的第三种模式。删除永远不可撤销无论怎么配置deleteComment/deleteThread都恒为ignore软删除标志一次写入、由服务端清理这一点不需要也无法通过配置改变。切换策略需要重挂载编辑器选项在CommentTool.configure注册时固定运行中不可变参照示例用 React 的key触发重挂载并用共享 store 保住已有的评论记录。参考文件一览示例 README、CommentHistoryExample.tsx实现 options.ts、comment-mutations.ts、thread-pin.tsx类型 history-types.ts测试 options.test.ts、comment-mutations.test.ts。【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表