Skip to content

Commit 3c6006d

Browse files
[PE-31] feat: Add lock unlock archive restore realtime sync (#5629)
* fix: add lock unlock archive restore realtime sync * fix: show only after editor loads * fix: added strong types * fix: live events fixed * fix: remove unused vars and logs * fix: converted objects to enum * fix: error handling and removing the events in read only mode * fix: added check to only update if the image aspect ratio is not present already * fix: imports * fix: props order * revert: no need of these changes anymore * fix: updated type names * fix: order of things * fix: fixed types and renamed variables * fix: better typing for the real time updates * fix: trying multiplexing our socket connection * fix: multiplexing socket connection in read only editor as well * fix: remove single socket logic * fix: fixing the cleanup deps for the provider and localprovider * fix: add a better data structure for managing events * chore: refactored realtime events into hooks * feat: fetch page meta while focusing tabs * fix: cycling through items on slash command item in down arrow * fix: better naming convention for realtime events * fix: simplified localprovider initialization and cleaning * fix: types from ui * fix: abstracted away from exposing the provider directly * fix: coderabbit suggestions * regression: pass user in dependency array * fix: removed page action api calls by the other users the document is synced with * chore: removed unused imports
1 parent 8c04aa6 commit 3c6006d

File tree

21 files changed

+275
-113
lines changed

21 files changed

+275
-113
lines changed

live/src/core/hocuspocus-server.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { v4 as uuidv4 } from "uuid";
44
import { handleAuthentication } from "@/core/lib/authentication.js";
55
// extensions
66
import { getExtensions } from "@/core/extensions/index.js";
7+
import {
8+
DocumentCollaborativeEvents,
9+
TDocumentEventsServer,
10+
} from "@plane/editor/lib";
711
// editor types
812
import { TUserDetails } from "@plane/editor";
913
// types
@@ -55,6 +59,14 @@ export const getHocusPocusServer = async () => {
5559
throw Error("Authentication unsuccessful!");
5660
}
5761
},
62+
async onStateless({ payload, document }) {
63+
// broadcast the client event (derived from the server event) to all the clients so that they can update their state
64+
const response =
65+
DocumentCollaborativeEvents[payload as TDocumentEventsServer].client;
66+
if (response) {
67+
document.broadcastStateless(response);
68+
}
69+
},
5870
extensions,
5971
debounce: 10000,
6072
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const DocumentCollaborativeEvents = {
2+
lock: { client: "locked", server: "lock" },
3+
unlock: { client: "unlocked", server: "unlock" },
4+
archive: { client: "archived", server: "archive" },
5+
unarchive: { client: "unarchived", server: "unarchive" },
6+
} as const;

packages/editor/src/core/extensions/custom-image/components/image-block.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
118118
height: `${Math.round(initialHeight)}px` satisfies Pixel,
119119
aspectRatio: aspectRatioCalculated,
120120
};
121-
122121
setSize(initialComputedSize);
123122
updateAttributesSafely(
124123
initialComputedSize,

packages/editor/src/core/extensions/custom-image/components/image-node.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,9 @@ export const CustomImageNode = (props: CustomImageNodeProps) => {
2929

3030
useEffect(() => {
3131
const closestEditorContainer = imageComponentRef.current?.closest(".editor-container");
32-
if (!closestEditorContainer) {
33-
console.error("Editor container not found");
34-
return;
32+
if (closestEditorContainer) {
33+
setEditorContainer(closestEditorContainer as HTMLDivElement);
3534
}
36-
37-
setEditorContainer(closestEditorContainer as HTMLDivElement);
3835
}, []);
3936

4037
// the image is already uploaded if the image-component node has src attribute
@@ -55,7 +52,7 @@ export const CustomImageNode = (props: CustomImageNodeProps) => {
5552
setResolvedSrc(url as string);
5653
};
5754
getImageSource();
58-
}, [imageFromFileSystem, node.attrs.src]);
55+
}, [imgNodeSrc]);
5956

