{
  "version": 3,
  "sources": ["../../src/canvas/comment-mutations.ts"],
  "sourcesContent": ["import {\n\tEditor,\n\tTLComment,\n\tTLCommentId,\n\tTLCommentReactionId,\n\tTLCommentThread,\n\tTLCommentThreadId,\n\tTLHistoryBatchOptions,\n\tTLRecord,\n\tTLRichText,\n} from 'tldraw'\nimport { getCommentRecord, getLiveComments, type TLCommentRecord } from './comment-store'\nimport { getCommentingOptions, type CommentingOptions } from './options'\nimport { openThreadId } from './state'\n\n/**\n * Every write to a comment record, and the undo/redo policy governing them.\n *\n * The file layers bottom-up: {@link commitCommentMutation} resolves the history mode and hands its\n * callback a writer, {@link putCommentRecords} and {@link removeCommentRecords} are the typed\n * writes that run under it, and the verbs below are those writes plus the one rule each carries.\n * Every verb takes the record it acts on as the identity of what to change, not the value to write\n * back \u2014 see {@link readLatest}.\n *\n * Posting carries no such rule, so it isn't a verb here: build the records with\n * `createCommentThread`/`createComment` and write them with {@link putCommentRecords}.\n */\n\n/**\n * Which history policy a comment write follows:\n *\n * - `mutation` \u2014 {@link CommentingOptions.history}: posts, replies, edits, resolves.\n * - `drag` \u2014 {@link CommentingOptions.dragHistory}, falling back to `history`: pin and region\n *   re-anchors, which are spatial edits a host may reasonably want undoable alongside a shape move.\n * - `delete` \u2014 always `'ignore'`, whatever the options say. A soft-delete flag is write-once\n *   server-side, so an undo clearing it would be vetoed and rebased rather than restore anything.\n *\n * @internal\n */\nexport type CommentMutationKind = 'delete' | 'drag' | 'mutation'\n\ninterface CommentMutationWriter {\n\tput(records: TLCommentRecord[]): void\n\tremove(ids: (TLCommentId | TLCommentReactionId | TLCommentThreadId)[]): void\n}\n\nconst activeCommentMutations = new WeakMap<Editor, { history: TLHistoryBatchOptions['history'] }>()\n\n/** The undo/redo mode a write of the given kind runs under. See {@link CommentMutationKind}. */\nfunction historyModeFor(\n\toptions: CommentingOptions,\n\tkind: CommentMutationKind\n): TLHistoryBatchOptions['history'] {\n\tswitch (kind) {\n\t\tcase 'delete':\n\t\t\treturn 'ignore'\n\t\tcase 'drag':\n\t\t\treturn options.dragHistory ?? options.history\n\t\tcase 'mutation':\n\t\t\treturn options.history\n\t}\n}\n\n/**\n * Commit a comment mutation with the configured undo/redo behavior, so the\n * {@link CommentingOptions.history} option governs whether it lands on the undo stack. Defaults to\n * `'ignore'`. See {@link CommentMutationKind} for what each kind resolves to.\n *\n * `editor.run`'s history option isn't additive \u2014 a nested run overwrites the enclosing mode \u2014 so\n * constituent records go through the callback's writer instead of opening a commit of their own,\n * which would quietly make a `drag` write non-undoable.\n *\n * Commits that nest are only a problem when they resolve to different modes, and then neither is\n * the right one to keep, so it throws. Matching modes have to nest: a store side effect runs inside\n * the write that triggered it, with no \"after the commit\" to defer to. (A `store.listen` handler\n * normally flushes on a later frame, so its writes open a commit of their own, but a synchronous\n * flush \u2014 as under test \u2014 lands it inside too.)\n * @internal\n */\nexport function commitCommentMutation<T>(\n\teditor: Editor,\n\tfn: (writer: CommentMutationWriter) => T,\n\tkind: CommentMutationKind = 'mutation'\n): T {\n\tconst history = historyModeFor(getCommentingOptions(editor), kind)\n\tconst enclosing = activeCommentMutations.get(editor)\n\tif (enclosing && enclosing.history !== history) {\n\t\tthrow new Error(\n\t\t\t`A comment mutation that records history as '${history}' can't run inside one recording it as '${enclosing.history}': one of the two modes would be silently discarded. Use the provided writer for constituent records, or run this operation after the enclosing one has committed.`\n\t\t)\n\t}\n\n\tactiveCommentMutations.set(editor, { history })\n\ttry {\n\t\tlet result: T\n\t\teditor.run(\n\t\t\t() => {\n\t\t\t\tlet isWriterActive = true\n\t\t\t\tconst assertWriterActive = () => {\n\t\t\t\t\tif (!isWriterActive) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'A comment mutation writer cannot be used after its commit has finished.'\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tresult = fn({\n\t\t\t\t\t\tput: (records) => {\n\t\t\t\t\t\t\tassertWriterActive()\n\t\t\t\t\t\t\teditor.store.put(records as unknown as TLRecord[])\n\t\t\t\t\t\t},\n\t\t\t\t\t\tremove: (ids) => {\n\t\t\t\t\t\t\tassertWriterActive()\n\t\t\t\t\t\t\teditor.store.remove(ids as unknown as TLRecord['id'][])\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t} finally {\n\t\t\t\t\tisWriterActive = false\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ history }\n\t\t)\n\t\treturn result!\n\t} finally {\n\t\tif (enclosing) {\n\t\t\tactiveCommentMutations.set(editor, enclosing)\n\t\t} else {\n\t\t\tactiveCommentMutations.delete(editor)\n\t\t}\n\t}\n}\n\n/**\n * Write comment records to the store, under the configured {@link CommentingOptions.history}\n * behavior. Defaults to `'ignore'`.\n *\n * Use it to seed or import threads, and to save an edit. To delete, prefer {@link deleteComment}\n * and {@link deleteThread}: comments are soft-deleted, and a synced server rejects the hard delete.\n *\n * @public\n */\nexport function putCommentRecords(editor: Editor, records: TLCommentRecord[]): void {\n\tcommitCommentMutation(editor, ({ put }) => put(records))\n}\n\n/**\n * Remove comment records from the store by id, under the configured\n * {@link CommentingOptions.history} behavior.\n *\n * This is a hard delete, which is rarely what you want: the built-in UI soft-deletes\n * ({@link deleteComment}, {@link deleteThread}) so the server can prune the records, and a server\n * enforcing per-record permissions vetoes a hard delete outright. Reach for this on a local,\n * unsynced store, or to drop a reaction (see {@link toggleCommentReaction}).\n *\n * @public\n */\nexport function removeCommentRecords(\n\teditor: Editor,\n\tids: (TLCommentId | TLCommentReactionId | TLCommentThreadId)[]\n): void {\n\tcommitCommentMutation(editor, ({ remove }) => remove(ids))\n}\n\n/**\n * The record as the store currently holds it, or `undefined` if it isn't there any more.\n *\n * A verb is handed a record, but that record is a snapshot and comment records move underneath it:\n * deleting a pinned shape converts the anchor to a point, reparenting rehomes the thread, a drag\n * re-anchors it. Writing the caller's snapshot back would revert those fields for everyone. `put`\n * is also an upsert, so a record a remote delete already removed would come back.\n *\n * So a verb reads what it's changing rather than trusting what it was given, and a record that's\n * gone is a no-op.\n */\nfunction readLatest<T extends TLComment | TLCommentThread>(\n\teditor: Editor,\n\trecord: T\n): T | undefined {\n\tconst current = getCommentRecord(editor, record.id)\n\t// Record ids carry their type, so a matching `typeName` means a record of exactly T.\n\treturn current?.typeName === record.typeName ? (current as T) : undefined\n}\n\n/**\n * Replace a comment's body and stamp it as edited, which renders the \"(edited)\" marker on its\n * byline. Editing is the author's to do by default ({@link CommentingOptions.canModifyComment}),\n * and a server enforcing per-record permissions rejects anyone else's. Widening one end without the\n * other leaves an edit that's offered and then rejected, so widen both.\n *\n * The body lands on the version the store currently holds, so a stale copy can't revert a later\n * change or re-create a removed comment \u2014 editing one of those does nothing.\n *\n * @example\n * ```ts\n * editComment(editor, comment, toRichText('Actually, make it dashed'))\n * ```\n *\n * @public\n */\nexport function editComment(editor: Editor, comment: TLComment, body: TLRichText): void {\n\tcommitCommentMutation(editor, ({ put }) => {\n\t\tconst current = readLatest(editor, comment)\n\t\tif (!current) return\n\t\tput([{ ...current, body, editedAt: Date.now() }])\n\t})\n}\n\n/**\n * Mark a thread resolved, stamping who resolved it and when. Resolved threads keep their pin (a\n * checked one) and are hidden from the sidebar until its \"show resolved\" filter is on.\n *\n * Only the resolution is written \u2014 the rest of the thread is read fresh, so a stale copy can't drag\n * a pin back. A no-op on a thread that's gone.\n *\n * @public\n */\nexport function resolveThread(editor: Editor, thread: TLCommentThread, userId: string): void {\n\tcommitCommentMutation(editor, ({ put }) => {\n\t\tconst current = readLatest(editor, thread)\n\t\tif (!current) return\n\t\tput([{ ...current, resolved: { at: Date.now(), by: userId } }])\n\t})\n}\n\n/**\n * Reopen a resolved thread, clearing the resolution. A no-op on a thread that isn't resolved, and\n * on one that's gone. Like {@link resolveThread}, it touches only the resolution.\n *\n * @public\n */\nexport function reopenThread(editor: Editor, thread: TLCommentThread): void {\n\tcommitCommentMutation(editor, ({ put }) => {\n\t\tconst current = readLatest(editor, thread)\n\t\tif (!current) return\n\t\tput([{ ...current, resolved: null }])\n\t})\n}\n\n/**\n * Delete a comment.\n *\n * This is a soft delete: it sets `isDeleted` rather than removing the record, and the server prunes\n * the comment and its reactions once the flag is persisted \u2014 so no client removes records it\n * doesn't own, and a server enforcing per-record permissions has a write it can check.\n *\n * Deleting is the author's to do by default; {@link CommentingOptions.canModifyComment} widens\n * that, as does its counterpart on the server.\n *\n * Never undoable, whatever {@link CommentingOptions.history} says: the flag is write-once\n * server-side, so an undo clearing it would be vetoed rather than bring the comment back.\n *\n * Deleting a thread's last comment closes it and leaves the thread record for the server to prune,\n * since the deleter may not be its creator. An already-deleted comment is a no-op.\n *\n * @public\n */\nexport function deleteComment(editor: Editor, comment: TLComment): void {\n\tcommitCommentMutation(\n\t\teditor,\n\t\t({ put }) => {\n\t\t\tconst current = readLatest(editor, comment)\n\t\t\t// Deleting twice is nothing to do rather than something to redo. The check below counts this\n\t\t\t// comment among the live ones, so it only reads as \"the last one\" while this delete takes it away.\n\t\t\tif (!current || current.isDeleted) return\n\t\t\tconst isLastInThread =\n\t\t\t\tgetLiveComments(editor).filter((c) => c.threadId === current.threadId).length <= 1\n\t\t\tif (isLastInThread && openThreadId.get(editor) === current.threadId) {\n\t\t\t\topenThreadId.set(editor, null)\n\t\t\t}\n\t\t\tput([{ ...current, isDeleted: true }])\n\t\t},\n\t\t'delete'\n\t)\n}\n\n/**\n * Delete a thread and, with it, the whole conversation.\n *\n * A soft delete on the same model as {@link deleteComment}: the server prunes the thread, its\n * comments, and their reactions once the flag is persisted. Deleting a thread is its creator's to\n * do by default ({@link CommentingOptions.canModifyComment}), and the write is never undoable.\n * Closes the thread if it's the open one; a pruned thread is a no-op.\n *\n * @public\n */\nexport function deleteThread(editor: Editor, thread: TLCommentThread): void {\n\tcommitCommentMutation(\n\t\teditor,\n\t\t({ put }) => {\n\t\t\tconst current = readLatest(editor, thread)\n\t\t\tif (!current) return\n\t\t\tif (openThreadId.get(editor) === current.id) {\n\t\t\t\topenThreadId.set(editor, null)\n\t\t\t}\n\t\t\tput([{ ...current, isDeleted: true }])\n\t\t},\n\t\t'delete'\n\t)\n}\n"],
  "mappings": "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",
  "names": []
}
