import { Editor } from '@tiptap/react'; import { default as React } from 'react'; import { CommentAnchor } from '../extensions/comment/comment-decoration-plugin'; import { CommentFloatingCard, EnsCache, InlineCommentData, InlineCommentDraft, InlineDraftLocation } from '../components/inline-comment/context/types'; import { EnsStatus } from '../components/inline-comment/types'; import { IComment } from '../extensions/comment'; import { CommentMutationMeta, CommentMutationType } from '../types'; import * as Y from 'yjs'; export interface CommentExternalDeps { editor: Editor | null; ydoc: Y.Doc; setActiveCommentId: (id: string | null) => void; focusCommentWithActiveId: (id: string) => void; setInitialComments?: React.Dispatch>; setUsername?: React.Dispatch>; onNewComment?: (comment: IComment, meta?: CommentMutationMeta) => void; onEditComment?: (commentId: string, meta?: CommentMutationMeta) => void; onEditReply?: (commentId: string, replyId: string, meta?: CommentMutationMeta) => void; onCommentReply?: (activeCommentId: string, reply: IComment) => void; onResolveComment?: (commentId: string, meta?: CommentMutationMeta) => void; onUnresolveComment?: (commentId: string, meta?: CommentMutationMeta) => void; onDeleteComment?: (commentId: string, meta?: CommentMutationMeta) => void; onDeleteReply?: (commentId: string, replyId: string) => void; onInlineComment?: () => void; onComment?: () => void; setCommentDrawerOpen?: (open: boolean) => void; connectViaWallet?: () => Promise; connectViaUsername?: (username: string) => Promise; ensResolutionUrl: string; commentAnchorsRef?: React.MutableRefObject; refreshCommentAnchorState?: () => void; /** * Derived anchor list for in-progress suggestion drafts. Maintained by the * store (not the consumer) — draft actions upsert into this ref whenever * state.drafts changes. The decoration extension reads this ref alongside * commentAnchorsRef to render both layers identically. */ draftAnchorsRef?: React.MutableRefObject; } /** * A suggestion draft is the viewer's in-progress proposed edit — kept local * until Submit. Drafts live in memory only (lost on refresh). Derived into * a CommentAnchor for decoration rendering via deriveDraftAnchor(). Most * suggestion types are derived at use time from hadDeletion + insertedText; * linkHref marks a link suggestion over the original selected text. */ export interface DraftSuggestion { id: string; anchorFrom: Y.RelativePosition; anchorTo: Y.RelativePosition; /** The text that was selected at draft creation (for Delete/Replace strikethrough). */ originalContent: string; /** Accumulated text the viewer has typed. Empty for a pure Delete draft. */ insertedText: string; /** Per-keystroke history for undo — each entry is one typed character. */ keystrokes: string[]; /** True when the draft was created via select+delete or select+type. */ hadDeletion: boolean; /** Pasted link href for a link suggestion. */ linkHref?: string; } type SuggestionDeleteDirection = 'backward' | 'forward'; export declare function isRangeDraft(draft: DraftSuggestion): boolean; type FloatingCardsUpdater = React.SetStateAction; type InlineCommentDataUpdater = Partial | ((prev: InlineCommentData) => Partial | InlineCommentData); type InlineDraftRecordMap = Record; type CreateInlineDraftOptions = { location?: InlineDraftLocation; tabId?: string; allowEmptySelection?: boolean; }; type CommentEditRequest = { requestId: string; kind: 'comment' | 'reply'; commentId: string; replyId?: string; text: string; }; type ReplyEditTarget = { kind: 'comment' | 'reply'; commentId: string; replyId?: string; originalText: string; }; type CommentEditCompletion = { nonce: number; kind: 'comment' | 'reply'; commentId: string; replyId?: string; }; type FocusCommentInEditorOptions = { source?: 'explicit-ui' | 'passive'; }; type ReconcileFloatingThreadsForActiveTabOptions = { hydrationReady: boolean; }; export declare const EXPLICIT_COMMENT_FOCUS_META = "inlineCommentExplicitFocus"; export interface CommentStoreState { initialComments: IComment[]; tabComments: IComment[]; activeComments: IComment[]; activeComment: IComment | undefined; activeCommentIndex: number; username: string | null; activeCommentId: string | null; activeTabId: string; isConnected: boolean; isLoading: boolean; isDDocOwner: boolean; /** Inline comments usable at all (not RTC, published, not disabled) — * synced from CommentStoreProvider's prop; drives insert.comment * enablement in useEditorCommands. */ isInlineCommentAvailable: boolean; onComment: (() => void) | null; setCommentDrawerOpen: ((open: boolean) => void) | null; connectViaWallet: (() => Promise) | null; connectViaUsername: ((username: string) => Promise) | null; isCommentActive: boolean; isCommentResolved: boolean; showResolved: boolean; reply: string; comment: string; openReplyId: string | null; selectedText: string; isCommentOpen: boolean; isBubbleMenuSuppressed: boolean; inlineCommentData: InlineCommentData; floatingCards: CommentFloatingCard[]; pendingPrehydrationFloatingThreadIds: string[]; /** In-progress suggestion drafts — keyed by suggestionId. Viewer-local, lost on refresh. */ drafts: Record; /** Suggestion draft currently under the focused editor cursor, if any. */ activeSuggestionDraftIdAtCursor: string | null; inlineDrafts: InlineDraftRecordMap; activeDraftId: string | null; isDesktopFloatingEnabled: boolean; ensCache: EnsCache; inProgressFetch: string[]; editRequest: CommentEditRequest | null; replyEditTarget: ReplyEditTarget | null; editCompletion: CommentEditCompletion | null; _externalDepsRef: React.RefObject | null; setExternalDepsRef: (ref: React.RefObject) => void; _recomputeDerived: () => void; setInitialComments: (comments: IComment[]) => void; setUsername: (username: string | null) => void; setActiveCommentId: (id: string | null) => void; setActiveTabId: (tabId: string) => void; setIsConnected: (connected: boolean) => void; setIsLoading: (loading: boolean) => void; setIsDDocOwner: (isOwner: boolean) => void; setIsInlineCommentAvailable: (value: boolean) => void; setOnComment: (fn: (() => void) | null) => void; setCommentDrawerOpenFn: (fn: ((open: boolean) => void) | null) => void; setConnectViaWallet: (fn: (() => Promise) | null) => void; setConnectViaUsername: (fn: ((username: string) => Promise) | null) => void; setIsCommentActive: (active: boolean) => void; setIsCommentResolved: (resolved: boolean) => void; getTabComments: () => IComment[]; getActiveComment: () => IComment | undefined; getActiveComments: () => IComment[]; getActiveCommentIndex: () => number; getIsCommentActive: () => boolean; getIsCommentResolved: () => boolean; setShowResolved: (show: boolean) => void; setReply: (reply: string) => void; setComment: (comment: string) => void; setOpenReplyId: (id: string | null) => void; setSelectedText: (text: string) => void; setIsCommentOpen: (open: boolean) => void; setIsBubbleMenuSuppressed: (suppressed: boolean) => void; setInlineCommentData: (data: InlineCommentDataUpdater) => void; setFloatingCards: (floatingCards: FloatingCardsUpdater) => void; clearFloatingCards: () => void; setActiveDraftId: (draftId: string | null) => void; setIsDesktopFloatingEnabled: (enabled: boolean) => void; toggleResolved: () => void; clearEditRequest: (requestId: string) => void; setReplyEditTarget: (target: ReplyEditTarget | null) => void; cancelReplyEdit: () => void; handleInput: (e: React.FormEvent, content: string) => void; handleReplyChange: (e: React.ChangeEvent) => void; handleCommentChange: (e: React.ChangeEvent) => void; handleCommentKeyDown: (e: React.KeyboardEvent, tabId?: string) => void; handleReplyKeyDown: (e: React.KeyboardEvent) => void; handleReplySubmit: () => void; handleCommentSubmit: (tabId?: string) => void; handleInlineComment: () => void; addComment: (content?: string, usernameProp?: string) => string | undefined; createFloatingDraft: (options?: CreateInlineDraftOptions) => string | null; updateInlineDraftText: (draftId: string, value: string) => void; cancelInlineDraft: (draftId: string) => void; submitInlineDraft: (draftId: string) => void; updateFloatingDraftText: (draftId: string, value: string) => void; cancelFloatingDraft: (draftId: string) => void; submitFloatingDraft: (draftId: string) => void; openFloatingThread: (commentId: string) => void; focusSubmittedSuggestionFromEditor: (commentId: string) => boolean; closeFloatingCard: (floatingCardId: string) => void; blurFloatingCard: (floatingCardId: string) => void; focusFloatingCard: (floatingCardId: string) => void; removeInvalidFloatingDrafts: () => void; reconcileFloatingThreadsForActiveTab: (options: ReconcileFloatingThreadsForActiveTabOptions) => void; syncFloatingThreadCardWithActiveComment: () => void; submitPendingFloatingDrafts: () => void; /** * Apply anchor edits to local comment state. * Called after transaction analysis identifies edited anchors. * Updates selectedContent for each affected comment * so thread display stays in sync immediately, before consumer rehydration. */ applyCommentAnchorEdits: (edits: Array<{ commentId: string; selectedContent: string; }>) => void; resolveComment: (commentId: string) => void; unresolveComment: (commentId: string) => void; deleteComment: (commentId: string, options?: { skipExternalCallback?: boolean; }) => void; acceptSuggestion: (commentId: string) => void; /** * Append typed characters to the draft at the current cursor position. * Creates a new Add draft if no draft exists at the cursor. */ appendToDraftAtCursor: (text: string) => void; /** * Create a Delete (or pending Replace) draft from a selection range. * Captures originalContent, leaves insertedText empty; type becomes 'replace' * as soon as the viewer types. */ startDeleteDraft: (from: number, to: number, collapseTo?: number) => void; /** * Create a link suggestion from a selected range and pasted href. * The document stays unchanged until the owner accepts the suggestion. */ startLinkDraft: (from: number, to: number, href: string) => void; /** * Convert a collapsed-caret Backspace/Delete into a delete draft when the * caret is adjacent to text, or shrink the active draft if the caret is * already inside one. */ deleteAtCursorOrUndoActiveDraft: (direction: SuggestionDeleteDirection) => void; /** * Handle browser deletion paths that resolved to a concrete range even * though the user had no explicit selection. */ deleteRangeOrUndoActiveDraft: (from: number, to: number) => void; /** * Undo the last keystroke in the draft at the current cursor position. * When the draft has no keystrokes left, it is discarded. * For a pure Delete draft (no keystrokes ever), calling this discards. */ undoLastKeystrokeInActiveDraft: () => void; /** Drop a draft entirely — removes the inline overlay and draft card. */ discardDraft: (suggestionId: string) => void; /** * Refresh the `originalContent` (and the suggestion-draft card's selectedText) * for a Delete/Replace draft whose anchored range still resolves but now * covers different text — happens when the owner edits within the anchored * range while the viewer's draft is open. */ refreshDraftOriginalContent: (suggestionId: string, currentText: string) => void; /** * Sync the draft id under the current editor cursor. Each draft card uses * this to decide whether its own auto-submit countdown should run. */ syncActiveSuggestionDraftAtCursor: () => void; /** * Promote a draft to a submitted suggestion. Pushes the anchor into * commentAnchorsRef, calls onNewComment, removes the draft, and swaps the * suggestion-draft floating card for a thread card (same floatingCardId). */ submitDraft: (suggestionId: string) => void; deleteReply: (commentId: string, replyId: string) => void; requestEditComment: (commentId: string) => void; requestEditReply: (commentId: string, replyId: string) => void; editCommentContent: (commentId: string, content: string) => void; editReplyContent: (commentId: string, replyId: string, content: string) => void; handleAddReply: (activeCommentId: string, replyContent: string, replyCallback?: (activeCommentId: string, reply: IComment) => void) => void; focusCommentInEditor: (commentId: string, options?: FocusCommentInEditorOptions) => void; focusSuggestionDraftInEditor: (suggestionId: string) => void; onPrevComment: () => void; onNextComment: () => void; getEnsStatus: (walletAddress: string, setEnsStatus: React.Dispatch>) => void; createMutationMeta: (type: CommentMutationType, mutate: () => boolean) => CommentMutationMeta | undefined; } export declare const createCommentStore: () => import('zustand').StoreApi; export declare const CommentStoreContext: React.Context | null>; export declare function useCommentStore(selector: (state: CommentStoreState) => T): T; /** * Non-throwing variant of useCommentStore: returns undefined when no * CommentStoreProvider is above the caller (e.g. useEditorCommands used * outside DdocEditor). The selector's RETURN VALUE must be Object.is-stable * for unchanged state (primitives or refs already held by the store) — * useSyncExternalStore compares snapshots with Object.is, so a selector * that constructs a fresh object/array per call would re-render forever. * Selector function identity itself doesn't matter. */ export declare function useCommentStoreOptional(selector: (state: CommentStoreState) => T): T | undefined; export {};