Google-Docs-style comments: anchored threads, margin rail, sidebar, host-configurable permissions, live updates, DOCX/PDF export. Guide: Comments plugin. The DOM- and React-free half (model, validation, policy, REST mapping, stores, schema) is also published as @nextgensoftwares/folio-plugin-comments/core for servers.
in memory (replace(threads) too); type MemoryCommentStoreOptions
ServerCommentStore
REST + live events + ETag polling, optimistic with rollback; setLive(source), refresh(); type ServerCommentStoreOptions, type CommentLiveSource (onEvent, e.g. a CollabClient)
OptimisticThreads, type PendingOp
confirmed state + pending local ops, version-ordered
TypingBoard
typing indicators with expiry
opToRequest, requestToOp
the REST mapping (shared with the server); type CommentRequest
@nextgensoftwares/folio-plugin-comments
Google-Docs-style comments: anchored threads, margin rail, sidebar, host-configurable permissions, live updates, DOCX/PDF export. Guide: Comments plugin. The DOM- and React-free half (model, validation, policy, REST mapping, stores, schema) is also published as
@nextgensoftwares/folio-plugin-comments/corefor servers.Plugin
commentsPlugin(options?: CommentsPluginOptions) => CommentsPluginMod-Alt-m, the Comment item, highlight page layer + painter, overlay rail, sidebar and settings slotstype CommentsPluginFolioPluginpluscontroller,setUser,setOptions,getOptions,setStore,forEditor(editor),docxComments(editor),pdfAnnotations(editor),subscribetype CommentsPluginOptionsuser,store,mentionables,onMention,onError,canEditDocument,blockTypes,shortcut,rail,sidebar,settings,exportComments,reactions,highlight,typing,nowtype LiveOptionssetOptionsaccepts (all butblockTypes,shortcut,sidebar,settings)type MentionEvent{ thread, comment, mentioned, by }type SlotOptionboolean | { slot?, label?, order? }CommentsControllerforEditor,editorOf(state),run(editor, op),visible(),setOptions,setStore,subscribeEditorCommentsactive,draft,mode,threads(),anchors(),startComment(),submitDraft(body),reply,edit,remove,resolve,react,jumpTo(id),typing,setActive,subscribetype AnchorSpan{ from, to, node }where a thread is anchored nowtype Draft{ id, target }: a comment being writtenDEFAULT_REACTIONS,RAIL_WIDTH,RAIL_GAP['👍', '❤️', '🎉'], 272, 20Model
type CommentUser{ id, name, color?, avatarUrl?, role? }(role: 'reader'= reader by default)type CommentAuthorrole, as storedtype Comment{ id, author, createdAt, editedAt?, body, mentions?, reactions? }type CommentThreadCommentplusanchor,replies,resolved,resolvedBy?,resolvedAt?type CommentAnchortext(quote, positions,detached?, context),node(nodeType?,pos?) orpoint(pos, context)type CommentOpcreate,reply,edit,delete,resolve,react,anchortype CommentAction'view' | 'comment' | 'reply' | 'resolve' | 'edit' | 'delete' | 'react' | 'anchor'CommentErrorcode: 'invalid' | 'not-found' | 'forbidden' | 'conflict'applyOp(threads, op, actor, now) => OpResult: the shared, validating reducer (client, store and server)type OpResult{ threads, threadId, thread }(threadnull = deleted)parseOpCommentError)findComment,authorOfsanitizeBody,sanitizeAnchor,sanitizeAuthor,sanitizeEmoji,isIdmentionsOf,bodyParts,mentionToken,MENTION@[Name](id)mention tokensCOMMENT_LIMITSnewCommentIdPermissions
type CommentPolicyOptionsreaders,isReader,canView,canComment,canResolve,canEdit,canDelete,canReacttype ReaderAccess'hidden' | 'view' | 'comment'resolvePolicy(options) => CommentPolicywith defaults; a throwing rule deniestype CommentPolicyauthorizeOp(policy, op, user, threads) => booleanactionOfCommentActionan op needsStores
type CommentStorethreads(),subscribe,apply(op, actor), optionalstatus,error,typing,typists,disposetype CommentStoreStatus'loading' | 'ready' | 'offline' | 'error'MemoryCommentStorereplace(threads)too);type MemoryCommentStoreOptionsServerCommentStoresetLive(source),refresh();type ServerCommentStoreOptions,type CommentLiveSource(onEvent, e.g. aCollabClient)OptimisticThreads,type PendingOpTypingBoardopToRequest,requestToOptype CommentRequesttype CommentsSnapshot,type CommentWriteResult{ version, threads };{ version, threadId, thread }type CommentEvent,type CommentChangeEvent,type CommentTypingEvent,isCommentEventkind: 'comments' | 'comments-typing')Anchors
commentSchema(blockTypes?) => Partial<SchemaSpec>: thecommentmark (inclusive: false,excludes: '') and thecommentIdsblock attr; servers add it to their schemaCOMMENT_MARK,COMMENT_ATTR,COMMENT_META,DEFAULT_BLOCK_TYPES'comment','commentIds', the plugin's transaction meta, default block typesAnchorIndex,anchorIndexPlugin,anchorsOf,anchorKey,updateIndexof(id),has(id),at(pos))type AnchorRange{ id, from, to, node }pasteGuardPluginaddAnchor,removeAnchor,anchorFor,targetOf,type AnchorTargetcontextOf,findQuote,findPointUI helpers and export
stackCards,type CardSlotrailFrame,type RailFramematchesShortcut,shortcutLabel'Mod-Alt-m'matching and labelsrelativeTimeCOMMENTS_CSS,ensureCommentStylesdocxComments,type DocxThreadExportdocxExportPlugin({ comments })pdfAnnotations,type PdfAnnotationExportpdfExportPlugin({ annotationsFor })type ExportedComment{ author, initials, text, date }