Skip to content

Commit

Permalink
[PE-31] feat: Add lock unlock archive restore realtime sync (#5629)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Palanikannan1437 authored Dec 2, 2024
1 parent 8c04aa6 commit 3c6006d
Show file tree
Hide file tree
Showing 21 changed files with 275 additions and 113 deletions.
12 changes: 12 additions & 0 deletions live/src/core/hocuspocus-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { v4 as uuidv4 } from "uuid";
import { handleAuthentication } from "@/core/lib/authentication.js";
// extensions
import { getExtensions } from "@/core/extensions/index.js";
import {
DocumentCollaborativeEvents,
TDocumentEventsServer,
} from "@plane/editor/lib";
// editor types
import { TUserDetails } from "@plane/editor";
// types
Expand Down Expand Up @@ -55,6 +59,14 @@ export const getHocusPocusServer = async () => {
throw Error("Authentication unsuccessful!");
}
},
async onStateless({ payload, document }) {
// broadcast the client event (derived from the server event) to all the clients so that they can update their state
const response =
DocumentCollaborativeEvents[payload as TDocumentEventsServer].client;
if (response) {
document.broadcastStateless(response);
}
},
extensions,
debounce: 10000,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const DocumentCollaborativeEvents = {
lock: { client: "locked", server: "lock" },
unlock: { client: "unlocked", server: "unlock" },
archive: { client: "archived", server: "archive" },
unarchive: { client: "unarchived", server: "unarchive" },
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
height: `${Math.round(initialHeight)}px` satisfies Pixel,
aspectRatio: aspectRatioCalculated,
};

