import { EventEmitter } from 'eventemitter3'; import { HocuspocusProviderWebsocket } from '@hocuspocus/provider'; import { SuperToolbar, FontsResolvedPayload, ListDefinitionsPayload, PresentationEditor } from '../../../super-editor/src/index.js'; import { createSuperdocVueApp } from './create-app.js'; import { Whiteboard } from './whiteboard/Whiteboard.js'; import { AwarenessUser, CanPerformPermissionParams, CollaborationProvider, Config, ContentControlActiveChangePayload, ContentControlClickPayload, DocumentMode, Editor, EditorUpdateEvent, ExportParams, InternalConfig, NavigableAddress, SearchMatch, SuperDocAwarenessUpdatePayload, SuperDocCommentsUpdatePayload, SuperDocEditorPayload, SuperDocExceptionPayload, SuperDocLockedPayload, SuperDocReadyPayload, SuperDocState, SurfaceHandle, SurfaceRequest, UpgradeToCollaborationOptions, User } from './types/index.js'; import { WhiteboardData } from './whiteboard/Whiteboard.js'; import type * as Y from 'yjs'; interface SuperDocWhiteboardPayload { whiteboard: Whiteboard; } interface SuperDocZoomPayload { zoom: number; } interface SuperDocFormattingMarksPayload { showFormattingMarks: boolean; superdoc: SuperDoc; } interface SuperDocDocumentModeChangePayload { documentMode: DocumentMode; } interface SuperDocPaginationPayload { totalPages: number; superdoc: SuperDoc; } interface SuperDocContentErrorPayload { error: unknown; editor: Editor; } /** * SuperDoc lifecycle event registry. Keys are event names emitted via * `this.emit(...)`; each value is the tuple of arguments. Used as the * generic parameter of `EventEmitter` so `superdoc.on` * / `superdoc.emit` reject unknown event names at compile time. */ interface SuperDocEventMap { ready: [SuperDocReadyPayload]; editorBeforeCreate: [SuperDocEditorPayload]; editorCreate: [SuperDocEditorPayload]; editorDestroy: []; 'pdf:document-ready': []; 'sidebar-toggle': [boolean]; zoomChange: [SuperDocZoomPayload]; 'formatting-marks-change': [SuperDocFormattingMarksPayload]; 'document-mode-change': [SuperDocDocumentModeChangePayload]; 'editor-update': [EditorUpdateEvent]; 'content-error': [SuperDocContentErrorPayload]; 'fonts-resolved': [FontsResolvedPayload]; 'pagination-update': [SuperDocPaginationPayload]; 'list-definitions-change': [ListDefinitionsPayload]; 'comments-update': [SuperDocCommentsUpdatePayload]; 'content-control:active-change': [ContentControlActiveChangePayload]; 'content-control:click': [ContentControlClickPayload]; 'collaboration-ready': [SuperDocEditorPayload]; 'awareness-update': [SuperDocAwarenessUpdatePayload]; locked: [SuperDocLockedPayload]; 'whiteboard:init': [SuperDocWhiteboardPayload]; 'whiteboard:ready': [SuperDocWhiteboardPayload]; 'whiteboard:change': [WhiteboardData]; 'whiteboard:enabled': [boolean]; 'whiteboard:tool': [string]; exception: [SuperDocExceptionPayload]; } /** * SuperDoc class * Expects a config object * * @class */ export declare class SuperDoc extends EventEmitter { #private; static allowedTypes: ("text/html" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "application/pdf")[]; /** * Build-time SuperDoc version string. Initialized to `'0.0.0'` so the * field is structurally assigned before the constructor runs, then * overwritten with the injected `__APP_VERSION__` constant inside * `#init` (the existing `@ts-expect-error` keeps the injected global * out of the JSDoc type graph). Consumers reading `superdoc.version` * immediately after `new SuperDoc(...)` see the real version because * `#init` runs synchronously through the overwrite before returning. */ version: string; /** * Local copy of the shared users list. Initialized to `[]` so direct * reads (`superdoc.users`) are stable before the async `#init` * re-seeds from `config.users`. Pre-ready `addSharedUser` / * `removeSharedUser` mutations would be silently overwritten by the * re-seed, so those methods guard with `#requireReady('addSharedUser')` * and throw a clear lifecycle error instead. */ users: User[]; /** Yjs document for collaboration; set in `#init` when collaboration is enabled, otherwise undefined. */ ydoc: Y.Doc | undefined; /** * Provider for the SuperDoc-level collaboration room (separate from * per-document providers). Widened to `CollaborationProvider` to match * the runtime, which stores whatever provider the consumer passed via * `Config.modules.collaboration.provider`. Consumers needing Hocuspocus- * specific members must narrow before use. * */ provider: CollaborationProvider | undefined; /** * Whiteboard instance, created by `#initWhiteboard()` after the * collaboration await. Initialized to `null` so consumers reading * `superdoc.whiteboard` before the `whiteboard:init` event fires get * a stable null, not `undefined`. */ whiteboard: Whiteboard | null; /** * Awareness palette assigned to local users when no explicit color is set. * Defaults to an empty array so `#assignUserColor` falls back to the * built-in `DEFAULT_AWARENESS_PALETTE`. */ colors: string[]; /** * Pinia stores and Vue runtime references. Populated by `#initVueApp` * inside the async `#init`, which runs *after* `await #initCollaboration`, * so these fields are `undefined` between `new SuperDoc(config)` * returning and the `ready` event firing. Typed as `T | undefined` so * @ts-check forces every access path to either narrow or use the * `#requireSuperdocStore` / `#requireCommentsStore` helpers below * (which throw a clear "wait for ready" error). SD-2916 PR-B closed * the delayed-init soundness gap. * * `@private` is a TypeScript-surface hide, not runtime privacy: the * fields still exist on the runtime instance and internal callers * across the package keep working. Consumers can no longer reach into * them via `.d.ts`, which collapses the Pinia type graph from the * public surface (SD-3213f). The headless-toolbar host contract was * refactored in the same PR to replace raw store reach with the * narrow methods `getPresentationEditorForDocument(documentId)` and * `getComment(commentId)` below, so SuperDoc instances satisfy * `HeadlessToolbarSuperdocHost` directly without exposing * `superdocStore` publicly. * * @private */ private superdocStore; /** * @private */ private commentsStore; /** * @private */ private highContrastModeStore; /** * Internal mount handle for the `SuperComments` Vue component, created * lazily by `addCommentsList()` and torn down by `removeCommentsList()`. * Not consumer API: `SuperComments` is not publicly exported, no docs * or examples reference `superdoc.commentsList`, and the inner fields * (`element`, `superdoc` backref, `container` Vue ComponentPublicInstance) * are internal mount state. * * Typed as `SuperComments | null | undefined` so the runtime states * stay type-clean: `undefined` before `addCommentsList()` runs (e.g. * when the viewer role skips initialization; see SuperDoc.test.js * for the assertion), `SuperComments` after `addCommentsList()`, and * `null` after `removeCommentsList()` tears down. No initializer, to * match the convention used by the adjacent `@private` store fields. * * @private */ private commentsList; /** * Internal Vue app handle created in `#initVueApp()` and used for * mount/unmount, `provide()`, and `config.globalProperties` setup. * Not consumer API: no docs or examples reference `superdoc.app`, * and the only cross-file reader (`SuperComments.createVueApp()` * at `super-comments-list.js:35`) is a `.js` file under * `checkJs: false`, so the `@private` boundary does not break * internal source compilation. * * Same SD-3213f-style TS surface hide as * `superdocStore` / `commentsStore` / `highContrastModeStore` / * `commentsList`; not runtime privacy. * * @private */ private app; /** Pinia store root for the SuperDoc Vue app. Set in `#initVueApp`. */ pinia: ReturnType['pinia'] | undefined; /** Count of editors that have signaled `editorCreate`. */ readyEditors: number; /** Outstanding async saves waiting for collaboration ack. */ pendingCollaborationSaves: number; activeEditor: Editor | null; toolbar: SuperToolbar | null; toolbarElement: string | HTMLElement | undefined; userColorMap: Map; colorIndex: number; isCollaborative: boolean; isLocked: boolean; lockedBy: User | null; isDev: boolean; superdocId: string; comments: unknown[]; socket: HocuspocusProviderWebsocket | null; user: AwarenessUser; _cleanupAwareness: (() => void) | null; _commentsCollabInitialized: boolean; /** * The active configuration. Typed as `InternalConfig` because `#init` runs * synchronously in the constructor and normalizes the consumer-provided * `Config` into the wider shape (`documents` filled, `modules` defaulted, * `user` spread with `DEFAULT_USER`, etc.). Any callsite reading * `this.config` runs after `#init`, so it sees the normalized shape. * * Public consumer input shape: `Config` (re-exported from `superdoc`). * Internal post-normalize shape: `InternalConfig`. */ config: InternalConfig; constructor(config: Config); /** * Get the number of editors that are required for this superdoc * @returns The number of required editors */ get requiredNumberOfEditors(): number; /** * Snapshot of the current SuperDoc state. Always reflects the most * recent values from the Pinia store; consumers must re-read on * change rather than caching. * * @see {@link SuperDocState} for the public return shape. The runtime * still walks `RuntimeDocument[]` internally, but `state.documents` * is exposed as the public `Document[]` view - consumers should not * rely on the richer runtime fields (`getEditor`, etc.). */ get state(): SuperDocState; /** * Look up the PresentationEditor associated with a given documentId. * Returns null if no document matches or the document has no * presentation editor. Replaces the legacy * `superdoc.superdocStore.documents[].getPresentationEditor()` reach * for `superdoc/headless-toolbar` host routing (SD-3213f). * */ getPresentationEditorForDocument(documentId: string): PresentationEditor | null; /** * Look up a comment by id. Returns null if not found. Replaces the * legacy `superdoc.commentsStore.getComment(id)` reach for * `superdoc/headless-toolbar` helpers (SD-3213f). The return type is * intentionally wide (`Record | null`) so the public * surface does not pull the Pinia comment model type graph. * */ getComment(commentId: string): Record | null; /** * Get the SuperDoc container element */ get element(): Element | null; /** * Upgrade a local SuperDoc instance into collaboration by overwriting * the supplied room with the current local document and comment state, * then attaching collaboration to the live editor instance in place. * * This is a **destructive promotion**: the target room is authoritatively * overwritten with the caller's current local state. It is NOT the API * for joining an existing room without changing its content. * * Currently limited to: * - A single DOCX document * - External `{ ydoc, provider }` collaboration * - Overwrite-and-upgrade only (no merge semantics) * * @returns Resolves once the collaborative runtime is ready */ upgradeToCollaboration({ ydoc, provider }: UpgradeToCollaborationOptions): Promise; /** * Add a user to the shared users list. Requires the instance to be * ready; pre-ready mutations would be silently overwritten by the * `this.users = this.config.users || []` re-seed inside `#init`. * * @param user The user to add */ addSharedUser(user: User): void; /** * Remove a user from the shared users list. Requires the instance * to be ready for the same reason as `addSharedUser`. Accepts * either a user-like object or a legacy email string. * * @param userOrEmail The user or email of the user to remove */ removeSharedUser(userOrEmail: User | string): void; /** * Forward the editor's raw content-error to the consumer callback, * enriching with documentId and the source file. `error` is widened * to `unknown` because super-editor's emitters do not normalize to * `Error` consistently (e.g. `insertContentAt` forwards the original * caught value). * */ onContentError({ error, editor }: { error: unknown; editor: Editor; }): void; /** * Triggered when the PDF document is ready */ broadcastPdfDocumentReady(): void; /** * Triggered when the superdoc is ready */ broadcastReady(): void; /** * Triggered before an editor is created * @param editor The editor that is about to be created */ broadcastEditorBeforeCreate(editor: Editor): void; /** * Triggered when an editor is created * @param editor The editor that was created */ broadcastEditorCreate(editor: Editor): void; /** * Triggered when an editor is destroyed */ broadcastEditorDestroy(): void; /** * Triggered when the comments sidebar is toggled */ broadcastSidebarToggle(isOpened: boolean): void; /** * Set the active editor * @param editor The editor to set as active */ setActiveEditor(editor: Editor): void; /** * Toggle the ruler visibility for SuperEditors * */ toggleRuler(): void; /** * Determine whether the current configuration allows a given permission. * Used by downstream consumers (toolbar, context menu, commands) to keep * tracked-change affordances consistent with customer overrides. * * The `comment` and `trackedChange` fields on the input carry open * index signatures because the function forwards the full payload to * `isAllowed()`; tracked-change payloads from the editor include * `type`, `attrs`, `from`, `to`, `segments`, and consumer comment * shapes vary. The fields read directly here are documented on the * input type itself. * * @see {@link CanPerformPermissionParams} for the input shape. */ canPerformPermission({ permission, role, isInternal, comment, trackedChange, }?: CanPerformPermissionParams): boolean; /** * Add a comments list to the superdoc * Requires the comments module to be enabled * @param element The DOM element to render the comments list in */ addCommentsList(element: HTMLElement): void; /** * Remove the comments list from the superdoc */ removeCommentsList(): void; /** * Scroll the document to a given comment by id. * * @param commentId The comment id * @param [options] * @returns Whether a matching element was found */ scrollToComment(commentId: string, options?: { behavior?: ScrollBehavior; block?: ScrollLogicalPosition; }): boolean; /** * Navigate to a block, bookmark, comment, or tracked change target. * * Story-aware navigation is currently supported for bookmark and tracked * change targets. Block and comment targets are body-only. * * @returns Whether the target was found and navigated to. */ navigateTo(target: NavigableAddress): Promise; /** * Scroll to any document element by its ID. * * Pass any element ID — paragraph nodeId, comment entityId, or tracked * change entityId. The method resolves the element type automatically * and scrolls to it. * * @param elementId - The element's stable ID. * @returns Whether the element was found and scrolled to. * * @example * // Navigate to a paragraph by its nodeId * await superdoc.scrollToElement('5AF80E61'); * * // Navigate to a comment by its entityId * await superdoc.scrollToElement('imported-25def254'); */ scrollToElement(elementId: string): Promise; /** * Toggle the custom context menu globally. * Updates both flow editors and PresentationEditor instances so downstream listeners can short-circuit early. */ setDisableContextMenu(disabled?: boolean): void; /** * SD-2454: Toggle bookmark bracket indicators (opt-in, off by default). * Matches Word's "Show bookmarks" option. Triggers a re-layout on change * because the brackets are visible characters participating in text flow. */ setShowBookmarks(show?: boolean): void; /** * Toggle nonprinting formatting marks (spaces, tabs, paragraph marks) in the * rendered layout. This is a view-only setting and is not exported to DOCX. */ setShowFormattingMarks(show?: boolean): void; /** * Toggle nonprinting formatting marks from their current state. */ toggleFormattingMarks(): void; /** * Set the document mode. */ setDocumentMode(type: DocumentMode): void; /** * Force PresentationEditor instances to render a specific tracked-changes mode * or disable tracked-change metadata entirely. * * @param [preferences] */ setTrackedChangesPreferences(preferences?: { mode?: 'review' | 'original' | 'final' | 'off'; enabled?: boolean; }): void; /** * Search for text or regex in the active editor. * * Returns `undefined` when there is no active editor; otherwise * returns the array of matches the underlying search command produced * (possibly empty). * * @param text The text or regex to search for * @returns The search results */ search(text: string | RegExp): SearchMatch[] | undefined; /** * Go to the next search result. * * Pass back a match returned by `superdoc.search()` unchanged; the * runtime resolves its current document position via the embedded * tracker ids. * * @param match The match object returned by `superdoc.search()`. * @returns Whether the command dispatched, or `undefined` if no active editor. */ goToSearchResult(match: SearchMatch): boolean | undefined; /** * Get the current zoom level as a percentage (e.g., 100 for 100%) * @returns The current zoom level as a percentage * @example * const zoom = superdoc.getZoom(); // Returns 100, 150, 200, etc. */ getZoom(): number; /** * Set the zoom level for all documents. * Updates the centralized activeZoom state, which propagates to all * presentation editors, PDF viewers, and whiteboard layers via the Vue watcher. * @param percent - The zoom level as a percentage (e.g., 100, 150, 200) * @example * superdoc.setZoom(150); // Set zoom to 150% * superdoc.setZoom(50); // Set zoom to 50% */ setZoom(percent: number): void; /** * Set the document to locked or unlocked */ setLocked(lock?: boolean): void; /** * Get the HTML content of all editors * @returns The HTML content of all editors */ getHTML(options?: Parameters[0]): string[]; /** * Lock the current superdoc and emit the `locked` event. * * @param [isLocked] Whether the superdoc is locked. Defaults to `false`. * @param [lockedBy] The user who locked the superdoc, or `null` * when unlocking (or when no user is known). Defaults to `null`. */ lockSuperdoc(isLocked?: boolean, lockedBy?: User | null): void; /** * Export the superdoc to a file * @param params - Export configuration */ export({ exportType, commentsType, exportedName, additionalFiles, additionalFileNames, isFinalDoc, triggerDownload, fieldsHighlightColor, }?: ExportParams): Promise; /** * Export editors to DOCX format. * @param [options] */ exportEditorsToDOCX({ commentsType, isFinalDoc, fieldsHighlightColor, }?: { commentsType?: string; isFinalDoc?: boolean; fieldsHighlightColor?: string | null; }): Promise; /** * Save the superdoc if in collaboration mode. Resolves when all * collaboration documents have flushed their pending writes. */ save(): Promise; /** * Open a surface (dialog or floating) above the document content. * */ openSurface(request: SurfaceRequest): SurfaceHandle; /** * Close a surface by id, or the topmost surface if no id is given. */ closeSurface(id?: string): void; /** * Destroy the superdoc instance */ destroy(): void; /** * Focus the active editor or the first editor in the superdoc */ focus(): void; /** * Set the high contrast mode */ setHighContrastMode(isHighContrast: boolean): void; } export {}; //# sourceMappingURL=SuperDoc.d.ts.map