@kedataindo/docflow-core 0.0.83 → 0.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,15 +1,15 @@
1
1
  import { AnyExtension, Editor, Extension } from '@tiptap/core';
2
- import { a as CollaborationSetup } from './Collaboration-CGv9OZlB.cjs';
3
- export { A as AwarenessState, C as CollaborationOptions, c as collaborationExtensions, b as createCollaboration } from './Collaboration-CGv9OZlB.cjs';
4
- import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-BTF6gajh.cjs';
5
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIContextLocation, g as AIDraftCitation, h as AIDraftEvent, i as AIProvider, j as AIProviderFactory, k as AiChatRequestContext, l as Auth, m as CONTEXT_CHAR_CAP, n as CslDate, o as CslItemData, p as CslName, H as HttpKeyStorageUrls, q as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, r as buildAIPrompt, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from './index-BTF6gajh.cjs';
2
+ import { Awareness } from 'y-protocols/awareness';
3
+ import { IndexeddbPersistence } from 'y-indexeddb';
4
+ import { WebrtcProvider } from 'y-webrtc';
5
+ import { WebsocketProvider } from 'y-websocket';
6
+ import * as Y from 'yjs';
7
+ import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-BsIFJXPk.cjs';
8
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, k as CONTEXT_CHAR_CAP, l as CslDate, m as CslItemData, n as CslName, H as HttpKeyStorageUrls, o as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, p as buildAIPrompt, q as httpKeyStorage, r as localStorageKeyStorage, s as memoryKeyStorage, t as openaiCompatibleProvider, u as toAIStreamFn, v as trimContextAfter, w as trimContextBefore } from './index-BsIFJXPk.cjs';
9
+ import { PaginationPlusOptions } from 'tiptap-pagination-plus';
10
+ export { PAGE_SIZES, PageSize, PaginationPlus, PaginationPlusOptions } from 'tiptap-pagination-plus';
6
11
  import { PluginKey } from '@tiptap/pm/state';
7
12
  import { Node } from '@tiptap/pm/model';
8
- import 'y-protocols/awareness';
9
- import 'y-indexeddb';
10
- import 'y-webrtc';
11
- import 'y-websocket';
12
- import 'yjs';
13
13
 