setSize(initialComputedSize);
updateAttributesSafely(
initialComputedSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,9 @@ export const CustomImageNode = (props: CustomImageNodeProps) => {

useEffect(() => {
const closestEditorContainer = imageComponentRef.current?.closest(".editor-container");
if (!closestEditorContainer) {
console.error("Editor container not found");
return;
if (closestEditorContainer) {
setEditorContainer(closestEditorContainer as HTMLDivElement);
}

setEditorContainer(closestEditorContainer as HTMLDivElement);
}, []);

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

return (
<NodeViewWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const SlashCommandsMenu = (props: SlashCommandsMenuProps) => {
if (nextItem < 0) {
nextSection = currentSection - 1;
if (nextSection < 0) nextSection = sections.length - 1;
nextItem = sections[nextSection].items.length - 1;
nextItem = sections[nextSection]?.items.length - 1;
}
}
if (e.key === "ArrowDown") {
Expand Down
11 changes: 11 additions & 0 deletions packages/editor/src/core/helpers/get-document-server-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DocumentCollaborativeEvents } from "@/constants/document-collaborative-events";
import { TDocumentEventKey, TDocumentEventsClient, TDocumentEventsServer } from "@/types/document-collaborative-events";

export const getServerEventName = (clientEvent: TDocumentEventsClient): TDocumentEventsServer | undefined => {
for (const key in DocumentCollaborativeEvents) {
if (DocumentCollaborativeEvents[key as TDocumentEventKey].client === clientEvent) {
return DocumentCollaborativeEvents[key as TDocumentEventKey].server;
}
}
return undefined;
};
22 changes: 10 additions & 12 deletions packages/editor/src/core/hooks/use-collaborative-editor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { HocuspocusProvider } from "@hocuspocus/provider";
import Collaboration from "@tiptap/extension-collaboration";
import { IndexeddbPersistence } from "y-indexeddb";
Expand Down Expand Up @@ -58,21 +58,19 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
[id, realtimeConfig, serverHandler, user]
);

// destroy and disconnect connection on unmount
const localProvider = useMemo(
() => (id ? new IndexeddbPersistence(id, provider.document) : undefined),
[id, provider]
);

// destroy and disconnect all providers connection on unmount
useEffect(
() => () => {
provider.destroy();
provider.disconnect();
provider?.destroy();
localProvider?.destroy();
},
[provider]
[provider, localProvider]
);
// indexed db integration for offline support
useLayoutEffect(() => {
const localProvider = new IndexeddbPersistence(id, provider.document);
return () => {
localProvider?.destroy();
};
}, [provider, id]);

const editor = useEditor({
disabledExtensions,
Expand Down
11 changes: 7 additions & 4 deletions packages/editor/src/core/hooks/use-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import { IMarking, scrollSummary, scrollToNodeViaDOMCoordinates } from "@/helper
// props
import { CoreEditorProps } from "@/props";
// types
import {
import type {
TDocumentEventsServer,
EditorRefApi,
IMentionHighlight,
IMentionSuggestion,
TEditorCommands,
TExtensions,
TFileHandler,
TExtensions,
} from "@/types";

export interface CustomEditorProps {
Expand Down Expand Up @@ -67,9 +68,9 @@ export const useEditor = (props: CustomEditorProps) => {
onChange,
onTransaction,
placeholder,
provider,
tabIndex,
value,
provider,
autofocus = false,
} = props;
// states
Expand Down Expand Up @@ -257,7 +258,7 @@ export const useEditor = (props: CustomEditorProps) => {
if (empty) return null;

const nodesArray: string[] = [];
state.doc.nodesBetween(from, to, (node, pos, parent) => {
state.doc.nodesBetween(from, to, (node, _pos, parent) => {
if (parent === state.doc && editorRef.current) {
const serializer = DOMSerializer.fromSchema(editorRef.current?.schema);
const dom = serializer.serializeNode(node);
Expand Down Expand Up @@ -298,6 +299,8 @@ export const useEditor = (props: CustomEditorProps) => {
if (!document) return;
Y.applyUpdate(document, value);
},
emitRealTimeUpdate: (message: TDocumentEventsServer) => provider?.sendStateless(message),
listenToRealTimeUpdate: () => provider && { on: provider.on.bind(provider), off: provider.off.bind(provider) },
}),
[editorRef, savedSelection]
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { HocuspocusProvider } from "@hocuspocus/provider";
import Collaboration from "@tiptap/extension-collaboration";
import { IndexeddbPersistence } from "y-indexeddb";
Expand Down Expand Up @@ -31,8 +31,8 @@ export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEdit
const provider = useMemo(
() =>
new HocuspocusProvider({
url: realtimeConfig.url,
name: id,
url: realtimeConfig.url,
token: JSON.stringify(user),
parameters: realtimeConfig.queryParams,
onAuthenticationFailed: () => {
Expand All @@ -48,23 +48,23 @@ export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEdit
},
onSynced: () => setHasServerSynced(true),
}),
[id, realtimeConfig, user]
[id, realtimeConfig, serverHandler, user]
);

// indexed db integration for offline support
const localProvider = useMemo(
() => (id ? new IndexeddbPersistence(id, provider.document) : undefined),
[id, provider]
);

// destroy and disconnect connection on unmount
useEffect(
() => () => {
provider.destroy();
provider.disconnect();
localProvider?.destroy();
},
[provider]
[provider, localProvider]
);
// indexed db integration for offline support
useLayoutEffect(() => {
const localProvider = new IndexeddbPersistence(id, provider.document);
return () => {
localProvider?.destroy();
};
}, [provider, id]);

const editor = useReadOnlyEditor({
disabledExtensions,
Expand Down
10 changes: 9 additions & 1 deletion packages/editor/src/core/hooks/use-read-only-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import { IMarking, scrollSummary } from "@/helpers/scroll-to-node";
// props
import { CoreReadOnlyEditorProps } from "@/props";
// types
import { EditorReadOnlyRefApi, IMentionHighlight, TExtensions, TFileHandler } from "@/types";
import type {
EditorReadOnlyRefApi,
IMentionHighlight,
TExtensions,
TDocumentEventsServer,
TFileHandler,
} from "@/types";

interface CustomReadOnlyEditorProps {
disabledExtensions: TExtensions[];
Expand Down Expand Up @@ -120,6 +126,8 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
editorRef.current?.off("update");
};
},
emitRealTimeUpdate: (message: TDocumentEventsServer) => provider?.sendStateless(message),
listenToRealTimeUpdate: () => provider && { on: provider.on.bind(provider), off: provider.off.bind(provider) },
getHeadings: () => editorRef?.current?.storage.headingList.headings,
}));

Expand Down
10 changes: 10 additions & 0 deletions packages/editor/src/core/types/document-collaborative-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DocumentCollaborativeEvents } from "@/constants/document-collaborative-events";

export type TDocumentEventKey = keyof typeof DocumentCollaborativeEvents;
export type TDocumentEventsClient = (typeof DocumentCollaborativeEvents)[TDocumentEventKey]["client"];
export type TDocumentEventsServer = (typeof DocumentCollaborativeEvents)[TDocumentEventKey]["server"];

export type TDocumentEventEmitter = {
on: (event: string, callback: (message: { payload: TDocumentEventsClient }) => void) => void;
off: (event: string, callback: (message: { payload: TDocumentEventsClient }) => void) => void;
};
4 changes: 4 additions & 0 deletions packages/editor/src/core/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
IMentionSuggestion,
TAIHandler,
TDisplayConfig,
TDocumentEventEmitter,
TDocumentEventsServer,
TEmbedConfig,
TExtensions,
TFileHandler,
Expand Down Expand Up @@ -83,6 +85,8 @@ export type EditorReadOnlyRefApi = {
};
onHeadingChange: (callback: (headings: IMarking[]) => void) => () => void;
getHeadings: () => IMarking[];
emitRealTimeUpdate: (action: TDocumentEventsServer) => void;
listenToRealTimeUpdate: () => TDocumentEventEmitter | undefined;
};

export interface EditorRefApi extends EditorReadOnlyRefApi {
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from "./image";
export * from "./mention-suggestion";
export * from "./slash-commands-suggestion";
export * from "@/plane-editor/types";
export * from "./document-collaborative-events";
3 changes: 3 additions & 0 deletions packages/editor/src/lib.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from "@/extensions/core-without-props";
export * from "@/constants/document-collaborative-events";
export * from "@/helpers/get-document-server-event";
export * from "@/types/document-collaborative-events";
1 change: 1 addition & 0 deletions packages/ui/src/icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type { ISvgIcons } from "./type";
export * from "./cycle";
export * from "./module";
export * from "./state";
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ const PageDetailsPage = observer(() => {
? () => getPageById(workspaceSlug?.toString(), projectId?.toString(), pageId.toString())
: null,
{
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: true,
revalidateOnFocus: true,
revalidateOnReconnect: true,
}
);

Expand Down
2 changes: 1 addition & 1 deletion web/core/components/pages/editor/editor-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
onConnect: handleServerConnect,
onServerError: handleServerError,
}),
[]
[handleServerConnect, handleServerError]
);

const realtimeConfig: TRealtimeConfig | undefined = useMemo(() => {
Expand Down
Loading

0 comments on commit 3c6006d

Please sign in to comment.