@tldraw/tlschema
Version:
tldraw infinite canvas SDK (schema).
8 lines (7 loc) • 19.4 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/records/TLComment.ts"],
"sourcesContent": ["import { BaseRecord, RecordId, createRecordMigrationSequence } from '@tldraw/store'\nimport { JsonObject } from '@tldraw/utils'\nimport { T } from '@tldraw/validate'\nimport { idValidator } from '../misc/id-validator'\nimport { richTextValidator, TLRichText } from '../misc/TLRichText'\nimport { CustomRecordInfo, createCustomRecordId, isCustomRecordId } from './TLCustomRecord'\nimport { TLPageId } from './TLPage'\nimport { TLShapeId } from './TLShape'\n\n/**\n * Where a comment thread is anchored on the canvas. Modeled as a discriminated union so new\n * anchor kinds can be added without breaking existing threads:\n *\n * - `shape` \u2014 pinned to a shape. `x`/`y` are normalized (0\u20131) within the shape's bounds, so the pin\n * keeps its spot as the shape moves, resizes, and rotates. `isPrecise` mirrors arrow bindings:\n * when false (the default) the pin sits at a consumer-defined spot instead, top-right out of the\n * box, and `x`/`y` are the remembered precise position\n * - `point` \u2014 pinned to a fixed point on the page, in page coordinates\n * - `region` \u2014 pinned to a rectangular area of the page, in page coordinates\n * - `page` \u2014 a page-level thread with no spatial anchor\n *\n * @public\n */\nexport type TLCommentAnchor =\n\t| { type: 'shape'; shapeId: TLShapeId; x: number; y: number; isPrecise: boolean }\n\t| { type: 'point'; x: number; y: number }\n\t| {\n\t\t\ttype: 'region'\n\t\t\tx: number\n\t\t\ty: number\n\t\t\tw: number\n\t\t\th: number\n\t\t\t/** The normalized (0\u20131) corner the pin sits on \u2014 where the creating drag was released.\n\t\t\t * Absent on older records; consumers fall back to a corner of their own choosing. */\n\t\t\tpinX?: number\n\t\t\tpinY?: number\n\t }\n\t| { type: 'page' }\n\n/**\n * A comment thread. The thread owns the anchor (where the conversation lives on the canvas) and\n * the resolution state; the messages themselves are `TLComment` records pointing at the thread\n * via `threadId`, ordered by `createdAt`. v1 threads are flat \u2014 no nested replies.\n *\n * Threads and comments are document records, but they're intended to be served through the sync\n * server's object-store lane: gated by the session's `objectAccess` rather than `isReadonly`\n * (so \"can comment but not edit\" is expressible), excluded from document snapshots and `.tldr`\n * exports server-side, and persisted in a separate lane from the main document.\n *\n * Opt-in: register with `createTLSchema({ records: commentSchemaRecords })` on the server and\n * the matching `records` option on the client \u2014 neither type is part of the default schema, and\n * both sides must register them identically.\n *\n * @public\n */\nexport interface TLCommentThread extends BaseRecord<'comment-thread', TLCommentThreadId> {\n\t/** The page the thread lives on. */\n\tpageId: TLPageId\n\t/** Where the thread is anchored on that page. */\n\tanchor: TLCommentAnchor\n\t/** Who started the thread. Client-supplied; sync servers are expected to stamp/verify it. */\n\tcreatedBy: string\n\tcreatedAt: number\n\t/** Resolution state: when and by whom the thread was resolved, or null while open. */\n\tresolved: { at: number; by: string } | null\n\t/**\n\t * Whether the thread is soft-deleted. Clients set the flag and leave the record in place. Sync\n\t * servers are expected to enforce it as write-once and creator-only, reject client hard\n\t * deletes, and drop flagged threads from future room loads.\n\t */\n\tisDeleted: boolean\n\tmeta: JsonObject\n}\n\n/** @public */\nexport type TLCommentThreadId = RecordId<TLCommentThread>\n\n/**\n * A single comment message within a thread. See `TLCommentThread` for the overall model and\n * sync/registration notes.\n *\n * Comment mutations are deliberately not undoable \u2014 create/edit them with\n * `{ history: 'ignore' }` to avoid multiplayer surprises like a comment reappearing after\n * someone else deleted it.\n *\n * @public\n */\nexport interface TLComment extends BaseRecord<'comment', TLCommentId> {\n\t/** The thread this comment belongs to. */\n\tthreadId: TLCommentThreadId\n\t/** Denormalized from the thread so per-page queries don't need a join. */\n\tpageId: TLPageId\n\t/** See `TLCommentThread.createdBy` \u2014 same server-stamping caveat applies. */\n\tauthorId: string\n\tcreatedAt: number\n\t/** Null until the comment is first edited. */\n\teditedAt: number | null\n\t/** Rich text body. Use `toRichText(...)` for plaintext input. */\n\tbody: TLRichText\n\t/** Whether the comment is soft-deleted. Same model as `TLCommentThread.isDeleted`. */\n\tisDeleted: boolean\n\tmeta: JsonObject\n}\n\n/** @public */\nexport type TLCommentId = RecordId<TLComment>\n\n/**\n * One person's emoji reaction to one comment.\n *\n * A reaction is its own record rather than a field on the comment: comment records are owner-only\n * for updates, and a shared field would make concurrent reactions race to overwrite each other.\n *\n * A user holds at most one record per (comment, emoji) pair, enforced by the derived id \u2014 see\n * `createCommentReactionId`. Limiting a user to one reaction overall is client-side policy (the\n * commenting package's `reactionMode`), not a property of this record.\n *\n * @public\n */\nexport interface TLCommentReaction extends BaseRecord<'comment-reaction', TLCommentReactionId> {\n\t/** The comment being reacted to. */\n\tcommentId: TLCommentId\n\t/** Denormalized from the comment, so a thread's reactions can be found without a join. */\n\tthreadId: TLCommentThreadId\n\t/** Denormalized from the comment \u2014 see `TLComment.pageId`. */\n\tpageId: TLPageId\n\t/** Who reacted. See `TLCommentThread.createdBy` \u2014 same server-stamping caveat applies. */\n\tuserId: string\n\t/** The emoji itself, as a string (e.g. `'\uD83D\uDC4D'`), not a shortcode. */\n\temoji: string\n\tcreatedAt: number\n\tmeta: JsonObject\n}\n\n/** @public */\nexport type TLCommentReactionId = RecordId<TLCommentReaction>\n\n/**\n * An emoji shortcode or literal. Bounded because reaction ids embed the emoji verbatim\n * (see {@link createCommentReactionId}), so an unbounded value means an unbounded record id.\n */\nconst emojiValidator = T.string.check((value) => {\n\tif (value.length === 0 || value.length > 64) {\n\t\tthrow new T.ValidationError(`Expected an emoji of 1-64 characters, got ${value.length}`)\n\t}\n})\n\nconst commentAnchorValidator: T.Validator<TLCommentAnchor> = T.union('type', {\n\tshape: T.object({\n\t\ttype: T.literal('shape'),\n\t\tshapeId: idValidator<TLShapeId>('shape'),\n\t\tx: T.number,\n\t\ty: T.number,\n\t\tisPrecise: T.boolean,\n\t}),\n\tpoint: T.object({\n\t\ttype: T.literal('point'),\n\t\tx: T.number,\n\t\ty: T.number,\n\t}),\n\tregion: T.object({\n\t\ttype: T.literal('region'),\n\t\tx: T.number,\n\t\ty: T.number,\n\t\tw: T.number,\n\t\th: T.number,\n\t\tpinX: T.number.optional(),\n\t\tpinY: T.number.optional(),\n\t}),\n\tpage: T.object({\n\t\ttype: T.literal('page'),\n\t}),\n})\n\n/**\n * Guard migrations for the comment record types. Each sequence starts with an identity migration\n * that has no `down`, so a session whose schema predates the comment types is rejected with\n * CLIENT_TOO_OLD instead of being sent records its store can't represent.\n */\nfunction createCommentGuardMigrations(\n\ttypeName: 'comment' | 'comment-thread' | 'comment-reaction',\n\textra: Parameters<typeof createRecordMigrationSequence>[0]['sequence'] = []\n) {\n\treturn createRecordMigrationSequence({\n\t\tsequenceId: `com.tldraw.${typeName}`,\n\t\trecordType: typeName,\n\t\tretroactive: true,\n\t\tsequence: [\n\t\t\t{\n\t\t\t\tid: `com.tldraw.${typeName}/1`,\n\t\t\t\tup: (record) => record,\n\t\t\t},\n\t\t\t...extra,\n\t\t],\n\t})\n}\n\n/**\n * Config for registering the `comment-thread` record type in a tldraw schema. Pass via\n * `commentSchemaRecords`; see `TLCommentThread`.\n *\n * @public\n */\nexport const commentThreadRecordConfig: CustomRecordInfo = {\n\tscope: 'document',\n\tmigrations: createCommentGuardMigrations('comment-thread', [\n\t\t{\n\t\t\t// Shape anchors gained normalized x/y + isPrecise; existing ones were imprecise (top-right).\n\t\t\tid: 'com.tldraw.comment-thread/2',\n\t\t\tup: (record) => {\n\t\t\t\tconst anchor = (record as any).anchor\n\t\t\t\tif (anchor?.type === 'shape' && anchor.x === undefined) {\n\t\t\t\t\t;(record as any).anchor = { ...anchor, x: 1, y: 0, isPrecise: false }\n\t\t\t\t}\n\t\t\t\treturn record\n\t\t\t},\n\t\t\tdown: (record) => {\n\t\t\t\tconst anchor = (record as any).anchor\n\t\t\t\tif (anchor?.type === 'shape') {\n\t\t\t\t\t;(record as any).anchor = { type: 'shape', shapeId: anchor.shapeId }\n\t\t\t\t}\n\t\t\t\treturn record\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// Region anchors gained an optional pin corner (where the creating drag released).\n\t\t\tid: 'com.tldraw.comment-thread/3',\n\t\t\tup: (record) => record,\n\t\t\tdown: (record) => {\n\t\t\t\tconst anchor = (record as any).anchor\n\t\t\t\tif (anchor?.type === 'region') {\n\t\t\t\t\tconst { pinX: _pinX, pinY: _pinY, ...rest } = anchor\n\t\t\t\t\t;(record as any).anchor = rest\n\t\t\t\t}\n\t\t\t\treturn record\n\t\t\t},\n\t\t},\n\t]),\n\tvalidator: T.object({\n\t\tid: idValidator<TLCommentThreadId>('comment-thread'),\n\t\ttypeName: T.literal('comment-thread'),\n\t\tpageId: idValidator<TLPageId>('page'),\n\t\tanchor: commentAnchorValidator,\n\t\tcreatedBy: T.string,\n\t\tcreatedAt: T.number,\n\t\tresolved: T.object({ at: T.number, by: T.string }).nullable(),\n\t\tisDeleted: T.boolean,\n\t\tmeta: T.jsonValue,\n\t}),\n}\n\n/**\n * Config for registering the `comment` record type in a tldraw schema. Pass via\n * `commentSchemaRecords`; see `TLComment`.\n *\n * @public\n */\nexport const commentRecordConfig: CustomRecordInfo = {\n\tscope: 'document',\n\tmigrations: createCommentGuardMigrations('comment'),\n\tvalidator: T.object({\n\t\tid: idValidator<TLCommentId>('comment'),\n\t\ttypeName: T.literal('comment'),\n\t\tthreadId: idValidator<TLCommentThreadId>('comment-thread'),\n\t\tpageId: idValidator<TLPageId>('page'),\n\t\tauthorId: T.string,\n\t\tcreatedAt: T.number,\n\t\teditedAt: T.number.nullable(),\n\t\tbody: richTextValidator,\n\t\tisDeleted: T.boolean,\n\t\tmeta: T.jsonValue,\n\t}),\n}\n\n/**\n * Config for registering the `comment-reaction` record type in a tldraw schema. Pass via\n * `commentSchemaRecords`; see `TLCommentReaction`.\n *\n * @public\n */\nexport const commentReactionRecordConfig: CustomRecordInfo = {\n\tscope: 'document',\n\tmigrations: createCommentGuardMigrations('comment-reaction'),\n\tvalidator: T.object({\n\t\tid: idValidator<TLCommentReactionId>('comment-reaction'),\n\t\ttypeName: T.literal('comment-reaction'),\n\t\tcommentId: idValidator<TLCommentId>('comment'),\n\t\tthreadId: idValidator<TLCommentThreadId>('comment-thread'),\n\t\tpageId: idValidator<TLPageId>('page'),\n\t\tuserId: T.string,\n\t\temoji: emojiValidator,\n\t\tcreatedAt: T.number,\n\t\tmeta: T.jsonValue,\n\t}),\n}\n\n/**\n * The `records` map to pass to `createTLSchema` / the client `records` option so comment\n * threads, comments, and reactions sync. Register the types together \u2014 one without the others\n * will fail schema validation on one side of the connection.\n *\n * @public\n */\nexport const commentSchemaRecords = {\n\t'comment-thread': commentThreadRecordConfig,\n\tcomment: commentRecordConfig,\n\t'comment-reaction': commentReactionRecordConfig,\n}\n\n/** @public */\nexport function createCommentThreadId(id?: string): TLCommentThreadId {\n\treturn createCustomRecordId('comment-thread', id) as TLCommentThreadId\n}\n\n/** @public */\nexport function createCommentId(id?: string): TLCommentId {\n\treturn createCustomRecordId('comment', id) as TLCommentId\n}\n\n/**\n * The id of one user's reaction to one comment, derived from the (comment, user, emoji) triple\n * rather than random, so the same triple always addresses the same record and two tabs converge\n * instead of racing to create duplicates. The sync authorizer leans on this, so it must be\n * injective: all three parts are URI-encoded before joining, so a `:` in any of them can't shift\n * the boundary and collapse two triples onto one id.\n *\n * @public\n */\nexport function createCommentReactionId(\n\tcommentId: TLCommentId,\n\tuserId: string,\n\temoji: string\n): TLCommentReactionId {\n\treturn createCustomRecordId(\n\t\t'comment-reaction',\n\t\t`${encodeURIComponent(commentId)}:${encodeURIComponent(userId)}:${encodeURIComponent(emoji)}`\n\t) as TLCommentReactionId\n}\n\n/**\n * Type guard for `TLCommentThreadId`. `isCommentId` rejects these \u2014 `comment-thread:` and\n * `comment:` differ in the character after `comment`, so the prefixes never overlap.\n *\n * @public\n */\nexport function isCommentThreadId(id: string): id is TLCommentThreadId {\n\treturn isCustomRecordId('comment-thread', id)\n}\n\n/**\n * Type guard for `TLCommentId`. See `isCommentThreadId`.\n *\n * @public\n */\nexport function isCommentId(id: string): id is TLCommentId {\n\treturn isCustomRecordId('comment', id)\n}\n\n/**\n * Type guard for `TLCommentReactionId`. See `isCommentThreadId`.\n *\n * @public\n */\nexport function isCommentReactionId(id: string): id is TLCommentReactionId {\n\treturn isCustomRecordId('comment-reaction', id)\n}\n\n/**\n * Create a new comment thread record. Pair with `createComment` for the thread's first message.\n *\n * @public\n */\nexport function createCommentThread(props: {\n\tpageId: TLPageId\n\tanchor: TLCommentAnchor\n\tcreatedBy: string\n\tnow?: number\n\tmeta?: JsonObject\n}): TLCommentThread {\n\treturn {\n\t\tid: createCommentThreadId(),\n\t\ttypeName: 'comment-thread',\n\t\tpageId: props.pageId,\n\t\tanchor: props.anchor,\n\t\tcreatedBy: props.createdBy,\n\t\tcreatedAt: props.now ?? Date.now(),\n\t\tresolved: null,\n\t\tisDeleted: false,\n\t\tmeta: props.meta ?? {},\n\t}\n}\n\n/**\n * Create a new comment record within a thread.\n *\n * @public\n */\nexport function createComment(props: {\n\tthreadId: TLCommentThreadId\n\tpageId: TLPageId\n\tauthorId: string\n\tbody: TLRichText\n\tnow?: number\n\tmeta?: JsonObject\n}): TLComment {\n\treturn {\n\t\tid: createCommentId(),\n\t\ttypeName: 'comment',\n\t\tthreadId: props.threadId,\n\t\tpageId: props.pageId,\n\t\tauthorId: props.authorId,\n\t\tcreatedAt: props.now ?? Date.now(),\n\t\teditedAt: null,\n\t\tbody: props.body,\n\t\tisDeleted: false,\n\t\tmeta: props.meta ?? {},\n\t}\n}\n\n/**\n * Create a reaction record for one user's reaction to one comment. The id is derived from the\n * comment and user (see `createCommentReactionId`), so re-reacting overwrites rather than adding.\n *\n * @public\n */\nexport function createCommentReaction(props: {\n\tcommentId: TLCommentId\n\tthreadId: TLCommentThreadId\n\tpageId: TLPageId\n\tuserId: string\n\temoji: string\n\tnow?: number\n\tmeta?: JsonObject\n}): TLCommentReaction {\n\treturn {\n\t\tid: createCommentReactionId(props.commentId, props.userId, props.emoji),\n\t\ttypeName: 'comment-reaction',\n\t\tcommentId: props.commentId,\n\t\tthreadId: props.threadId,\n\t\tpageId: props.pageId,\n\t\tuserId: props.userId,\n\t\temoji: props.emoji,\n\t\tcreatedAt: props.now ?? Date.now(),\n\t\tmeta: props.meta ?? {},\n\t}\n}\n"],
"mappings": "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",
"names": []
}