[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
This commit is contained in:
M. Palanikannan 2024-12-02 14:26:36 +05:30 committed by GitHub
parent 8c04aa6f51
commit 3c6006d04a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 277 additions and 115 deletions

View file

@ -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;

View file

@ -118,7 +118,6 @@ export const CustomImageBlock: React.FC<CustomImageBlockProps> = (props) => {
height: `${Math.round(initialHeight)}px` satisfies Pixel,
aspectRatio: aspectRatioCalculated,
};
setSize(initialComputedSize);
updateAttributesSafely(
initialComputedSize,

View file

@ -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
@ -55,7 +52,7 @@ export const CustomImageNode = (props: CustomImageNodeProps) => {
setResolvedSrc(url as string);
};
getImageSource();
}, [imageFromFileSystem, node.attrs.src]);
}, [imgNodeSrc]);
return (
<NodeViewWrapper>

View file

@ -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") {

View file

@ -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;
};

View file

@ -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";
@ -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]
);
// indexed db integration for offline support
useLayoutEffect(() => {
const localProvider = new IndexeddbPersistence(id, provider.document);
return () => {
provider?.destroy();
localProvider?.destroy();
};
}, [provider, id]);
},
[provider, localProvider]
);
const editor = useEditor({
disabledExtensions,

View file

@ -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 {
@ -67,9 +68,9 @@ export const useEditor = (props: CustomEditorProps) => {
onChange,
onTransaction,
placeholder,
provider,
tabIndex,
value,
provider,
autofocus = false,
} = props;
// states
@ -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);
@ -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]
);

View file

@ -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";
@ -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: () => {
@ -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();
},
[provider]
);
// indexed db integration for offline support
useLayoutEffect(() => {
const localProvider = new IndexeddbPersistence(id, provider.document);
return () => {
localProvider?.destroy();
};
}, [provider, id]);
},
[provider, localProvider]
);
const editor = useReadOnlyEditor({
disabledExtensions,

View file

@ -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[];
@ -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,
}));

View file

@ -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;
};

View file

@ -8,6 +8,8 @@ import {
IMentionSuggestion,
TAIHandler,
TDisplayConfig,
TDocumentEventEmitter,
TDocumentEventsServer,
TEmbedConfig,
TExtensions,
TFileHandler,
@ -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 {

View file

@ -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";

View file

@ -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";