import { CommentAnnotation } from '@veltdev/types'; import { Mark, Editor, Extension } from '@tiptap/core'; /** * Common type definitions shared across the entire codebase. * These types are SDK-agnostic and represent domain concepts. */ /** * Context object for comment annotations. * Contains text editor configuration and any additional context data. */ interface CommentAnnotationContext { textEditorConfig?: { text: string; occurrence: number; editorId?: string; targetTextNodeId?: string; }; [key: string]: unknown; } /** * Request interface for addComment function. * Matches legacy AddCommentRequest interface. */ interface AddCommentRequest { editorId?: string; editor: unknown; context?: CommentAnnotationContext; } /** * Request interface for renderComments function. * Matches legacy RenderCommentsRequest interface. */ interface RenderCommentsRequest { editor: unknown; editorId?: string; commentAnnotations?: CommentAnnotation[]; } /** * Add comment feature module. * * Purpose: End-to-end add comment orchestration. * Orchestrates adapters and core to implement the add comment flow. * * Key responsibilities: * - Get Velt comment element * - Extract selection context from editor * - Create comment via Velt SDK * - Apply mark to editor document * - Update state with annotation data * * Dependencies: * - adapters/velt.ts (for Velt SDK calls) * - adapters/host/doc.ts (for selection extraction, editor setup, and selection reset) * - adapters/host/marks.ts (for mark application) * - adapters/host/storage.ts (for mark application configuration) * - core/state.ts (for state updates) * - types/common.ts (for CommentAnnotationContext) * * IMPORTANT: This module MUST NOT import editor or Velt types directly. * All SDK access goes through adapters. */ /** * Adds a comment to the currently selected text in the editor. * * This is the main entry point for adding comments. It handles: * - Extracting selection context from the editor * - Creating the comment via Velt SDK * - Applying marks to the editor if configured * - Updating state with annotation data * * @param request - Object containing editorId (optional), editor, and context (optional) * @returns Promise that resolves when the comment is added (or fails silently) * * @example * ```typescript * // Simple usage * await addComment({ editor }); * * // With editor ID * await addComment({ editorId: 'my-editor', editor }); * * // With custom context * await addComment({ * editorId: 'my-editor', * editor, * context: { * userId: 'user123', * metadata: { source: 'web-app' } * } * }); * ``` * * @remarks * - Requires text to be selected in the editor * - Requires Velt SDK to be available (window.Velt) * - Marks are only applied if persistVeltMarks is FALSE in extension config (matching legacy behavior) * - Fails silently if Velt SDK is unavailable or selection is invalid */ declare function addComment({ editorId, editor, context: clientContext, }: AddCommentRequest): Promise; /** * Render comments feature module. * * Purpose: Public API entry point for rendering comment annotations. * Orchestrates state updates, subscriptions, and delegates to commentRenderer. * * Key responsibilities: * - Update state with new annotations * - Subscribe to selected annotations from Velt * - Delegate rendering logic to commentRenderer * * Dependencies: * - adapters/velt.ts (for Velt SDK subscription) * - adapters/host/doc.ts (for editor setup) * - core/state.ts (for state management) * - features/commentRenderer.ts (for rendering logic) * - types/velt.ts (for CommentAnnotation) * - utils/console.ts (for logging) * * IMPORTANT: This module MUST NOT import editor or Velt types directly. * All SDK access goes through adapters. */ /** * Renders comment annotations as marks in the editor. * * This function renders comment annotations as visual marks in the editor. * It filters annotations for the specific editor and applies marks accordingly. * * @param request - Object containing editor, editorId (optional), and commentAnnotations (optional) * * @example * ```typescript * // Simple usage * renderComments({ editor }); * * // With editor ID * renderComments({ editorId: 'my-editor', editor }); * * // With annotations * renderComments({ * editorId: 'my-editor', * editor, * commentAnnotations: [ * { * annotationId: 'ann-123', * context: { * textEditorConfig: { * text: 'Hello world', * occurrence: 1, * editorId: 'my-editor' * } * } * } * ] * }); * ``` * * @remarks * - Only annotations with matching editorId are rendered * - Automatically subscribes to selected annotations changes * - Filters out terminal/resolved comments unless they're selected * - Removes marks when comments become resolved * - Re-applies marks when resolved comments are selected */ declare const renderComments: ({ editor, editorId, commentAnnotations, }: RenderCommentsRequest) => void; /** * Host adapter type definitions. * These types represent editor-specific concepts. * NOTE: This is the ONLY place where editor-specific types should be defined. */ /** * Extension configuration options for VeltCommentsExtension. */ interface VeltCommentsExtensionConfig { HTMLAttributes: Record; persistVeltMarks: boolean; editorId?: string; extensionName?: string; } /** * Core extension module. * * Purpose: Extension factory that wires everything together. * This is the ONLY place where Mark.create() is called. * * Key responsibilities: * - Create Mark extension * - Wire up transaction handler to updateContent feature * - Register editor in registry on extension creation * - Expose commands and storage * * Dependencies: adapters/host/marks.ts, features/updateContent.ts, core/registry.ts */ /** * Creates the Mark extension. * This is the main extension that integrates Velt comments with the editor. */ declare const VeltCommentsExtension: Mark; /** * Public and internal types for suggestion mode. */ /** What a suggestion proposes. */ type SuggestionKind = 'insertion' | 'deletion' | 'replacement' | 'format-add' | 'format-remove'; /** Passed to the `summary` option to build the text of a suggestion's first comment. */ interface SuggestionSummaryInput { kind: SuggestionKind; /** Text the suggestion removes (deletion / replacement) or the formatted text (format changes). */ oldText: string; /** Text the suggestion adds (insertion / replacement). Empty for format changes. */ newText: string; /** The format mark, for format changes. */ markName?: string; } /** Why a commit to Velt did not happen. */ interface SuggestionErrorContext { stage: 'commit' | 'apply'; targetId?: string; kind?: SuggestionKind; } /** Options of the `TiptapVeltSuggestions` extension. */ interface TiptapVeltSuggestionsOptions { /** Idle time (ms) after the last edit before pending edits are committed. Default 2000. */ debounceMs: number; /** Also commit when the editor loses focus. Default true. */ commitOnBlur: boolean; /** Also commit edits in other blocks when the caret moves to another block. Default true. */ commitOnBlockChange: boolean; /** * Format marks tracked as suggestions while suggesting. `false` turns format tracking off. * Default: bold, italic, underline, strike (not `code`: Tiptap's inline code cannot carry other marks). */ trackFormats: readonly string[] | false; /** Builds the text of the suggestion's first comment. Default: an English one-liner. */ summary: ((input: SuggestionSummaryInput) => string) | null; /** Colour of an author's suggestions. Default: a stable colour from the user id. */ getAuthorColor: ((userId: string) => string) | null; /** Called when a suggestion could not be committed or applied. */ onError: ((error: unknown, context: SuggestionErrorContext) => void) | null; /** Inject the default styles (CSS variables `--velt-suggestion-color`). Default true. */ injectCSS: boolean; } /** * Keeps one editor's suggestion marks and Velt in step. * * - Follows Velt's suggestion mode and signed-in user (→ plugin state). * - Commits the current user's pending clusters (idle / blur / block change / page hidden): * registers a getter, calls `commitSuggestion`, then gives the cluster its target id. A cluster is * frozen while it is committed, so new keystrokes start a new one. * - Applies accept / reject / stale from Velt's events, and reconciles the document with Velt's * suggestion list (a suggestion resolved while this editor was closed is applied on load). * * Talks to Velt only through the `SuggestionVeltPort` it is given. */ interface Subscribable { subscribe: (next: (value: T) => void) => { unsubscribe: () => void; }; } /** The Velt surface the sync needs (implemented over `window.Velt` by the adapter). */ interface SuggestionVeltPort { registerTarget: (config: { targetId: string; getter: () => unknown; }) => void; unregisterTarget: (targetId: string) => void; commitSuggestion: (config: { targetId: string; newValue: unknown; summary?: string; metadata?: Record; }) => Promise; enableSuggestionMode: () => void; disableSuggestionMode: () => void; isSuggestionModeEnabled$: () => Subscribable; getSuggestions$: () => Subscribable | null | undefined>; on: (event: 'suggestionApproved' | 'suggestionRejected' | 'suggestionStale') => Subscribable<{ suggestion?: { targetId?: string; }; } | null | undefined>; currentUser$: () => Subscribable<{ userId?: string; } | null | undefined> | null; } declare class SuggestionSync { private readonly editor; private readonly options; private readonly port; private subscriptions; private debounce; private lastBlock; /** pendingId → the target id it is committed under (fixed across retries). */ private targetIds; /** Target ids with a registered getter. */ private registered; private inFlight; /** Resolutions that arrived while their commit was still in flight (targetId → outcome). */ private earlyResolutions; /** Velt's latest suggestion list: content loaded later is reconciled against it. */ private lastList; private attached; private destroyed; constructor(editor: Editor, options: TiptapVeltSuggestionsOptions, port: SuggestionVeltPort); /** Starts following Velt. Call once Velt is loaded. */ attach(): void; destroy(): void; /** Commits every pending cluster of the current user. Resolves when the commits settle. */ flush(exceptPendingId?: string | null): Promise; /** Turns suggesting on, or flushes pending edits and then turns it off. */ setSuggesting(enabled: boolean): Promise; /** * Commits pending edits until none is left that was not tried yet — including edits typed while an * earlier commit was in flight. Each cluster is tried once, so a failing one cannot loop. */ private drain; private pluginState; private setPluginState; private freeze; private readonly onTransaction; private readonly onSelectionUpdate; private readonly onBlur; private readonly onVisibilityChange; /** The getter Velt reads for a target's current value (the text the suggestion replaces). */ private getter; private commit; /** Applies a resolution from Velt to this document (no-op when it has no such suggestion). */ private resolve; /** Velt's list → resolve committed marks whose suggestion is no longer pending; register getters for the rest. */ private reconcile; /** Every committed suggestion in the document has a getter; removed ones lose theirs. */ private syncGetters; private unregister; private reportError; } /** * `TiptapVeltSuggestions` — suggestion mode (tracked changes) backed by Velt suggestions. * * While Velt's suggestion mode is on, the signed-in user's edits are kept as pending suggestion * marks and committed to Velt as suggestions; accepting or rejecting one in Velt applies or drops * the change in the document. */ interface SuggestionsStorage { sync: SuggestionSync | null; attachTimer: ReturnType | null; } declare const TiptapVeltSuggestions: Extension; /** The document JSON with every pending (not yet committed) suggestion dropped. Use it to save content. */ declare const getContentWithoutPendingSuggestions: (editor: Editor) => Record; /** The document HTML with every pending (not yet committed) suggestion dropped. Use it to save content. */ declare const getHTMLWithoutPendingSuggestions: (editor: Editor) => string; /** Commits the current user's pending edits now; resolves when the commits have settled. */ declare const flushSuggestions: (editor: Editor) => Promise; /** The document range of a committed suggestion (by its Velt `targetId`), e.g. to scroll to it; null when absent. */ declare const getSuggestionRange: (editor: Editor, targetId: string) => { from: number; to: number; } | null; /** * Public API for Tiptap-Velt Comments extension. * * This module exports the public API surface for the v2 implementation. * * @example * ```typescript * import { TiptapVeltComments, addComment, renderComments } from 'tiptap-velt-comments'; * * // Use the extension * const editor = new Editor({ * extensions: [TiptapVeltComments.configure({ editorId: 'my-editor' })], * }); * * // Add a comment * await addComment({ editorId: 'editor-id', editor, context: { customData: 'value' } }); * * // Render existing comments * renderComments({ editorId: 'editor-id', editor, commentAnnotations: annotations }); * ``` */ declare module '@tiptap/core' { interface Commands { tiptapVeltComments: { setVeltComment: (annotationId?: string, multiThreadAnnotationId?: string, from?: number, to?: number) => ReturnType; }; tiptapVeltSuggestions: { /** Turns Velt suggestion mode on, or commits pending edits and then turns it off. */ setSuggesting: (enabled: boolean) => ReturnType; /** Commits the current user's pending edits now. */ flushSuggestions: () => ReturnType; /** Suggests replacing `[from, to)` with `text` (empty text = deletion, from === to = insertion) and commits it. */ suggestReplacement: (range: { from: number; to: number; }, text: string) => ReturnType; }; } } export { AddCommentRequest, CommentAnnotationContext, RenderCommentsRequest, SuggestionErrorContext, SuggestionKind, SuggestionSummaryInput, VeltCommentsExtension as TiptapVeltComments, VeltCommentsExtensionConfig as TiptapVeltCommentsOptions, TiptapVeltSuggestions, TiptapVeltSuggestionsOptions, addComment, flushSuggestions, getContentWithoutPendingSuggestions, getHTMLWithoutPendingSuggestions, getSuggestionRange, renderComments };