6057
return (
6158
<NodeViewWrapper>

packages/editor/src/core/extensions/slash-commands/command-menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const SlashCommandsMenu = (props: SlashCommandsMenuProps) => {
4141
if (nextItem < 0) {
4242
nextSection = currentSection - 1;
4343
if (nextSection < 0) nextSection = sections.length - 1;
44-
nextItem = sections[nextSection].items.length - 1;
44+
nextItem = sections[nextSection]?.items.length - 1;
4545
}
4646
}
4747
if (e.key === "ArrowDown") {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { DocumentCollaborativeEvents } from "@/constants/document-collaborative-events";
2+
import { TDocumentEventKey, TDocumentEventsClient, TDocumentEventsServer } from "@/types/document-collaborative-events";
3+
4+
export const getServerEventName = (clientEvent: TDocumentEventsClient): TDocumentEventsServer | undefined => {
5+
for (const key in DocumentCollaborativeEvents) {
6+
if (DocumentCollaborativeEvents[key as TDocumentEventKey].client === clientEvent) {
7+
return DocumentCollaborativeEvents[key as TDocumentEventKey].server;
8+
}
9+
}
10+
return undefined;
11+
};

packages/editor/src/core/hooks/use-collaborative-editor.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
1+
import { useEffect, useMemo, useState } from "react";
22
import { HocuspocusProvider } from "@hocuspocus/provider";
33
import Collaboration from "@tiptap/extension-collaboration";
44
import { IndexeddbPersistence } from "y-indexeddb";
@@ -58,21 +58,19 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
5858
[id, realtimeConfig, serverHandler, user]
5959
);
6060

61-
// destroy and disconnect connection on unmount
61+
const localProvider = useMemo(
62+
() => (id ? new IndexeddbPersistence(id, provider.document) : undefined),
63+
[id, provider]
64+
);
65+
66+
// destroy and disconnect all providers connection on unmount
6267
useEffect(
6368
() => () => {
64-
provider.destroy();
65-
provider.disconnect();
69+
provider?.destroy();
70+
localProvider?.destroy();
6671
},
67-
[provider]
72+
[provider, localProvider]
6873
);
69-
// indexed db integration for offline support
70-
useLayoutEffect(() => {
71-
const localProvider = new IndexeddbPersistence(id, provider.document);
72-
return () => {
73-
localProvider?.destroy();
74-
};
75-
}, [provider, id]);
7674

7775
const editor = useEditor({
7876
disabledExtensions,

packages/editor/src/core/hooks/use-editor.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ import { IMarking, scrollSummary, scrollToNodeViaDOMCoordinates } from "@/helper
1616
// props
1717
import { CoreEditorProps } from "@/props";
1818
// types
19-
import {
19+
import type {
20+
TDocumentEventsServer,
2021
EditorRefApi,
2122
IMentionHighlight,
2223
IMentionSuggestion,
2324
TEditorCommands,
24-
TExtensions,
2525
TFileHandler,
26+
TExtensions,
2627
} from "@/types";
2728

2829
export interface CustomEditorProps {
@@ -67,9 +68,9 @@ export const useEditor = (props: CustomEditorProps) => {
6768
onChange,
6869
onTransaction,
6970
placeholder,
70-
provider,
7171
tabIndex,
7272
value,
73+
provider,
7374
autofocus = false,
7475
} = props;
7576
// states
@@ -257,7 +258,7 @@ export const useEditor = (props: CustomEditorProps) => {
257258
if (empty) return null;
258259

259260
const nodesArray: string[] = [];
260-
state.doc.nodesBetween(from, to, (node, pos, parent) => {
261+
state.doc.nodesBetween(from, to, (node, _pos, parent) => {
261262
if (parent === state.doc && editorRef.current) {
262263
const serializer = DOMSerializer.fromSchema(editorRef.current?.schema);
263264
const dom = serializer.serializeNode(node);
@@ -298,6 +299,8 @@ export const useEditor = (props: CustomEditorProps) => {
298299
if (!document) return;
299300
Y.applyUpdate(document, value);
300301
},
302+
emitRealTimeUpdate: (message: TDocumentEventsServer) => provider?.sendStateless(message),
303+
listenToRealTimeUpdate: () => provider && { on: provider.on.bind(provider), off: provider.off.bind(provider) },
301304
}),
302305
[editorRef, savedSelection]
303306
);

packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
1+
import { useEffect, useMemo, useState } from "react";
22
import { HocuspocusProvider } from "@hocuspocus/provider";
33
import Collaboration from "@tiptap/extension-collaboration";
44
import { IndexeddbPersistence } from "y-indexeddb";
@@ -31,8 +31,8 @@ export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEdit
3131
const provider = useMemo(
3232
() =>
3333
new HocuspocusProvider({
34-
url: realtimeConfig.url,
3534
name: id,
35+
url: realtimeConfig.url,
3636
token: JSON.stringify(user),
3737
parameters: realtimeConfig.queryParams,
3838
onAuthenticationFailed: () => {
@@ -48,23 +48,23 @@ export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEdit
4848
},
4949
onSynced: () => setHasServerSynced(true),
5050
}),
51-
[id, realtimeConfig, user]
51+
[id, realtimeConfig, serverHandler, user]
52+
);
53+
54+
// indexed db integration for offline support
55+
const localProvider = useMemo(
56+
() => (id ? new IndexeddbPersistence(id, provider.document) : undefined),
57+
[id, provider]
5258
);
59+
5360
// destroy and disconnect connection on unmount
5461
useEffect(
5562
() => () => {
5663
provider.destroy();
57-
provider.disconnect();
64+
localProvider?.destroy();
5865
},
59-
[provider]
66+
[provider, localProvider]
6067
);
61-
// indexed db integration for offline support
62-
useLayoutEffect(() => {
63-
const localProvider = new IndexeddbPersistence(id, provider.document);
64-
return () => {
65-
localProvider?.destroy();
66-
};
67-
}, [provider, id]);
6868

6969
const editor = useReadOnlyEditor({
7070
disabledExtensions,

packages/editor/src/core/hooks/use-read-only-editor.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import { IMarking, scrollSummary } from "@/helpers/scroll-to-node";
1111
// props
1212
import { CoreReadOnlyEditorProps } from "@/props";
1313
// types
14-
import { EditorReadOnlyRefApi, IMentionHighlight, TExtensions, TFileHandler } from "@/types";
14+
import type {
15+
EditorReadOnlyRefApi,
16+
IMentionHighlight,
17+
TExtensions,
18+
TDocumentEventsServer,
19+
TFileHandler,
20+
} from "@/types";
1521

1622
interface CustomReadOnlyEditorProps {
1723
disabledExtensions: TExtensions[];
@@ -120,6 +126,8 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
120126
editorRef.current?.off("update");
121127
};
122128
},
129+
emitRealTimeUpdate: (message: TDocumentEventsServer) => provider?.sendStateless(message),
130+
listenToRealTimeUpdate: () => provider && { on: provider.on.bind(provider), off: provider.off.bind(provider) },
123131
getHeadings: () => editorRef?.current?.storage.headingList.headings,
124132
}));
125133

0 commit comments

Comments
 (0)