14
14
  interface ToolbarItem {
15
15
  id: string;
@@ -18,16 +18,6 @@ interface ToolbarItem {
18
18
  label?: string;
19
19
  action: string;
20
20
  args?: unknown[];
21
- /**
22
- * Group this item into the toolbar's compact groups instead of rendering it
23
- * as a flat button. Currently only `'insert'` (the "+" / Sisipkan dropdown)
24
- * is supported; built-in insert actions (link, image, table, footnote, page
25
- * break, …) are grouped automatically, so plugins only need this flag for
26
- * their OWN actions.
27
- *
28
- * Defaults to `undefined` → flat toolbar button (previous behaviour).
29
- */
30
- menu?: 'insert';
31
21
  }
32
22
  interface SlashCommand {
33
23
  name: string;
@@ -50,67 +40,86 @@ declare function collectExtensions(plugins: DocsEditorPlugin[]): AnyExtension[];
50
40
  declare function resolveAction(editor: Editor, action: string, ...args: unknown[]): boolean;
51
41
  declare function createActionMap(editor: Editor, plugins: DocsEditorPlugin[]): Record<string, (...args: unknown[]) => boolean>;
52
42
 
53
- interface PageSize {
54
- pageHeight: number;
55
- pageWidth: number;
56
- marginTop: number;
57
- marginBottom: number;
58
- marginLeft: number;
59
- marginRight: number;
43
+ interface AwarenessState {
44
+ clientId: number;
45
+ user: {
46
+ name: string;
47
+ color: string;
48
+ };
49
+ cursor?: {
50
+ from: number;
51
+ to: number;
52
+ } | null;
53
+ /**
54
+ * Phase 9 PR2 — *present* flag. False when the local user has navigated
55
+ * away from the editor route (the Yjs provider is still alive but the
56
+ * host has gated cursor emission off, per the §6 *\"TOC sidebar orphan
57
+ * wiring\"* / REST-heartbeat scope notes). Peers see this instantly
58
+ * via the awareness `change` event — far faster than the 15s REST
59
+ * heartbeat fallback. Defaults to true when the user is in the editor.
60
+ */
61
+ present: boolean;
60
62
  }
61
-
62
- interface PaginationPlusOptions {
63
- enabled?: boolean;
64
- pageBreakBackground?: string;
65
- pageHeight?: number;
66
- pageWidth?: number;
67
- marginTop?: number;
68
- marginBottom?: number;
69
- marginLeft?: number;
70
- marginRight?: number;
71
- pageGap?: number;
72
- contentMarginTop?: number;
73
- contentMarginBottom?: number;
74
- footerRight?: string;
75
- footerLeft?: string;
76
- headerRight?: string;
77
- headerLeft?: string;
78
- customHeader?: Record<number, any>;
79
- customFooter?: Record<number, any>;
80
- pageGapBorderSize?: number;
81
- pageGapBorderColor?: string;
82
- onHeaderClick?: (info: {
83
- event: MouseEvent;
84
- pageNumber: number;
85
- }) => void;
86
- onFooterClick?: (info: {
87
- event: MouseEvent;
88
- pageNumber: number;
89
- }) => void;
63
+ interface CollaborationOptions {
64
+ room: string;
65
+ provider?: 'webrtc' | 'websocket';
66
+ websocketUrl?: string;
67
+ signaling?: string[];
68
+ user: {
69
+ name: string;
70
+ color: string;
71
+ };
72
+ onAwarenessChange?: (states: AwarenessState[]) => void;
73
+ initialStorageState?: Uint8Array;
74
+ /**
75
+ * Phase 9 PR2 — when false, the local user is treated as
76
+ * out-of-editor: their cursor field is nulled and `present` is false.
77
+ * Default is true (in editor). Toggle at runtime via
78
+ * `setLocalCursorEnabled()` returned from `createCollaboration`.
79
+ */
80
+ emitCursor?: boolean;
81
+ /**
82
+ * Phase 9 OF1 — when true, mirror the room's Y.Doc into IndexedDB
83
+ * (`docflow-<room>`) via y-indexeddb so edits survive offline and reload.
84
+ * Reconcile-on-reconnect is plain Yjs merge — no conflict resolution
85
+ * needed. The IndexedDB mirror is NEVER a seed source: the server (or the
86
+ * legacy-JSON seed path) still owns initial state; the mirror only
87
+ * contributes local offline edits via the normal Yjs update exchange.
88
+ */
89
+ offline?: boolean;
90
90
  }
91
- declare const PAGE_SIZES: Record<string, PageSize>;
92
- declare const PaginationPlus: Extension<PaginationPlusOptions, any>;
93
- declare module '@tiptap/core' {
94
- interface Commands<ReturnType> {
95
- paginationPlus: {
96
- updatePageBreakBackground: (color: string) => ReturnType;
97
- updatePageSize: (size: PageSize) => ReturnType;
98
- updatePageWidth: (width: number) => ReturnType;
99
- updatePageHeight: (height: number) => ReturnType;
100
- updatePageGap: (gap: number) => ReturnType;
101
- updateMargins: (margins: {
102
- top: number;
103
- bottom: number;
104
- left: number;
105
- right: number;
106
- }) => ReturnType;
107
- enablePagination: () => ReturnType;
108
- disablePagination: () => ReturnType;
109
- updateHeaderContent: (left?: string, right?: string) => ReturnType;
110
- updateFooterContent: (left?: string, right?: string) => ReturnType;
111
- };
112
- }
91
+ interface CollaborationSetup {
92
+ ydoc: Y.Doc;
93
+ provider: WebrtcProvider | WebsocketProvider | null;
94
+ awareness: Awareness;
95
+ /**
96
+ * Phase 9 OF1 — y-indexeddb persistence instance, present only when
97
+ * `options.offline` is true. Exposed so hosts/tests can `await
98
+ * persistence.whenSynced` before asserting the mirror contents. Do not
99
+ * use it as a seed source.
100
+ */
101
+ persistence?: IndexeddbPersistence;
102
+ /**
103
+ * Issue #117 — resolves after BOTH the offline mirror (if `offline`) AND
104
+ * the provider's first sync (if `websocket`) have completed. Hosts MUST
105
+ * `await setup.whenReady` before binding the editor, otherwise the
106
+ * TipTap view can render against the IndexedDB cache before the
107
+ * authoritative server state arrives, briefly showing — and on a
108
+ * disconnected peer potentially re-broadcasting — nodes the server has
109
+ * since deleted. Resolves immediately when neither side applies.
110
+ */
111
+ whenReady: Promise<void>;
112
+ destroy: () => void;
113
+ /**
114
+ * Phase 9 PR2 — toggle the local cursor emission + present flag.
115
+ * Pass `false` when the editor route unmounts, `true` when it remounts.
116
+ * Triggers an awareness `change` event so peers see the leave/rejoin
117
+ * instantly (no REST-heartbeat lag).
118
+ */
119
+ setLocalCursorEnabled: (enabled: boolean) => void;
113
120
  }
121
+ declare function createCollaboration(options: CollaborationOptions): CollaborationSetup;
122
+ declare function collaborationExtensions(options: CollaborationOptions | CollaborationSetup): AnyExtension[];
114
123
 
115
124
  /**
116
125
  * Lightweight client-side performance monitor (debug overlay).
@@ -207,31 +216,12 @@ interface EditorOptions {
207
216
  plugins?: DocsEditorPlugin[];
208
217
  editable?: boolean;
209
218
  onUpdate?: (json: object) => void;
210
- /**
211
- * Prebuilt collaboration setup from `await createCollaboration(...)`.
212
- *
213
- * A `CollaborationSetup` — never raw `CollaborationOptions` (issue
214
- * fe-aktifai#230): building a provider is async (lazy-imports of
215
- * y-webrtc/y-websocket), so `createEditor` cannot construct one itself.
216
- * When set, history is disabled and the Yjs document is authoritative
217
- * (local `content` is not seeded).
218
- *
219
- * **Ownership (host-owned):** the host that built the setup owns its
220
- * lifecycle. `editor.destroy()` tears down the TipTap view only — it does
221
- * **not** call `setup.destroy()`. This is deliberate: hosts routinely
222
- * remount the editor against the same room (Vue `:key` bumps, tab
223
- * switches, external document refreshes), and destroying the shared
224
- * `Y.Doc`/provider/awareness on every remount silently kills sync and
225
- * remote cursors until a full page reload. Destroy the setup yourself when
226
- * the room is no longer needed (unmount, room change, `createCollaboration`
227
- * resolving after disposal).
228
- */
229
- collaboration?: CollaborationSetup;
219
+ collaboration?: CollaborationOptions | CollaborationSetup;
230
220
  getPageMap?: () => Map<number, {
231
221
  page: number;
232
222
  blockIndex: number;
233
223
  }>;
234
- paginationOptions?: PaginationPlusOptions;
224
+ paginationOptions?: Partial<PaginationPlusOptions>;
235
225
  /** Host-injected image upload port (see docs/LIBRARY_CONTRACT.md). */
236
226
  onImageUpload?: ImageUploadHandler;
237
227
  /** Host-injected citation port (Phase 6 — reference library + CSL styles). */
@@ -343,4 +333,66 @@ declare function replaceCurrent(editor: Editor, replacement: string): boolean;
343
333
  /** Replace every match in one transaction (applied back-to-front). */
344
334
  declare function replaceAll(editor: Editor, replacement: string): number;
345
335
 
346
- export { AIDraftFn, AIStreamFn, BlockAttributesExtension, CitationPort, type ClipboardSource, CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PAGE_SIZES, type PageSize, PaginationPlus, type PaginationPlusOptions, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type ToolbarItem, clearSearch, collectExtensions, createActionMap, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
336
+ interface SubdocState {
337
+ id: string;
338
+ doc: Y.Doc;
339
+ provider: WebsocketProvider | null;
340
+ active: boolean;
341
+ }
342
+ interface SubdocumentProviderOptions {
343
+ /** WebSocket URL for the collaboration server. */
344
+ websocketUrl: string;
345
+ /** Room prefix — subdocuments use `${roomPrefix}/${id}` as room name. */
346
+ roomPrefix: string;
347
+ /** User info for awareness. */
348
+ user: {
349
+ name: string;
350
+ color: string;
351
+ };
352
+ /** Number of subdocuments to keep active (buffer around visible viewport). */
353
+ maxActive?: number;
354
+ /** Called when subdocument state changes (active/deactivated). */
355
+ onStateChange?: (states: SubdocState[]) => void;
356
+ }
357
+ /**
358
+ * Manages multiple Y.Doc subdocuments under a parent document.
359
+ *
360
+ * Use case: large documents (100+ pages) split into per-section subdocuments.
361
+ * Only active (visible) subdocuments are connected to the network via
362
+ * WebSocketProvider; inactive ones keep local state but stop syncing.
363
+ *
364
+ * Usage:
365
+ * const provider = new SubdocumentProvider({ websocketUrl, roomPrefix, user })
366
+ * provider.createSubdoc('section-1')
367
+ * provider.activate('section-1') // starts syncing
368
+ * provider.deactivate('section-1') // stops syncing
369
+ * const frag = provider.getFragment('section-1') // Y.XmlFragment for editor
370
+ */
371
+ declare class SubdocumentProvider {
372
+ readonly parentDoc: Y.Doc;
373
+ readonly subdocs: Y.Map<Y.Doc>;
374
+ readonly awareness: Awareness;
375
+ private readonly options;
376
+ private readonly states;
377
+ private readonly activeQueue;
378
+ private readonly parentProvider;
379
+ constructor(options: SubdocumentProviderOptions);
380
+ /** Create a new subdocument. Does NOT activate it. */
381
+ createSubdoc(id: string, initialState?: Uint8Array): Y.Doc;
382
+ /** Activate a subdocument — connects it to the network for live sync. */
383
+ activate(id: string): void;
384
+ /** Deactivate a subdocument — disconnects it from the network. */
385
+ deactivate(id: string): void;
386
+ /** Get the Y.XmlFragment for a subdocument. */
387
+ getFragment(id: string): Y.XmlFragment | null;
388
+ /** Check if a subdocument is currently active (syncing). */
389
+ isActive(id: string): boolean;
390
+ /** Get all subdocument states. */
391
+ getAllStates(): SubdocState[];
392
+ /** Get IDs of all active subdocuments. */
393
+ getActiveIds(): string[];
394
+ /** Destroy all subdocuments and providers. */
395
+ destroy(): void;
396
+ }
397
+
398
+ export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type ClipboardSource, type CollaborationOptions, type CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
package/dist/index.d.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import { AnyExtension, Editor, Extension } from '@tiptap/core';
2
- import { a as CollaborationSetup } from './Collaboration-CGv9OZlB.js';
3
- export { A as AwarenessState, C as CollaborationOptions, c as collaborationExtensions, b as createCollaboration } from './Collaboration-CGv9OZlB.js';
4
- import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-BTF6gajh.js';
5
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIContextLocation, g as AIDraftCitation, h as AIDraftEvent, i as AIProvider, j as AIProviderFactory, k as AiChatRequestContext, l as Auth, m as CONTEXT_CHAR_CAP, n as CslDate, o as CslItemData, p as CslName, H as HttpKeyStorageUrls, q as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, r as buildAIPrompt, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from './index-BTF6gajh.js';
2
+ import { Awareness } from 'y-protocols/awareness';
3
+ import { IndexeddbPersistence } from 'y-indexeddb';
4
+ import { WebrtcProvider } from 'y-webrtc';
5
+ import { WebsocketProvider } from 'y-websocket';
6
+ import * as Y from 'yjs';
7
+ import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-BsIFJXPk.js';
8
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, k as CONTEXT_CHAR_CAP, l as CslDate, m as CslItemData, n as CslName, H as HttpKeyStorageUrls, o as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, p as buildAIPrompt, q as httpKeyStorage, r as localStorageKeyStorage, s as memoryKeyStorage, t as openaiCompatibleProvider, u as toAIStreamFn, v as trimContextAfter, w as trimContextBefore } from './index-BsIFJXPk.js';
9
+ import { PaginationPlusOptions } from 'tiptap-pagination-plus';
10
+ export { PAGE_SIZES, PageSize, PaginationPlus, PaginationPlusOptions } from 'tiptap-pagination-plus';
6
11
  import { PluginKey } from '@tiptap/pm/state';
7
12
  import { Node } from '@tiptap/pm/model';
8
- import 'y-protocols/awareness';
9
- import 'y-indexeddb';
10
- import 'y-webrtc';
11
- import 'y-websocket';
12
- import 'yjs';
13
13
 
14
14
  interface ToolbarItem {
15
15
  id: string;
@@ -18,16 +18,6 @@ interface ToolbarItem {
18
18
  label?: string;
19
19
  action: string;
20
20
  args?: unknown[];
21
- /**
22
- * Group this item into the toolbar's compact groups instead of rendering it
23
- * as a flat button. Currently only `'insert'` (the "+" / Sisipkan dropdown)
24
- * is supported; built-in insert actions (link, image, table, footnote, page
25
- * break, …) are grouped automatically, so plugins only need this flag for
26
- * their OWN actions.
27
- *
28
- * Defaults to `undefined` → flat toolbar button (previous behaviour).
29
- */
30
- menu?: 'insert';
31
21
  }
32
22
  interface SlashCommand {
33
23
  name: string;
@@ -50,67 +40,86 @@ declare function collectExtensions(plugins: DocsEditorPlugin[]): AnyExtension[];
50
40
  declare function resolveAction(editor: Editor, action: string, ...args: unknown[]): boolean;
51
41
  declare function createActionMap(editor: Editor, plugins: DocsEditorPlugin[]): Record<string, (...args: unknown[]) => boolean>;
52
42
 
53
- interface PageSize {
54
- pageHeight: number;
55
- pageWidth: number;
56
- marginTop: number;
57
- marginBottom: number;
58
- marginLeft: number;
59
- marginRight: number;
43
+ interface AwarenessState {
44
+ clientId: number;
45
+ user: {
46
+ name: string;
47
+ color: string;
48
+ };
49
+ cursor?: {
50
+ from: number;
51
+ to: number;
52
+ } | null;
53
+ /**
54
+ * Phase 9 PR2 — *present* flag. False when the local user has navigated
55
+ * away from the editor route (the Yjs provider is still alive but the
56
+ * host has gated cursor emission off, per the §6 *\"TOC sidebar orphan
57
+ * wiring\"* / REST-heartbeat scope notes). Peers see this instantly
58
+ * via the awareness `change` event — far faster than the 15s REST
59
+ * heartbeat fallback. Defaults to true when the user is in the editor.
60
+ */
61
+ present: boolean;
60
62
  }
61
-
62
- interface PaginationPlusOptions {
63
- enabled?: boolean;
64
- pageBreakBackground?: string;
65
- pageHeight?: number;
66
- pageWidth?: number;
67
- marginTop?: number;
68
- marginBottom?: number;
69
- marginLeft?: number;
70
- marginRight?: number;
71
- pageGap?: number;
72
- contentMarginTop?: number;
73
- contentMarginBottom?: number;
74
- footerRight?: string;
75
- footerLeft?: string;
76
- headerRight?: string;
77
- headerLeft?: string;
78
- customHeader?: Record<number, any>;
79
- customFooter?: Record<number, any>;
80
- pageGapBorderSize?: number;
81
- pageGapBorderColor?: string;
82
- onHeaderClick?: (info: {
83
- event: MouseEvent;
84
- pageNumber: number;
85
- }) => void;
86
- onFooterClick?: (info: {
87
- event: MouseEvent;
88
- pageNumber: number;
89
- }) => void;
63
+ interface CollaborationOptions {
64
+ room: string;
65
+ provider?: 'webrtc' | 'websocket';
66
+ websocketUrl?: string;
67
+ signaling?: string[];
68
+ user: {
69
+ name: string;
70
+ color: string;
71
+ };
72
+ onAwarenessChange?: (states: AwarenessState[]) => void;
73
+ initialStorageState?: Uint8Array;
74
+ /**
75
+ * Phase 9 PR2 — when false, the local user is treated as
76
+ * out-of-editor: their cursor field is nulled and `present` is false.
77
+ * Default is true (in editor). Toggle at runtime via
78
+ * `setLocalCursorEnabled()` returned from `createCollaboration`.
79
+ */
80
+ emitCursor?: boolean;
81
+ /**
82
+ * Phase 9 OF1 — when true, mirror the room's Y.Doc into IndexedDB
83
+ * (`docflow-<room>`) via y-indexeddb so edits survive offline and reload.
84
+ * Reconcile-on-reconnect is plain Yjs merge — no conflict resolution
85
+ * needed. The IndexedDB mirror is NEVER a seed source: the server (or the
86
+ * legacy-JSON seed path) still owns initial state; the mirror only
87
+ * contributes local offline edits via the normal Yjs update exchange.
88
+ */
89
+ offline?: boolean;
90
90
  }
91
- declare const PAGE_SIZES: Record<string, PageSize>;
92
- declare const PaginationPlus: Extension<PaginationPlusOptions, any>;
93
- declare module '@tiptap/core' {
94
- interface Commands<ReturnType> {
95
- paginationPlus: {
96
- updatePageBreakBackground: (color: string) => ReturnType;
97
- updatePageSize: (size: PageSize) => ReturnType;
98
- updatePageWidth: (width: number) => ReturnType;
99
- updatePageHeight: (height: number) => ReturnType;
100
- updatePageGap: (gap: number) => ReturnType;
101
- updateMargins: (margins: {
102
- top: number;
103
- bottom: number;
104
- left: number;
105
- right: number;
106
- }) => ReturnType;
107
- enablePagination: () => ReturnType;
108
- disablePagination: () => ReturnType;
109
- updateHeaderContent: (left?: string, right?: string) => ReturnType;
110
- updateFooterContent: (left?: string, right?: string) => ReturnType;
111
- };
112
- }
91
+ interface CollaborationSetup {
92
+ ydoc: Y.Doc;
93
+ provider: WebrtcProvider | WebsocketProvider | null;
94
+ awareness: Awareness;
95
+ /**
96
+ * Phase 9 OF1 — y-indexeddb persistence instance, present only when
97
+ * `options.offline` is true. Exposed so hosts/tests can `await
98
+ * persistence.whenSynced` before asserting the mirror contents. Do not
99
+ * use it as a seed source.
100
+ */
101
+ persistence?: IndexeddbPersistence;
102
+ /**
103
+ * Issue #117 — resolves after BOTH the offline mirror (if `offline`) AND
104
+ * the provider's first sync (if `websocket`) have completed. Hosts MUST
105
+ * `await setup.whenReady` before binding the editor, otherwise the
106
+ * TipTap view can render against the IndexedDB cache before the
107
+ * authoritative server state arrives, briefly showing — and on a
108
+ * disconnected peer potentially re-broadcasting — nodes the server has
109
+ * since deleted. Resolves immediately when neither side applies.
110
+ */
111
+ whenReady: Promise<void>;
112
+ destroy: () => void;
113
+ /**
114
+ * Phase 9 PR2 — toggle the local cursor emission + present flag.
115
+ * Pass `false` when the editor route unmounts, `true` when it remounts.
116
+ * Triggers an awareness `change` event so peers see the leave/rejoin
117
+ * instantly (no REST-heartbeat lag).
118
+ */
119
+ setLocalCursorEnabled: (enabled: boolean) => void;
113
120
  }
121
+ declare function createCollaboration(options: CollaborationOptions): CollaborationSetup;
122
+ declare function collaborationExtensions(options: CollaborationOptions | CollaborationSetup): AnyExtension[];
114
123
 
115
124
  /**
116
125
  * Lightweight client-side performance monitor (debug overlay).
@@ -207,31 +216,12 @@ interface EditorOptions {
207
216
  plugins?: DocsEditorPlugin[];
208
217
  editable?: boolean;
209
218
  onUpdate?: (json: object) => void;
210
- /**
211
- * Prebuilt collaboration setup from `await createCollaboration(...)`.
212
- *
213
- * A `CollaborationSetup` — never raw `CollaborationOptions` (issue
214
- * fe-aktifai#230): building a provider is async (lazy-imports of
215
- * y-webrtc/y-websocket), so `createEditor` cannot construct one itself.
216
- * When set, history is disabled and the Yjs document is authoritative
217
- * (local `content` is not seeded).
218
- *
219
- * **Ownership (host-owned):** the host that built the setup owns its
220
- * lifecycle. `editor.destroy()` tears down the TipTap view only — it does
221
- * **not** call `setup.destroy()`. This is deliberate: hosts routinely
222
- * remount the editor against the same room (Vue `:key` bumps, tab
223
- * switches, external document refreshes), and destroying the shared
224
- * `Y.Doc`/provider/awareness on every remount silently kills sync and
225
- * remote cursors until a full page reload. Destroy the setup yourself when
226
- * the room is no longer needed (unmount, room change, `createCollaboration`
227
- * resolving after disposal).
228
- */
229
- collaboration?: CollaborationSetup;
219
+ collaboration?: CollaborationOptions | CollaborationSetup;
230
220
  getPageMap?: () => Map<number, {
231
221
  page: number;
232
222
  blockIndex: number;
233
223
  }>;
234
- paginationOptions?: PaginationPlusOptions;
224
+ paginationOptions?: Partial<PaginationPlusOptions>;
235
225
  /** Host-injected image upload port (see docs/LIBRARY_CONTRACT.md). */
236
226
  onImageUpload?: ImageUploadHandler;
237
227
  /** Host-injected citation port (Phase 6 — reference library + CSL styles). */
@@ -343,4 +333,66 @@ declare function replaceCurrent(editor: Editor, replacement: string): boolean;
343
333
  /** Replace every match in one transaction (applied back-to-front). */
344
334
  declare function replaceAll(editor: Editor, replacement: string): number;
345
335
 
346
- export { AIDraftFn, AIStreamFn, BlockAttributesExtension, CitationPort, type ClipboardSource, CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PAGE_SIZES, type PageSize, PaginationPlus, type PaginationPlusOptions, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type ToolbarItem, clearSearch, collectExtensions, createActionMap, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };
336
+ interface SubdocState {
337
+ id: string;
338
+ doc: Y.Doc;
339
+ provider: WebsocketProvider | null;
340
+ active: boolean;
341
+ }
342
+ interface SubdocumentProviderOptions {
343
+ /** WebSocket URL for the collaboration server. */
344
+ websocketUrl: string;
345
+ /** Room prefix — subdocuments use `${roomPrefix}/${id}` as room name. */
346
+ roomPrefix: string;
347
+ /** User info for awareness. */
348
+ user: {
349
+ name: string;
350
+ color: string;
351
+ };
352
+ /** Number of subdocuments to keep active (buffer around visible viewport). */
353
+ maxActive?: number;
354
+ /** Called when subdocument state changes (active/deactivated). */
355
+ onStateChange?: (states: SubdocState[]) => void;
356
+ }
357
+ /**
358
+ * Manages multiple Y.Doc subdocuments under a parent document.
359
+ *
360
+ * Use case: large documents (100+ pages) split into per-section subdocuments.
361
+ * Only active (visible) subdocuments are connected to the network via
362
+ * WebSocketProvider; inactive ones keep local state but stop syncing.
363
+ *
364
+ * Usage:
365
+ * const provider = new SubdocumentProvider({ websocketUrl, roomPrefix, user })
366
+ * provider.createSubdoc('section-1')
367
+ * provider.activate('section-1') // starts syncing
368
+ * provider.deactivate('section-1') // stops syncing
369
+ * const frag = provider.getFragment('section-1') // Y.XmlFragment for editor
370
+ */
371
+ declare class SubdocumentProvider {
372
+ readonly parentDoc: Y.Doc;
373
+ readonly subdocs: Y.Map<Y.Doc>;
374
+ readonly awareness: Awareness;
375
+ private readonly options;
376
+ private readonly states;
377
+ private readonly activeQueue;
378
+ private readonly parentProvider;
379
+ constructor(options: SubdocumentProviderOptions);
380
+ /** Create a new subdocument. Does NOT activate it. */
381
+ createSubdoc(id: string, initialState?: Uint8Array): Y.Doc;
382
+ /** Activate a subdocument — connects it to the network for live sync. */
383
+ activate(id: string): void;
384
+ /** Deactivate a subdocument — disconnects it from the network. */
385
+ deactivate(id: string): void;
386
+ /** Get the Y.XmlFragment for a subdocument. */
387
+ getFragment(id: string): Y.XmlFragment | null;
388
+ /** Check if a subdocument is currently active (syncing). */
389
+ isActive(id: string): boolean;
390
+ /** Get all subdocument states. */
391
+ getAllStates(): SubdocState[];
392
+ /** Get IDs of all active subdocuments. */
393
+ getActiveIds(): string[];
394
+ /** Destroy all subdocuments and providers. */
395
+ destroy(): void;
396
+ }
397
+
398
+ export { AIDraftFn, AIStreamFn, type AwarenessState, BlockAttributesExtension, CitationPort, type ClipboardSource, type CollaborationOptions, type CollaborationSetup, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, ImageUploadHandler, type MonitorMetric, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, detectClipboardSource, findMatches, getSearchState, normalizeClipboardHTML, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery };