tldraw Commenting 模块深入指南:解读 @tldraw/commenting 完整公共 API 与画布批注接入实践

发布时间:2026/9/10 20:29:15
tldraw Commenting 模块深入指南:解读 @tldraw/commenting 完整公共 API 与画布批注接入实践 tldraw Commenting 模块深入指南解读 tldraw/commenting 完整公共 API 与画布批注接入实践【免费下载链接】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 无限画布中为图形、页面任意点甚至矩形区域挂载“评论线程”Comment Thread并支持富文本正文、成员提及、Emoji 表情反应、已读状态、线索聚合与侧栏导航——这套能力由仓库中的 packages/commentingnpm 包名tldraw/commenting承载。它是一套开箱即用的协作评论层batteries-included直接读写editor.store中的评论记录并在画布上响应式渲染。本文以该包自动生成的公共 API 报告 api-report.api.md 为骨架对照其源码实现src 目录逐层拆解CommentTool、CommentingOptions、CommentingContext、记录读写、反应系统与全部 UI 组件让读者既能按名索骥地查阅每一个导出符号也能照着示例把评论能力真正接入自己的 tldraw 应用。一、包概览两层 API 设计与仓库中的位置tldraw/commenting的公共 API 由两层组成这一分层在 src/index.ts 的导出注释中写得非常明确第一层展示型组件Presentational。Byline、CommentCard、CommentComposer、CommentThread、CommentsList、CommentPin、Reactions、EmojiPicker等组件不依赖 tldraw可单独用来搭建自定义评论 UI。为了让公共 API 保持稳定tldraw/mentions中的Avatar、CommentAuthor、Mention、MentionList、MentionMember、createMentionSuggestion、filterMentionMembers等符号由本包直接 re-export。第二层与 tldraw 强耦合的评论层tldraw-coupled。包括CommentTool工具、围绕评论记录封装的一系列响应式 hooks、富文本正文渲染器CommentBody以及“全家桶”覆盖层组件CanvasComments这一层直接与Editor、editor.store打交道。仓库层面包配置见 package.json当前仓库中记录的版本为5.3.2peerDependencies 为react ^18.2.0 || ^19.2.1、react-dom同款Node 要求22.12.0。从tldraw_product元数据可以看出它属于Commenting特性、父级归类在tldraw:collaboration下并被标记为 premium 特性licenseFlag: FEAT_COMMENTING——与源码中的useCommentingEnabled钩子license.ts以及CanvasComments组件开头的许可证校验逻辑相呼应。源码目录结构与 API 层对应关系如下源码目录承载的公共 API 类别src/ui全部展示型组件、时间格式化、反应渲染原语src/canvasCommentTool、记录读写、hooks、覆盖层、状态 atom、权限判定src/clustering画布内评论聚合/聚簇的内部算法MST、运行时模型等二、数据模型与锚点评论记录从哪里来2.1 三种评论记录类型评论数据以记录record形式存放在editor.store中。类型TLCommentRecord是三种记录的联合export type TLCommentRecord TLComment | TLCommentReaction | TLCommentThreadTLCommentThread一条评论线程携带anchor锚点与可选的resolved解析信息TLComment线程中的一条消息/评论携带富文本body、authorId与可选的editedAtTLCommentReaction对某条评论的某个 Emoji 反应。实现细节comment-store.ts说明这些记录虽然是可选加入的、并不属于TLRecord联合类型但由于画布要响应式地渲染它们它们就存放在 editor 的本地 store 中。因为静态类型上editor.store是StoreTLRecord所有读取都需要重新解释类型comment-store.ts把这些“重新解释”收敛到一个边界后面使调用点保持类型安全。需要留意的一个实现事实评论记录被设计为不可变 软删除。删除操作只是置isDeleted标记由服务端负责真正的清除这一策略贯穿所有 mutation 函数。2.2 锚点Anchor语义线程的锚点TLCommentAnchor决定了 pin大头针落在哪里。通过 thread-state.ts 的源码可以梳理出四种锚点point纯粹的页面坐标点shape绑定到某个图形。精确precise锚点记录在图形自身坐标系内的归一化 (0–1) 偏移非精确锚点则渲染在配置的impreciseShapeAnchor位置默认右上角并通过图形的 page transform 跟随旋转region矩形区域锚点以虚线框 位于释放角上的 pin 呈现pinX/pinY记录 pin 所在角page按源码anchorPagePoint的 switch 分支page类型返回null即不渲染 pin作为兼容占位。与本主题强相关的三个公共函数负责锚点的换算anchorPagePoint(editor, anchor)算出线程 pin 应落在页面上的哪个点。精确 shape 锚点按其存储的归一化坐标定位非精确锚点使用impreciseShapeAnchor都经由 shape 的 page transform 计算因此 pin 能跟随图形旋转而不是留在包围盒里。shapeAnchorAt(editor, shapeId, page, precise)为一个页面点构造 shape 锚点把点击点换算成图形自身坐标系中的 (0–1) 偏移。零宽/零高图形退回中心(0.5, 0.5)。registerCommentAnchorLifecycle(editor)注册锚点的生命周期处理例如 pin 所在图形被删除时将锚点转换为 point 等。另有focusThread(editor, thread)打开线程并让它进入视野——必要时先切换页面再居中其 pinrevealThread(editor, threadOrCommentId)在画布上“揭示”线程若被聚合成徽标会先放大展开对应状态可见下文openThreadId。三、入口配置CommentTool 与 CommentingOptions3.1 把评论工具注册进编辑器评论能力的唯一静态入口是CommentTool。它是一个继承StateNode的状态节点comment-tool.tsx静态字段id comment、initial idle包含三个子状态idle、pointing、dragging。它的行为是按下时立即在指针处打开评论输入框composer并让它跟随指针移动“像贴便签一样”松手时决定锚点压在图形上则用shapeAnchorAt生成图形锚点否则落一个 point 锚点若enableRegions开启且拖拽超过阈值则切换进dragging状态绘制区域矩形松手时生成 region 锚点pin 落在释放方向对应的角上放置只打开 composer真正写记录发生在评论发布时工具在 composer 打开期间保持激活发布后回到 select。把CommentTool接入画布与 tldraw 既有约定如ShapeUtil.configure一致——先在tools里注册配置后的子类同时用commentToolOverrides注册 UI 入口图标、快捷键cimport { Tldraw } from tldraw import { CommentTool, CanvasComments, commentToolOverrides, } from tldraw/commenting function App() { return ( Tldraw tools{[CommentTool.configure({ history: ignore, enableClustering: false })]} overrides{commentToolOverrides} CanvasComments currentUserId{currentUserId} resolveAuthor{resolveAuthor} / /Tldraw ) }相关导出还有commentTools[CommentTool]数组和commentToolOverrides: TLUiOverrides。一旦注册tldraw 的DefaultQuickActionsContent就会显示评论按钮点击后通过editor.setCurrentTool(comment)进入该工具。3.2 CommentingOptions全部可配置项CommentTool.configure(options)会基于默认值合并出该 editor 的CommentingOptions注册为工具的options字段多次configure可以链式叠加其中components是浅合并而非整体替换保证后一次配置不会丢掉前一次已经设置的其他组件槽位见 comment-tool.tsx 的mergeCommentingOptions。选项是静态配置只传一次活的、会变化的值当前用户、作者解析、已读回调走CommentingContext见第四节。getCommentingOptions(editor)与useCommentingOptions()可随时读取合并结果——前者通过editor.getStateDescendant(comment)找到已注册工具并读取其options未注册时回退到defaultCommentingOptions后者是前者的 React hook 封装由于选项按 editor 固定useMemo后不需要响应式。全部选项及默认值defaultCommentingOptions见 options.ts汇总如下选项类型/默认值含义historyignore默认评论写操作如何与 undo 栈交互。默认ignore评论刻意不可撤销record在多玩家场景是雷区——撤销一次删除会把协作方已经移除的线程“复活”仅单机安全。dragHistoryundefined默认回退到history专门控制 pin 拖拽改锚点这类“空间编辑”的 history 模式可合理跟随图形移动进入撤销栈。enableClusteringtrue相机缩小时把相邻 pin 折叠成计数徽标count badge。allowMultipleReactionstrue同一用户能否对一条评论同时持有多个 Emoji 反应。true是 Slack 模型各 Emoji 独立开关false是单选选新 Emoji 替换旧反应。注意仅客户端强制服务端两种都收。isAllowedReaction(token)isAllowedReactionEmoji某个 token 能否被添加为反应防止脚本客户端写入选择器永远不会提供的垃圾值。移除不受此检查不在调色板上的反应也必须能清除。配合自定义ReactionPalette使用。enableRegionsfalse是否允许拖拽画出“区域锚点”评论虚线框 角落 pin。关闭时评论只挂在点与图形上。canCommentundefined当前观看者能否参与评论写、改、删、解决、拖 pin。回调在渲染期经useCanComment调用信号读取会被追踪抛异常会被记录并按false处理不让整个评论层崩掉。未设置时currentUserId非空即允许。canModifyCommentundefined针对特定记录的写权限编辑/删除某条评论、删除某条线程回调入参为CommentModificationContext。未设置时走defaultCanModifyComment见 5.4。impreciseShapeAnchor{ x: 1, y: 0 }非精确图形 pin 落在图形内的归一化 (0–1) 位置默认右上角。shouldBePrecise(editor, ctx)() true落在图形上的评论是精确钉在点击处还是锚定整图渲染在impreciseShapeAnchor。入参ShapeCommentPrecisionContext携带shapeId、point、altKey。默认总是精确只影响新放置已有锚点按存储渲染。components{}组件覆盖见 3.3。CommentModification是三种写操作的可辨识联合{ action: edit-comment, comment }、{ action: delete-comment, comment }、{ action: delete-thread, thread }。CommentModificationContext { editor, currentUserId } CommentModification。权限细节源码注释原话要点解析/重开/反应/移动 pin 不属于任何人所以只受canComment这一个门槛约束只有带“归属”的写操作才走canModifyComment。canModifyComment恒在canComment之后被检查——完全无权参与评论的观看者不会获得任何操作按钮。3.3 CommentingComponents组件插槽components: CommentingComponents提供一组组件槽位每个槽替换内置的一块 UI留空即保持默认。所有槽的 props 都由源码options.ts明确定义插槽Props作用CommentBody{ comment: TLComment }替换默认富文本正文渲染。PinContent{ thread, comments }替换 pin 内默认的作者首字母内容。ThreadPreview{ comment }替换侧栏行预览默认纯文本。ThreadRowCommentListItemRenderProps { thread }替换整个侧栏行。默认CommentListItem已导出——只加个未读圆点/状态徽章时可直接把 props 展开进它。仅改预览文本用ThreadPreview即可。ThreadActions{ thread, comments }在打开的线程头部追加额外控件在自带的 resolve/dismiss 之前不是替换。宿主提供getThreadHref时已内置“复制链接”。ReactionContent{ token: string }给定 token 画某个反应的视觉。默认把 token 字符串渲染给系统 Emoji 字体可改为自定义img/SVG。token 才是存储与同步的内容这只管怎么画。ReactionPaletteEmojiPickerProps“添加反应”按钮打开的面板默认EmojiPicker网格。与ReactionContent、isAllowedReaction配套。ReactionTooltipReactionTooltipPropschildrenreactors悬停显示“谁用这个 Emoji 反应过”的气泡整个外观归它管只想改措辞就翻译comments.reacted-*字符串。ComposerFallback{ context: pending \| thread }观看者不能评论时见canComment显示在 composer 位置上的替代物。context表示渲染面打开的线程弹层thread或评论工具放置弹层pending。未设置时这些面什么都不渲染。四、宿主接线CommentingContextCommentingContextcontext.ts是评论层的“活”输入当前用户是谁、作者 id 如何变成显示名、已读状态、提及名单。它通过 props 传给每个评论面。要点如下export interface CommentingContext { currentUserId: string | null resolveAuthor(id: string): CommentAuthor | undefined onPostComment?(comment: TLComment): void isCommentUnread?(commentId: TLCommentId): boolean onCommentsRead?(commentIds: TLCommentId[]): void getMentionSuggestions?(query: string): MentionMember[] | PromiseMentionMember[] renderMentionSuggestion?(member: MentionMember): ReactNode getThreadHref?(threadId: TLCommentThreadId): string | undefined }currentUserId登录用户 idnull表示只读观看者只有登录用户能发评论。resolveAuthor作者 id → 显示信息解析不到返回undefined。onPostComment任何评论发布后新线程首条或回复回调适合接后端写接口。isCommentUnread/onCommentsRead已读状态判定onCommentsRead把打开线程弹层中展示的所有未读评论 id批量上报一次报告一批避免每条评论一次写入依赖isCommentUnread判断哪些未读——宿主借此记录 read receipt。getMentionSuggestions/renderMentionSuggestioncomposer 里查询的成员名单可同步可异步与自定义名单行渲染。getThreadHref线程的宿主 URL。有它时侧栏行渲染为链接ctrl/cmd 点击、中键可新标签打开普通点击仍就地选中线程不跟随 href。源码还给出一个重要实践同一套 context 会被多个面消费CanvasComments与CanvasCommentsSidebar同时挂载时因此应构建一次、spread 进每一处const commenting: CommentingContext { currentUserId, resolveAuthor, isCommentUnread } CanvasComments {...commenting} / CanvasCommentsSidebar {...commenting} /五、记录读写读取、hooks、mutation 与权限5.1 非响应式读取函数comment-store.ts 提供一组类型安全的记录读取注意“包括软删除记录”与“应渲染的记录”的差别函数语义getCommentRecord(editor, id)按 id 读一条评论记录非评论记录/不存在返回undefined。getCommentThreads(editor)全部线程含软删除、已清空、等待服务端清除的——UI 不应该渲染这些应使用getLiveCommentThreads。getComments(editor)全部评论同样含软删除残留。getLiveComments(editor)应渲染的评论过滤掉isDeleted。计数与列表都要基于它构建。getLiveCommentThreads(editor)应渲染的线程未软删除、且仍持有至少一条存活评论。被最后一条评论删除清空的线程会残留到服务端清除落地。getCommentReactions(editor)当前 store 中所有评论反应。以上都非响应式在 React 里需要包useValue或直接用下面的 hooks。5.2 响应式 hooksHook语义useComments(editor)/useCommentThreads(editor)响应式读取“存活”评论/线程useComments另按最旧在前排序。useThreadComments(editor, threadId)某条线程下的存活评论。useCommentReactions(editor, commentId)某条评论上的反应。useCommentingOptions()读取合并后的选项见 3.2。5.3 Mutation 函数与 history 策略所有写操作集中在 comment-mutations.ts统一经由commitCommentMutation内部函数在editor.run(..., { history })中提交。核心函数函数行为putCommentRecords(editor, records)按配置的 history 模式写入记录。可用于种子导入或保存编辑结果。removeCommentRecords(editor, ids)按 id 硬删除。但硬删除很少是你想要的内置 UI 走软删除且强制执行每条记录权限的服务端会直接否决硬删除仅在本地未同步 store 或丢弃反应时使用见toggleCommentReaction。editComment(editor, comment, body)替换评论正文并打上editedAt时间戳byline 会渲染“(edited)”标记。示例editComment(editor, comment, toRichText(Actually, make it dashed))。deleteComment(editor, comment)软删除置isDeleted。删除线程的最后一条评论会关闭该线程并留待服务端清除。永远不可撤销见下。deleteThread(editor, thread)软删除整条线程及其会话。resolveThread(editor, thread, userId)写入resolved: { at, by }。已解决线程保留 pin对勾样式从侧栏隐藏直到打开“show resolved”。reopenThread(editor, thread)清除 resolution对未解决线程/已消失线程是 no-op。toggleCommentReaction(editor, comment, userId, emoji, now?)开关某个 Emoji 反应。三个容易踩坑的实现细节值得强调所有动词“读最新、写最小”传给函数的是记录快照但记录会移动——删除被钉住的图形会把锚点转成 point、重设父级会迁移线程、拖拽会改锚点。直接把调用方快照写回会把这些字段回滚给所有人。因此readLatest总以当前 store 中的版本为准已消失的记录直接 no-op。删除类写操作恒为ignore无论history怎么配。因为软删除标记在服务端是“一次写入”撤销清除它会被服务端否决而不是恢复内容。history模式冲突会抛错editor.run的 history 选项不可叠加嵌套 run 会覆盖外层模式因此嵌套且模式不一致的提交直接抛出参见commitCommentMutation的错误信息。5.4 权限判定canComment / canModifyComment提供“一次调用 React hook”两套形态getCanComment(editor, currentUserId)/useCanComment(currentUserId)观看者能否参与评论。未配置时等于currentUserId ! null回调抛异常则拒绝。getCanModifyComment(editor, currentUserId, modification)/useCanModifyComment(currentUserId, modification)能否对特定记录执行特定写操作。useCanModifyComment的依赖是[editor, currentUserId, modification.action, record]——评论记录不可变记录本身变了才是检查对象变了。defaultCanModifyComment(ctx)默认规则是“归属者拥有写权”——评论由其作者编辑/删除、线程由其创建者删除、无身份观看者一概无权。它被导出以便自定义回调“加宽”而非重述默认例如CommentTool.configure({ canModifyComment: (ctx) (ctx.action ! edit-comment isModerator(ctx.currentUserId)) || defaultCanModifyComment(ctx), })六、评论工具与 UI 状态atoms hooks评论层有一组基于EditorAtom的全局 UI 状态全部集中在 state.ts 并带配套读写函数/hooks状态值域配套读写配套 hook语义commentsHiddenbooleantoggleCommentsHidden(editor)useCommentsHidden()pin 是否隐藏ShiftC 切换见CanvasComments。commentsSidebarOpenbooleantoggleCommentsSidebar(editor)useCommentsSidebarOpen()评论侧栏开合。openThreadIdnull \| string—由打开/关闭动作维护useOpenThreadId()当前打开线程的 id。sidebarFiltersSidebarFilters—useSidebarFilters()侧栏过滤条件见下。reveal 待处理请求null \| stringrevealThread(editor, id)useRevealThreadPending()待揭示的线程/评论 idgetRevealThreadPending(editor)为非响应式读取。SidebarFilters四个布尔字段onlyCurrentPage只看当前页、onlyMine只看我的、onlyUnread只看未读、showResolved显示已解决默认值DEFAULT_SIDEBAR_FILTERS。侧栏排序辅助SidebarRow{ item, lastActivity }、sortSidebarRows(rows)。七、全家桶覆盖层CanvasComments 与侧栏7.1 CanvasCommentsCanvasComments(props: CommentingContext)comments-overlay.tsx是“装好电池”的评论层把每条线程钉在其锚点、点击打开线程弹层带回复 composer、在评论工具放置处显示 composer直接读写editor.store。所有可见部分都是CommentTool.configure({ components })的插槽且其拼装的零件CommentPin、CommentThread、CommentComposer、hooks、工具全部导出想自建可以拆开重拼。源码中值得了解的行为许可证门槛内部先useCommentingEnabled()未授权时返回null不渲染。隐藏与快捷键ShiftC切换 pin 可见性物理键KeyC在输入框/文本域/contenteditable 内不触发Escape 折叠打开的线程捕获阶段避开 mention picker 打开时。打开线程的 pin 特判无论是否隐藏/已解决过滤打开中的线程总会渲染避免从它自己的弹层解决时 pin 在脚下消失聚簇开启时打开线程走独立渲染槽避免弹层被挂载两次。渲染宿主通过EditorPortal渲染——pin 落在协作者光标之下、弹层落在 UI 面板之上而没有任何单一画布层能同时覆盖两者。聚簇clustering开启时由cluster-model计算聚簇表格与淡入淡出节点、pin-stacking处理同锚点叠置同锚点 pin 在任何缩放下都重合渲染为一个计数徽标栈关闭时每个线程渲染自己的 pin。7.2 CanvasCommentsSidebar 及其配套CanvasCommentsSidebar(props: CanvasCommentsSidebarProps)评论线程的侧栏列表。CanvasCommentsSidebarProps extends PickCommentingContext, currentUserId | getThreadHref | isCommentUnread | resolveAuthor另有两个展示插槽empty?: ReactNode、header?: ReactNode。CommentsFilterMenu({ canFilterByAuthor, canFilterByUnread })过滤菜单按作者、按未读。CommentsMenuItem()/CommentsOverflowMenu()/CommentsVisibilityToggle()顶栏 UI 的评论菜单项、溢出菜单、可见性开关——通常配合commentToolOverrides一起接入默认 UI。八、展示型 UI 组件与反应体系8.1 线程与列表组件CommentThread({ comments, header, headerActions, resolvedBanner, composer, footer, renderComment })完整线程视图。comments是CommentCardProps[]composer是CommentComposerPropsresolvedBanner可渲染已解决横幅。CommentCard({ author, body, date, you, edited, actions, footer })单条评论卡。you: boolean标识是否本人的评论。CommentComposer({ author, placeholder, value, onChange, onSubmit, sendLabel, onArrowUpWhenEmpty, disabled, autoFocus, leading, getMentionSuggestions, renderMentionSuggestion })富文本评论输入框。支持受控value: TLRichText、提及建议同步/异步、发送标签自定义、空状态下按上箭头回调、leading前置内容。CommentsList({ items, onSelect, header, headerAction, empty, resolvedLabel, renderItem })CommentListItem(props)可复用列表容器。CommentListItemProps包含id、author、preview、date、resolved、page、count、selected、reactions、hrefCommentListItemRenderProps额外有onSelect、resolvedLabel。CommentPin({ children, resolved, open })/CountBadge({ count, open })/EmptyState({ message })/SendButton({ label, disabled, onClick })/Byline({ author, date, edited })基础原子 UI。CommentBody({ richText, resolveName })把TLRichText渲染成富文本resolveName把 mention 的 id 解析为显示名在 UI 层之上封装了默认渲染。richTextToPlaintext(body, resolveName?)富文本转纯文本侧栏预览等场景。formatFullDateTime(iso, locale?)/formatRelativeTime(iso, locale?)时间格式化相对时间如“3 分钟前”。isOpenInNewTabClick(e)判断鼠标事件是否为“新标签打开”点击ctrl/cmd/中键供链接行使用。8.2 Emoji 反应体系反应体系由一条“token 存储”主线和一套分层 UI 组成存储与判定DEFAULT_REACTION_EMOJI: string[]默认表情面板、isAllowedReactionEmoji(emoji, palette?)默认允许集检查。汇总模型ReactionSummaryInput { createdAt, emoji, userId }→summarizeReactions(reactions, currentUserId?, resolveName?)→ReactionSummary { emoji, count, active, reactors }ReactionReactor { name, you }。单反应Reaction({ emoji, count, active, reactors, enableHoverList, renderReaction, ReactionTooltip, onClick })。反应组Reactions({ reactions, onToggle, canReact, enableHoverList, renderReaction, ReactionTooltip })返回null条件由内部决定无反应且不可添加时。选择器EmojiPicker({ emoji, selected, onSelect, renderReaction })与ReactionPicker({ emoji, selected, onSelect, renderReaction, palette: Palette, menuId, className })后者接受自定义palette组件。默认渲染defaultRenderReaction(token): ReactNode、DefaultReactionTooltip({ reactors, children })、DefaultReactionTooltipContent({ reactors })RenderReaction (token) ReactNode。画布层接线CommentReactions({ comment, currentUserId, resolveName })评论上的反应行、CommentReactionPicker({ comment, currentUserId, emoji })评论上加反应的按钮无权时返回null、hooksuseCommentReactions与命令toggleCommentReaction。九、一个最小可运行示例综合前八节把一个完整评论层挂进 tldraw 的最简骨架如下仅示意接线关系不涉及存储持久化import { useCallback } from react import { Tldraw, type TLComment, type TLCommentThreadId } from tldraw import { CanvasComments, CanvasCommentsSidebar, CommentTool, commentToolOverrides, type CommentAuthor, } from tldraw/commenting const currentUserId user_me // 作者 id → 显示信息实际应从成员数据/服务端解析 const resolveAuthor (id: string): CommentAuthor | undefined id currentUserId ? { id, name: Me } : undefined const isCommentUnread (id: string) unreadSet.has(id) // 你的已读数据源 const onPostComment useCallback((comment: TLComment) { // 在这里把新评论写入你的后端 }, []) const getThreadHref (threadId: TLCommentThreadId) /board?thread${threadId} const getMentionSuggestions (query: string) memberRoster.filter((m) m.name.includes(query)) export function CommentableBoard() { return ( Tldraw tools{[CommentTool.configure({})]} overrides{commentToolOverrides} CanvasComments currentUserId{currentUserId} resolveAuthor{resolveAuthor} isCommentUnread{isCommentUnread} onPostComment{onPostComment} getMentionSuggestions{getMentionSuggestions} getThreadHref{getThreadHref} / CanvasCommentsSidebar currentUserId{currentUserId} resolveAuthor{resolveAuthor} isCommentUnread{isCommentUnread} getThreadHref{getThreadHref} / /Tldraw ) }要点回顾CommentTool.configure({})提供画布放置能力commentToolOverrides提供工具按钮与c快捷键CanvasComments画 pin/弹层/composerCanvasCommentsSidebar提供可过滤的线程列表二者共享同一份CommentingContext。十、查阅完整 API 的入口若需要逐符号确认签名含所有 props 的确切可选性本仓库中的权威清单是 packages/commenting/api-report.api.md由 API Extractor 自动生成693 行全文标注每个导出为public。对应实现可在 packages/commenting/src/index.ts 查看分层导出、在 packages/commenting/src/canvas/options.ts 查看选项与权限、在 packages/commenting/src/canvas/comment-mutations.ts 查看全部写操作。src/canvas下还带有成体系的测试文件如 comment-mutations.test.ts、comment-tool.test.ts、comments-overlay.test.ts、anchor-lifecycle.test.ts是观察每条 mutation、每个状态机与锚点生命周期真实行为的绝佳参考。综合来看tldraw/commenting的公共 API 划分清晰展示层零依赖可复用画布层围绕 store 记录提供工具、状态、hooks 与覆盖层配置经CommentTool.configure静态注入、运行期数据经CommentingContext传入。理解这四根支柱就能在任意 React tldraw 应用中快速落地一套支持富文本、提及、反应、已读与聚簇的协作评论系统。【免费下载链接】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),仅供参考