@fileverse-dev/ddoc 6.0.0-drop-in.2 → 6.0.0
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/README.md +18 -89
- package/dist/ddoc-persistence-C2ay3lLb.mjs +370 -0
- package/dist/headless-editor-utils.es.js +1 -1
- package/dist/{index-8MQH4TSF.mjs → index-D9uM5S2Y.mjs} +30 -30
- package/dist/index.d.ts +3 -1
- package/dist/index.es.js +12496 -12445
- package/dist/{markdown-source-pane-CXO7aIOp.mjs → markdown-source-pane-DWliRVLH.mjs} +287 -287
- package/dist/package/components/editor-toolbar.d.ts +1 -2
- package/dist/package/components/fullscreen-toolbar.d.ts +2 -2
- package/dist/package/components/mobile-toolbar.d.ts +1 -2
- package/dist/package/components/tabs/document-mobile-tab-panel.d.ts +1 -3
- package/dist/package/components/tabs/document-tabs-sidebar.d.ts +1 -2
- package/dist/package/components/toc/document-outline.d.ts +1 -1
- package/dist/package/components/toc/toc.d.ts +1 -1
- package/dist/package/components/toc/types.d.ts +0 -7
- package/dist/package/hooks/use-doc-schema-version.d.ts +3 -1
- package/dist/package/hooks/use-export-headless-editor-content.d.ts +6 -2
- package/dist/package/hooks/use-focus-mode.d.ts +3 -1
- package/dist/package/hooks/use-headless-editor.d.ts +12 -4
- package/dist/package/hooks/use-tab-editor.d.ts +1 -4
- package/dist/package/hooks/use-tab-manager.d.ts +3 -1
- package/dist/package/hooks/use-yjs-setup.d.ts +7 -1
- package/dist/package/persistence/compaction.d.ts +27 -0
- package/dist/package/persistence/content-fingerprint.d.ts +27 -0
- package/dist/package/persistence/ddoc-persistence.d.ts +96 -0
- package/dist/package/persistence/index.d.ts +2 -0
- package/dist/package/persistence/provider-lifecycle.d.ts +37 -0
- package/dist/package/sync-local/SyncManager.d.ts +8 -1
- package/dist/package/sync-local/server-clock.d.ts +26 -0
- package/dist/package/sync-local/socketClient.d.ts +1 -1
- package/dist/package/sync-local/types/index.d.ts +2 -0
- package/dist/package/types.d.ts +89 -43
- package/dist/package/use-ddoc-editor.d.ts +1 -3
- package/dist/package/utils/focus-mode-canvas-click.d.ts +2 -0
- package/dist/package/utils/responsive.d.ts +1 -3
- package/dist/package/utils/schema-version.d.ts +2 -0
- package/dist/package/utils/storage-adapter.d.ts +30 -0
- package/dist/persistence.d.ts +1 -0
- package/dist/persistence.es.js +10 -0
- package/dist/{split-view-toolbar-BnfwwwTs.mjs → split-view-toolbar-CbVFNI6B.mjs} +1 -1
- package/dist/style.css +1 -1
- package/dist/tab-utils-CAcIp-Dt.mjs +313 -0
- package/dist/{use-headless-editor-Buqsz3UI.mjs → use-headless-editor-C1g_Z67w.mjs} +11127 -11430
- package/package.json +7 -2
- package/tailwind.preset.cjs +0 -42
- package/dist/package/components/editor-navbar.d.ts +0 -12
- package/dist/package/hooks/use-canvas-metrics.d.ts +0 -16
- package/dist/package/hooks/use-controllable-state.d.ts +0 -3
- package/dist/package/hooks/use-editor-media-query.d.ts +0 -4
- package/dist/package/utils/container-query-support.d.ts +0 -1
- package/dist/package/utils/editor-width-store.d.ts +0 -6
- package/dist/package/utils/stats-publisher.d.ts +0 -8
|
@@ -4,7 +4,7 @@ import { FontDescriptor, IpfsImageFetchPayload, IpfsImageUploadResponse } from '
|
|
|
4
4
|
import { Tab } from './tabs/utils/tab-utils';
|
|
5
5
|
|
|
6
6
|
import * as Y from 'yjs';
|
|
7
|
-
declare const MemoizedTiptapToolBar: React.MemoExoticComponent<({ editor, onError, isPresentationMode, setIsPresentationMode, enableCollaboration, zoomLevel, setZoomLevel, isNavbarVisible, setIsNavbarVisible,
|
|
7
|
+
declare const MemoizedTiptapToolBar: React.MemoExoticComponent<({ editor, onError, isPresentationMode, setIsPresentationMode, enableCollaboration, zoomLevel, setZoomLevel, isNavbarVisible, setIsNavbarVisible, ipfsImageUploadFn, onMarkdownExport, onMarkdownImport, onPdfExport, onHtmlExport, onTxtExport, onOdtExport, onDocxImport, ipfsImageFetchFn, fetchV1ImageFn, isConnected, tabs, ydoc, onRegisterExportTrigger, toggleFocusMode, isSplitView, onToggleSplitView, fonts: consumerFonts, renderToolbarLeading, }: {
|
|
8
8
|
editor: Editor | null;
|
|
9
9
|
onError?: (errorString: string) => void;
|
|
10
10
|
isPresentationMode?: boolean;
|
|
@@ -14,7 +14,6 @@ declare const MemoizedTiptapToolBar: React.MemoExoticComponent<({ editor, onErro
|
|
|
14
14
|
setZoomLevel: (zoom: string) => void;
|
|
15
15
|
isNavbarVisible: boolean;
|
|
16
16
|
setIsNavbarVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
|
17
|
-
showNavbarToggle?: boolean;
|
|
18
17
|
ipfsImageUploadFn?: (file: File) => Promise<IpfsImageUploadResponse>;
|
|
19
18
|
onMarkdownExport?: () => void;
|
|
20
19
|
onMarkdownImport?: () => void;
|
|
@@ -4,8 +4,8 @@ import { DdocProps } from '../types';
|
|
|
4
4
|
type FullScreenToolbarProps = {
|
|
5
5
|
dropdownOpen: boolean;
|
|
6
6
|
setDropdownOpen: Dispatch<SetStateAction<boolean>>;
|
|
7
|
-
zoomLevel:
|
|
8
|
-
setZoomLevel:
|
|
7
|
+
zoomLevel: DdocProps['zoomLevel'];
|
|
8
|
+
setZoomLevel: DdocProps['setZoomLevel'];
|
|
9
9
|
showTOC: DdocProps['showTOC'];
|
|
10
10
|
setShowTOC: DdocProps['setShowTOC'];
|
|
11
11
|
toggleFocusMode: () => Promise<void>;
|
|
@@ -2,13 +2,12 @@ import { default as React } from 'react';
|
|
|
2
2
|
import { Editor } from '@tiptap/react';
|
|
3
3
|
import { FontDescriptor, IpfsImageFetchPayload, IpfsImageUploadResponse } from '../types';
|
|
4
4
|
|
|
5
|
-
declare const MemoizedMobileToolbar: React.MemoExoticComponent<({ editor, onError, isKeyboardVisible, isNavbarVisible, setIsNavbarVisible,
|
|
5
|
+
declare const MemoizedMobileToolbar: React.MemoExoticComponent<({ editor, onError, isKeyboardVisible, isNavbarVisible, setIsNavbarVisible, ipfsImageUploadFn, ipfsImageFetchFn, fetchV1ImageFn, fonts, }: {
|
|
6
6
|
editor: Editor | null;
|
|
7
7
|
onError?: (errorString: string) => void;
|
|
8
8
|
isKeyboardVisible: boolean;
|
|
9
9
|
isNavbarVisible: boolean;
|
|
10
10
|
setIsNavbarVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
|
11
|
-
showNavbarToggle?: boolean;
|
|
12
11
|
ipfsImageUploadFn?: (file: File) => Promise<IpfsImageUploadResponse>;
|
|
13
12
|
ipfsImageFetchFn?: (_data: IpfsImageFetchPayload) => Promise<{
|
|
14
13
|
url: string;
|
|
@@ -25,7 +25,5 @@ export interface DocumentMobileTabPanelProps {
|
|
|
25
25
|
isVersionHistoryMode: boolean;
|
|
26
26
|
isConnected?: boolean;
|
|
27
27
|
isFocusMode?: boolean;
|
|
28
|
-
layout?: DocumentOutlineProps['layout'];
|
|
29
|
-
tabPanelSlot?: HTMLElement | null;
|
|
30
28
|
}
|
|
31
|
-
export declare const DocumentMobileTabPanel: ({ tabs, activeTabId, setActiveTabId, editor, items, setItems, orientation, renameTab, createTab, duplicateTab, orderTab, deleteTab, tabCommentCounts, isPreviewMode, tabConfig, isVersionHistoryMode, isConnected, isFocusMode,
|
|
29
|
+
export declare const DocumentMobileTabPanel: ({ tabs, activeTabId, setActiveTabId, editor, items, setItems, orientation, renameTab, createTab, duplicateTab, orderTab, deleteTab, tabCommentCounts, isPreviewMode, tabConfig, isVersionHistoryMode, isConnected, isFocusMode, }: DocumentMobileTabPanelProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -32,10 +32,9 @@ export interface DocumentTabsSidebarProps {
|
|
|
32
32
|
deleteTab?: (tabId: string) => void;
|
|
33
33
|
isConnected?: boolean;
|
|
34
34
|
isFocusMode?: boolean;
|
|
35
|
-
layout?: DocumentOutlineProps['layout'];
|
|
36
35
|
}
|
|
37
36
|
export declare const DocumentTabsSidebar: ({ tabSectionContainer, ...rest }: DocumentTabsSidebarProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
38
|
-
export declare const TabSidebar: ({ tabs, activeTabId, setActiveTabId, showTOC, setShowTOC, hasToC, isPreviewMode, editor, items, setItems, orientation, createTab, renameTab, duplicateTab, orderTab, tabCommentCounts, isVersionHistoryMode, tabConfig, deleteTab, isConnected, isFocusMode,
|
|
37
|
+
export declare const TabSidebar: ({ tabs, activeTabId, setActiveTabId, showTOC, setShowTOC, hasToC, isPreviewMode, editor, items, setItems, orientation, createTab, renameTab, duplicateTab, orderTab, tabCommentCounts, isVersionHistoryMode, tabConfig, deleteTab, isConnected, isFocusMode, }: DocumentTabsSidebarProps) => import("react/jsx-runtime").JSX.Element;
|
|
39
38
|
export declare const DdocTab: ({ tab, tabIndex, tabCount, handleEmojiChange, handleNameChange, onClick, editor, tocItem, setTocItems, orientation, activeTabId, duplicateTab, commentCount, moveTabUp, moveTabDown, isPreviewMode, isVersionHistoryMode, tabConfig, onDelete, showOutline, onShowOutlineChange, isConnected, }: {
|
|
40
39
|
tab: Tab;
|
|
41
40
|
tabIndex: number;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { DocumentOutlineProps } from './types';
|
|
2
2
|
|
|
3
|
-
export declare const DocumentOutline: ({ editor, hasToC, items, setItems, showTOC, setShowTOC, isPreviewMode, orientation, tabs, setTabs, activeTabId, setActiveTabId, createTab, renameTab, duplicateTab, orderTab, ydoc, tabCommentCounts, tabSectionContainer, isVersionHistoryMode, tabConfig, deleteTab, isConnected, isFocusMode,
|
|
3
|
+
export declare const DocumentOutline: ({ editor, hasToC, items, setItems, showTOC, setShowTOC, isPreviewMode, orientation, tabs, setTabs, activeTabId, setActiveTabId, createTab, renameTab, duplicateTab, orderTab, ydoc, tabCommentCounts, tabSectionContainer, isVersionHistoryMode, tabConfig, deleteTab, isConnected, isFocusMode, }: DocumentOutlineProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ToCProps, ToCItemProps } from './types';
|
|
2
2
|
|
|
3
|
-
export declare const ToCItem: import('react').MemoExoticComponent<({ item, onItemClick, index, orientation,
|
|
3
|
+
export declare const ToCItem: import('react').MemoExoticComponent<({ item, onItemClick, index, orientation, }: ToCItemProps) => import("react/jsx-runtime").JSX.Element>;
|
|
4
4
|
export declare const ToCEmptyState: import('react').MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
|
|
5
5
|
export declare const ToC: import('react').MemoExoticComponent<({ items, editor, setItems, orientation }: ToCProps) => import("react/jsx-runtime").JSX.Element>;
|
|
@@ -22,10 +22,6 @@ export type ToCItemProps = {
|
|
|
22
22
|
onItemRemove: (e: React.MouseEvent, id: string) => void;
|
|
23
23
|
index: number;
|
|
24
24
|
orientation?: 'portrait' | 'landscape';
|
|
25
|
-
/** At or below 1280px: the item is in the mobile list. */
|
|
26
|
-
isMobile?: boolean;
|
|
27
|
-
/** At or above 1280px: the item takes the rail's width caps. */
|
|
28
|
-
isWide?: boolean;
|
|
29
25
|
};
|
|
30
26
|
export interface DocumentOutlineProps {
|
|
31
27
|
editor: Editor;
|
|
@@ -58,7 +54,4 @@ export interface DocumentOutlineProps {
|
|
|
58
54
|
deleteTab?: (tabId: string) => void;
|
|
59
55
|
isConnected?: boolean;
|
|
60
56
|
isFocusMode?: boolean;
|
|
61
|
-
/** 'contained': sized from the editor canvas. Only DdocEditor passes it. */
|
|
62
|
-
layout?: 'viewport' | 'contained';
|
|
63
|
-
tabPanelSlot?: HTMLElement | null;
|
|
64
57
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { SchemaStampGuardEvent } from '../types';
|
|
1
2
|
import * as Y from 'yjs';
|
|
2
3
|
interface UseDocSchemaVersionArgs {
|
|
3
4
|
ydoc: Y.Doc;
|
|
4
5
|
isNewDdoc: boolean;
|
|
5
6
|
isContentResolved: boolean;
|
|
6
7
|
preferredSchemaVersion?: number;
|
|
8
|
+
onSchemaStampGuard?: (event: SchemaStampGuardEvent) => void;
|
|
7
9
|
}
|
|
8
|
-
export declare const useDocSchemaVersion: ({ ydoc, isNewDdoc, isContentResolved, preferredSchemaVersion, }: UseDocSchemaVersionArgs) => {
|
|
10
|
+
export declare const useDocSchemaVersion: ({ ydoc, isNewDdoc, isContentResolved, preferredSchemaVersion, onSchemaStampGuard, }: UseDocSchemaVersionArgs) => {
|
|
9
11
|
docSchemaVersion: number;
|
|
10
12
|
isSchemaUnsupported: boolean;
|
|
11
13
|
};
|
|
@@ -33,6 +33,10 @@ export declare const useExportHeadlessEditorContent: (props?: UseHeadlessEditorP
|
|
|
33
33
|
downloadContentAsTxt: (content: string | string[] | JSONContent, title: string) => Promise<void>;
|
|
34
34
|
mergeYjsUpdates: (contents: string[]) => string;
|
|
35
35
|
handleMarkdownContent: typeof import('../extensions/mardown-paste-handler').handleMarkdownContent;
|
|
36
|
-
getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<import('../types').IpfsImageUploadResponse
|
|
37
|
-
|
|
36
|
+
getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<import('../types').IpfsImageUploadResponse>, options?: {
|
|
37
|
+
schemaVersion?: number;
|
|
38
|
+
}) => Promise<string | null>;
|
|
39
|
+
getYjsContentFromDocx: (file: File, ipfsImageUploadFn: (file: File) => Promise<import('../types').IpfsImageUploadResponse>, options?: {
|
|
40
|
+
schemaVersion?: number;
|
|
41
|
+
}) => Promise<string | null>;
|
|
38
42
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
type UseFocusModeOptions = {
|
|
2
|
-
/**
|
|
2
|
+
/** Notify callback — fires on every toggle in both modes (existing API). */
|
|
3
3
|
onFocusMode?: (isFocusMode: boolean) => void;
|
|
4
|
+
/** Controlled value (D6). Omit for the legacy internal-state behavior. */
|
|
4
5
|
isFocusMode?: boolean;
|
|
6
|
+
/** Controlled setter — receives the next value instead of internal mutation. */
|
|
5
7
|
onFocusModeChange?: (isFocusMode: boolean) => void;
|
|
6
8
|
};
|
|
7
9
|
export declare const useFocusMode: ({ onFocusMode, isFocusMode: controlledValue, onFocusModeChange, }?: UseFocusModeOptions) => {
|
|
@@ -53,8 +53,12 @@ export declare const createHeadlessEditorRuntime: (props?: UseHeadlessEditorProp
|
|
|
53
53
|
downloadContentAsTxt: (content: string | string[] | JSONContent, title: string) => Promise<void>;
|
|
54
54
|
mergeYjsUpdates: (contents: string[]) => string;
|
|
55
55
|
handleMarkdownContent: typeof handleMarkdownContent;
|
|
56
|
-
getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse
|
|
57
|
-
|
|
56
|
+
getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>, options?: {
|
|
57
|
+
schemaVersion?: number;
|
|
58
|
+
}) => Promise<string | null>;
|
|
59
|
+
getYjsContentFromDocx: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>, options?: {
|
|
60
|
+
schemaVersion?: number;
|
|
61
|
+
}) => Promise<string | null>;
|
|
58
62
|
};
|
|
59
63
|
export declare const useHeadlessEditor: (props?: UseHeadlessEditorProps) => {
|
|
60
64
|
setContent: (initialContent: string | string[] | JSONContent, editor: Editor, ydoc: Y.Doc) => void;
|
|
@@ -76,6 +80,10 @@ export declare const useHeadlessEditor: (props?: UseHeadlessEditorProps) => {
|
|
|
76
80
|
downloadContentAsTxt: (content: string | string[] | JSONContent, title: string) => Promise<void>;
|
|
77
81
|
mergeYjsUpdates: (contents: string[]) => string;
|
|
78
82
|
handleMarkdownContent: typeof handleMarkdownContent;
|
|
79
|
-
getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse
|
|
80
|
-
|
|
83
|
+
getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>, options?: {
|
|
84
|
+
schemaVersion?: number;
|
|
85
|
+
}) => Promise<string | null>;
|
|
86
|
+
getYjsContentFromDocx: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>, options?: {
|
|
87
|
+
schemaVersion?: number;
|
|
88
|
+
}) => Promise<string | null>;
|
|
81
89
|
};
|
|
@@ -4,14 +4,12 @@ import { AnyExtension, Editor } from '@tiptap/react';
|
|
|
4
4
|
import { CommentAnchor } from '../extensions/comment/comment-decoration-plugin';
|
|
5
5
|
import { DBlockRuntimeStateRef } from '../extensions/d-block/dblock-runtime';
|
|
6
6
|
import { ToCItemType } from '../components/toc/types';
|
|
7
|
-
import { EditorWidthStore } from '../utils/editor-width-store';
|
|
8
7
|
import { CollabConnectionConfig, CollaborationProps } from '../sync-local/types';
|
|
9
8
|
|
|
10
9
|
import * as Y from 'yjs';
|
|
11
10
|
export declare const TAB_EDITOR_ATTRIBUTES: Record<string, string>;
|
|
12
11
|
interface UseTabEditorArgs {
|
|
13
12
|
ydoc: Y.Doc;
|
|
14
|
-
editorWidthStore?: EditorWidthStore;
|
|
15
13
|
isVersionMode?: boolean;
|
|
16
14
|
hasTabState?: boolean;
|
|
17
15
|
versionId?: string;
|
|
@@ -38,7 +36,6 @@ interface UseTabEditorArgs {
|
|
|
38
36
|
setWordCount?: DdocProps['setWordCount'];
|
|
39
37
|
setSelectedWordCount?: DdocProps['setSelectedWordCount'];
|
|
40
38
|
setPageCount?: DdocProps['setPageCount'];
|
|
41
|
-
onStatsChange?: DdocProps['onStatsChange'];
|
|
42
39
|
setIsContentLoading: Dispatch<SetStateAction<boolean>>;
|
|
43
40
|
setIsCollabContentLoading: Dispatch<SetStateAction<boolean>>;
|
|
44
41
|
unFocused?: boolean;
|
|
@@ -61,7 +58,7 @@ interface UseTabEditorArgs {
|
|
|
61
58
|
dBlockRuntimeStateRef?: DBlockRuntimeStateRef;
|
|
62
59
|
docSchemaVersion?: number;
|
|
63
60
|
}
|
|
64
|
-
export declare const useTabEditor: ({ ydoc,
|
|
61
|
+
export declare const useTabEditor: ({ ydoc, isVersionMode, hasTabState, versionId, isPreviewMode, viewerMode, initialContent, collaboration, isReady, awareness, disableInlineComment, isFocusMode, onCommentInteraction, onError, ipfsImageUploadFn, metadataProxyUrl, onCopyHeadingLink, ipfsImageFetchFn, fetchV1ImageFn, isConnected, activeModel, maxTokens, isAIAgentEnabled, setCharacterCount, setWordCount, setSelectedWordCount, setPageCount, setIsContentLoading, setIsCollabContentLoading, unFocused, zoomLevel, isPresentationMode, onInvalidContentError, ignoreCorruptedData, onConnect, onDisconnect, isIndexeddbSynced, hasCollabContentInitialised, initialiseYjsIndexedDbProvider, externalExtensions, isContentLoading, activeTabId, tabIds, theme, editorRef, initialCommentAnchors, dBlockRuntimeStateRef, docSchemaVersion, }: UseTabEditorArgs) => {
|
|
65
62
|
editor: Editor | null;
|
|
66
63
|
cachedEditorEntries: import('./use-tab-editor-cache').CachedTabEditorRenderEntry[];
|
|
67
64
|
ref: import('react').RefObject<HTMLDivElement>;
|
|
@@ -7,6 +7,8 @@ interface UseTabManagerArgs {
|
|
|
7
7
|
initialContent: DdocProps['initialContent'];
|
|
8
8
|
enableCollaboration?: boolean;
|
|
9
9
|
isDDocOwner: boolean;
|
|
10
|
+
/** Overrides the derived "brand-new document" (see DdocProps.isNewDocument). */
|
|
11
|
+
isNewDocument?: boolean;
|
|
10
12
|
createDefaultTabIfMissing: boolean;
|
|
11
13
|
shouldSyncActiveTab: boolean;
|
|
12
14
|
defaultTabId?: string;
|
|
@@ -16,7 +18,7 @@ interface UseTabManagerArgs {
|
|
|
16
18
|
flushPendingUpdate?: () => void;
|
|
17
19
|
}
|
|
18
20
|
export declare const getNewTabId: () => string;
|
|
19
|
-
export declare const useTabManager: ({ ydoc, initialContent, enableCollaboration, isDDocOwner, createDefaultTabIfMissing, shouldSyncActiveTab, defaultTabId, preferFirstTabOnInit, onVersionHistoryActiveTabChange, flushPendingUpdate, getEditor, }: UseTabManagerArgs) => {
|
|
21
|
+
export declare const useTabManager: ({ ydoc, initialContent, enableCollaboration, isDDocOwner, isNewDocument, createDefaultTabIfMissing, shouldSyncActiveTab, defaultTabId, preferFirstTabOnInit, onVersionHistoryActiveTabChange, flushPendingUpdate, getEditor, }: UseTabManagerArgs) => {
|
|
20
22
|
tabs: Tab[];
|
|
21
23
|
hasTabState: boolean;
|
|
22
24
|
activeTabId: string;
|
|
@@ -3,13 +3,19 @@ import { DdocProps } from '../types';
|
|
|
3
3
|
import * as Y from 'yjs';
|
|
4
4
|
interface UseYjsSetupArgs {
|
|
5
5
|
onChange?: DdocProps['onChange'];
|
|
6
|
+
onLocalChange?: DdocProps['onLocalChange'];
|
|
6
7
|
enableIndexeddbSync?: boolean;
|
|
7
8
|
ddocId?: string;
|
|
8
9
|
collaboration?: CollaborationProps;
|
|
9
10
|
onCollaboratorChange?: DdocProps['onCollaboratorChange'];
|
|
10
11
|
onIndexedDbError?: (error: Error) => void;
|
|
12
|
+
/**
|
|
13
|
+
* How long to wait for y-indexeddb before checking whether the store is
|
|
14
|
+
* corrupt. Internal; tests shorten it.
|
|
15
|
+
*/
|
|
16
|
+
indexeddbStallCheckMs?: number;
|
|
11
17
|
}
|
|
12
|
-
export declare const useYjsSetup: ({ onChange, enableIndexeddbSync, ddocId, collaboration, onCollaboratorChange, onIndexedDbError, }: UseYjsSetupArgs) => {
|
|
18
|
+
export declare const useYjsSetup: ({ onChange, onLocalChange, enableIndexeddbSync, ddocId, collaboration, onCollaboratorChange, onIndexedDbError, indexeddbStallCheckMs, }: UseYjsSetupArgs) => {
|
|
13
19
|
ydoc: Y.Doc;
|
|
14
20
|
onConnect: (connectConfig: import('../types').CollabConnectionConfig) => void;
|
|
15
21
|
onDisconnect: () => void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { IndexeddbPersistence } from 'y-indexeddb';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Below this many already-replayed rows the store is left alone. A normal
|
|
5
|
+
* open leaves two or three (the previous compacted row, the last session's
|
|
6
|
+
* edits and y-indexeddb's attach row), so rewriting the full doc then would
|
|
7
|
+
* buy little.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_COMPACTION_MIN_ROWS = 3;
|
|
10
|
+
/** Is this one of compaction's own transactions? */
|
|
11
|
+
export declare const isCompactionTransaction: (transaction: IDBTransaction) => boolean;
|
|
12
|
+
/**
|
|
13
|
+
* y-indexeddb appends a copy of the doc every time a provider attaches, and
|
|
14
|
+
* only trims after 500 rows. The editor applies the consumer's initialContent
|
|
15
|
+
* before attaching, so each open stores a FULL copy of the doc and every load
|
|
16
|
+
* replays them all.
|
|
17
|
+
*
|
|
18
|
+
* During sync y-indexeddb applies every stored row to the doc and then sets
|
|
19
|
+
* `_dbref` to one past the last key. Every row below `_dbref` is therefore
|
|
20
|
+
* already in `persistence.doc`, so those rows are replaced with a single
|
|
21
|
+
* `encodeStateAsUpdate(doc)` without reading or merging them. Rows at or above
|
|
22
|
+
* `_dbref` (later edits, writes from another tab) are never touched.
|
|
23
|
+
*
|
|
24
|
+
* Runs in ONE readwrite transaction: any failure rolls back to the original
|
|
25
|
+
* rows. Returns the number of rows replaced (0 when nothing was compacted).
|
|
26
|
+
*/
|
|
27
|
+
export declare const compactDdocPersistence: (persistence: IndexeddbPersistence, minRows?: number) => Promise<number>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
type ClockRange = [start: number, end: number];
|
|
3
|
+
/**
|
|
4
|
+
* Join ranges that touch. Input is sorted by start, as the store keeps it.
|
|
5
|
+
* Lossless: the result covers exactly the same clocks, written one way, so
|
|
6
|
+
* the description does not depend on how Yjs split or joined items.
|
|
7
|
+
*/
|
|
8
|
+
export declare const mergeRanges: (ranges: ClockRange[]) => ClockRange[];
|
|
9
|
+
/**
|
|
10
|
+
* A canonical description of a doc's content: for every writer, which clock
|
|
11
|
+
* ranges it inserted and which of those are deleted, leaving out the
|
|
12
|
+
* active-tab pointer.
|
|
13
|
+
*
|
|
14
|
+
* Every content edit (text, formatting, a tab rename or reorder, any root a
|
|
15
|
+
* later package version adds) inserts or deletes a struct, so it changes this.
|
|
16
|
+
* The active tab is per-user UI state written with the `'self'` origin
|
|
17
|
+
* (use-tab-manager.ts) and is skipped, so a tab switch does not. The rule
|
|
18
|
+
* lists what to skip rather than what to include, so a mistake can only make
|
|
19
|
+
* an unchanged doc look changed, never hide an edit.
|
|
20
|
+
*
|
|
21
|
+
* Ranges are merged so the result does not depend on how Yjs split or joined
|
|
22
|
+
* items: the same state gives the same description however it was produced.
|
|
23
|
+
*/
|
|
24
|
+
export declare const describeDdocContent: (doc: Y.Doc) => string;
|
|
25
|
+
/** SHA-256 of `describeDdocContent`, base64 (44 characters). */
|
|
26
|
+
export declare const fingerprintDdocContent: (doc: Y.Doc) => Promise<string>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read, merge into, delete and fingerprint a dDoc's y-indexeddb database
|
|
3
|
+
* without mounting an editor. The editor's IndexeddbPersistence names the
|
|
4
|
+
* database after the ddocId and stores Yjs updates in the `updates` store;
|
|
5
|
+
* these functions read and write that same layout.
|
|
6
|
+
*
|
|
7
|
+
* Nothing here imports React or the editor, so it is safe to call from app
|
|
8
|
+
* services and workers.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_DDOC_PERSISTENCE_TIMEOUT_MS = 8000;
|
|
11
|
+
/**
|
|
12
|
+
* `name` of the Error passed to `onIndexedDbError` when the editor repaired a
|
|
13
|
+
* corrupt store on load. It is a notice, not a failed save: the store is
|
|
14
|
+
* working again, but the unreadable rows it dropped are gone.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DDOC_STORE_REPAIRED = "DdocStoreRepaired";
|
|
17
|
+
export type DdocContentStatus = 'available' | 'empty' | 'missing' | 'corrupt' | 'timed-out' | 'unavailable';
|
|
18
|
+
export type DdocContentSnapshot = {
|
|
19
|
+
ddocId: string;
|
|
20
|
+
status: DdocContentStatus;
|
|
21
|
+
/** base64 Yjs update of the whole doc; null unless available or empty. */
|
|
22
|
+
encodedState: string | null;
|
|
23
|
+
error?: Error;
|
|
24
|
+
};
|
|
25
|
+
export type DdocContentMergeResult = DdocContentSnapshot & {
|
|
26
|
+
/** True when the merge wrote something the database did not have. */
|
|
27
|
+
changed: boolean;
|
|
28
|
+
/** True when the database did not exist and this merge created it. */
|
|
29
|
+
created: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type DdocContentDeleteResult = {
|
|
32
|
+
ddocId: string;
|
|
33
|
+
status: 'deleted' | 'blocked' | 'unavailable';
|
|
34
|
+
error?: Error;
|
|
35
|
+
};
|
|
36
|
+
export type DdocPersistenceOptions = {
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Read the doc's database as stored: every row replayed in key order, the
|
|
41
|
+
* same thing the editor loads on open. Never creates a database. Every call
|
|
42
|
+
* reads afresh: a read already under way may have started before the latest
|
|
43
|
+
* edit was written, so its result is never handed to a later caller.
|
|
44
|
+
*/
|
|
45
|
+
export declare const readDdocContent: (ddocId: string, options?: DdocPersistenceOptions) => Promise<DdocContentSnapshot>;
|
|
46
|
+
/**
|
|
47
|
+
* Merge a full Yjs state into the doc's database, creating the database if
|
|
48
|
+
* it does not exist. Only the part the database does not already hold is
|
|
49
|
+
* written, as one row, in one readwrite transaction; merging state that is
|
|
50
|
+
* already stored writes nothing.
|
|
51
|
+
*
|
|
52
|
+
* An editor open in another tab sees the new row on its next load. An editor
|
|
53
|
+
* open in this tab does not (its Y.Doc is in memory), so callers with a
|
|
54
|
+
* mounted editor merge into its live doc instead.
|
|
55
|
+
*
|
|
56
|
+
* Expects a complete state (`Y.encodeStateAsUpdate` of a doc). An update whose
|
|
57
|
+
* new parts all depend on updates the store does not have yet changes nothing
|
|
58
|
+
* visible, and is not stored.
|
|
59
|
+
*/
|
|
60
|
+
export declare const mergeDdocContent: (ddocId: string, encodedState: string, options?: DdocPersistenceOptions) => Promise<DdocContentMergeResult>;
|
|
61
|
+
export type DdocContentRepairResult = {
|
|
62
|
+
ddocId: string;
|
|
63
|
+
/**
|
|
64
|
+
* `repaired`: bad rows were dropped. `healthy`: nothing to drop, nothing
|
|
65
|
+
* written. `missing`: no database. `corrupt`: the database has no `updates`
|
|
66
|
+
* store, so there are no rows to keep.
|
|
67
|
+
*/
|
|
68
|
+
status: 'repaired' | 'healthy' | 'missing' | 'corrupt' | 'timed-out' | 'unavailable';
|
|
69
|
+
droppedRows: number;
|
|
70
|
+
error?: Error;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Drop the stored rows that are not valid Yjs updates. One row y-indexeddb
|
|
74
|
+
* cannot apply makes its replay throw and never finish, so an editor waiting
|
|
75
|
+
* for it never loads. Each row is checked on its own; only the bad ones are
|
|
76
|
+
* deleted, in one readwrite transaction, and the good rows are left exactly as
|
|
77
|
+
* they are. Their content is lost, but it was already unreadable. Writes
|
|
78
|
+
* nothing when every row is good. Never creates a database.
|
|
79
|
+
*/
|
|
80
|
+
export declare const repairDdocContent: (ddocId: string, options?: DdocPersistenceOptions) => Promise<DdocContentRepairResult>;
|
|
81
|
+
/**
|
|
82
|
+
* Delete the doc's database. Resolves `blocked` when another connection
|
|
83
|
+
* (an editor in another tab, for example) still holds it after the timeout;
|
|
84
|
+
* the browser keeps the delete queued and completes it once that connection
|
|
85
|
+
* closes. A missing database counts as deleted.
|
|
86
|
+
*/
|
|
87
|
+
export declare const deleteDdocContent: (ddocId: string, options?: DdocPersistenceOptions) => Promise<DdocContentDeleteResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Short, stable fingerprint of a doc's content (44 characters). Changes on
|
|
90
|
+
* every content edit, including delete-only and formatting-only edits; does
|
|
91
|
+
* not change when only the active tab changes. The same content gives the
|
|
92
|
+
* same fingerprint however the state was produced. See
|
|
93
|
+
* `describeDdocContent` for the rule. Uses `crypto.subtle`, which browsers
|
|
94
|
+
* only provide on secure (https or localhost) pages.
|
|
95
|
+
*/
|
|
96
|
+
export declare const getDdocContentFingerprint: (encodedState: string) => Promise<string>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { DDOC_STORE_REPAIRED, DEFAULT_DDOC_PERSISTENCE_TIMEOUT_MS, deleteDdocContent, getDdocContentFingerprint, mergeDdocContent, readDdocContent, repairDdocContent, } from './ddoc-persistence';
|
|
2
|
+
export type { DdocContentDeleteResult, DdocContentMergeResult, DdocContentRepairResult, DdocContentSnapshot, DdocContentStatus, DdocPersistenceOptions, } from './ddoc-persistence';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { IndexeddbPersistence } from 'y-indexeddb';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* y-indexeddb's `whenSynced` resolves only on its 'synced' event. If
|
|
5
|
+
* `indexedDB.open` rejects, it never settles and nothing reports the failure.
|
|
6
|
+
* Reject on an open failure so the caller's error path runs.
|
|
7
|
+
*/
|
|
8
|
+
export declare const waitForIndexeddbSync: (persistence: IndexeddbPersistence) => Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Like `waitForIndexeddbSync`, but resolves `stalled` if the provider has not
|
|
11
|
+
* synced after `stallMs`, so the caller can check why. The underlying wait is
|
|
12
|
+
* not cancelled; wait again to keep waiting.
|
|
13
|
+
*/
|
|
14
|
+
export declare const waitForIndexeddbSyncOrStall: (persistence: IndexeddbPersistence, stallMs: number) => Promise<"synced" | "stalled">;
|
|
15
|
+
type WatchOptions = {
|
|
16
|
+
/** Called for every failed write and for a lost connection. */
|
|
17
|
+
onError: (error: Error) => void;
|
|
18
|
+
/** Called once the provider has been detached from the doc. */
|
|
19
|
+
onDetached: () => void;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Watch a synced provider's connection for the failures y-indexeddb itself
|
|
23
|
+
* ignores:
|
|
24
|
+
*
|
|
25
|
+
* - A failed write aborts its transaction, and `abort` bubbles to the
|
|
26
|
+
* database. y-indexeddb never looks, so quota errors and the like are lost.
|
|
27
|
+
* - The browser can close the connection (site data cleared, storage
|
|
28
|
+
* pressure: `close`), and a delete from another tab waits on it
|
|
29
|
+
* (`versionchange`). After either, y-indexeddb's update handler throws
|
|
30
|
+
* InvalidStateError into the next edit. Destroy the provider instead: it
|
|
31
|
+
* unsubscribes from doc updates before closing, so editing continues in
|
|
32
|
+
* memory, and a pending delete can proceed.
|
|
33
|
+
*
|
|
34
|
+
* Returns a function that removes the listeners.
|
|
35
|
+
*/
|
|
36
|
+
export declare const watchIndexeddbConnection: (persistence: IndexeddbPersistence, { onError, onDetached }: WatchOptions) => (() => void);
|
|
37
|
+
export {};
|
|
@@ -57,9 +57,10 @@ export declare class SyncManager {
|
|
|
57
57
|
private ydoc;
|
|
58
58
|
private callbacksRef;
|
|
59
59
|
private onLocalUpdate?;
|
|
60
|
+
private onLocalChange?;
|
|
60
61
|
constructor(config: SyncManagerConfig, onCollabStateChange: (state: CollabState) => void);
|
|
61
62
|
/** Called by useSyncManager on every render to keep refs fresh */
|
|
62
|
-
updateRefs(_services: CollabServices | undefined, callbacks: CollabCallbacks | undefined, onLocalUpdate?: (updatedDocContent: string, updateChunk: string) => void): void;
|
|
63
|
+
updateRefs(_services: CollabServices | undefined, callbacks: CollabCallbacks | undefined, onLocalUpdate?: (updatedDocContent: string, updateChunk: string) => void, onLocalChange?: () => void): void;
|
|
63
64
|
get isConnected(): boolean;
|
|
64
65
|
get isReady(): boolean;
|
|
65
66
|
get isResuming(): boolean;
|
|
@@ -135,6 +136,12 @@ export declare class SyncManager {
|
|
|
135
136
|
private processNextUpdate;
|
|
136
137
|
private handleRemoteContentUpdate;
|
|
137
138
|
private applyRemoteYjsUpdate;
|
|
139
|
+
/**
|
|
140
|
+
* Tell the host that a collaborator's update changed the doc. The full
|
|
141
|
+
* document is encoded only for a host that asked for it (`onLocalUpdate`);
|
|
142
|
+
* `onLocalChange` gets the signal without that cost.
|
|
143
|
+
*/
|
|
144
|
+
private notifyHostOfRemoteChange;
|
|
138
145
|
private applyQueuedRemoteContents;
|
|
139
146
|
private withRetry;
|
|
140
147
|
private disconnectInternal;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type * as ucans from '@ucans/ucans';
|
|
2
|
+
/**
|
|
3
|
+
* The collab server's clock, learned from its handshake. The server checks auth UCAN nbf/exp
|
|
4
|
+
* against its own clock, so tokens built from a skewed device clock get a 401.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ServerClock {
|
|
7
|
+
private readonly wallNow;
|
|
8
|
+
private readonly monoNow;
|
|
9
|
+
private anchor;
|
|
10
|
+
constructor(wallNow?: () => number, monoNow?: (() => number) | null);
|
|
11
|
+
/**
|
|
12
|
+
* Time since the handshake is measured on the monotonic clock, so a device clock change
|
|
13
|
+
* mid-session cannot skew it. A missing or implausible stamp (older server) falls back to the
|
|
14
|
+
* device clock. Delivery delay and sleep only make the estimate trail the server, which the
|
|
15
|
+
* token window absorbs.
|
|
16
|
+
*/
|
|
17
|
+
sync(serverTimeMs: unknown): void;
|
|
18
|
+
nowSeconds(): number;
|
|
19
|
+
ucanWindow(): {
|
|
20
|
+
notBefore: number;
|
|
21
|
+
expiration: number;
|
|
22
|
+
};
|
|
23
|
+
/** Whether a cached UCAN can still be presented: active, and not expiring within the margin. */
|
|
24
|
+
isUsable(ucan: ucans.Ucan): boolean;
|
|
25
|
+
private elapsedBase;
|
|
26
|
+
}
|
|
@@ -74,6 +74,7 @@ export declare class SocketClient {
|
|
|
74
74
|
private contractAddress?;
|
|
75
75
|
private ownerUcan?;
|
|
76
76
|
private collaborationUcan?;
|
|
77
|
+
private serverClock;
|
|
77
78
|
private ownerAddress?;
|
|
78
79
|
private ddocId?;
|
|
79
80
|
private editLock?;
|
|
@@ -130,7 +131,6 @@ export declare class SocketClient {
|
|
|
130
131
|
terminateSession: () => Promise<void>;
|
|
131
132
|
flushBeacon(mergedUpdate: string): void;
|
|
132
133
|
private getCollaborationKeyPair;
|
|
133
|
-
private isUcanValid;
|
|
134
134
|
private getOwnerToken;
|
|
135
135
|
private buildSessionToken;
|
|
136
136
|
private _authenticate;
|
|
@@ -199,6 +199,8 @@ export interface SyncManagerConfig {
|
|
|
199
199
|
services?: CollabServices;
|
|
200
200
|
callbacks?: CollabCallbacks;
|
|
201
201
|
onLocalUpdate?: (updatedDocContent: Data['editorJSONData'], updateChunk: string) => void;
|
|
202
|
+
/** Like `onLocalUpdate` but without arguments, so no full-document encode. */
|
|
203
|
+
onLocalChange?: () => void;
|
|
202
204
|
/** Origins to ignore in the ydoc update handler (e.g. IndexedDB provider) */
|
|
203
205
|
ignoredOrigins?: Array<{
|
|
204
206
|
current: unknown;
|