Skip to content
Merged
1 change: 1 addition & 0 deletions live/src/core/hocuspocus-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ export const getHocusPocusServer = async () => {
}
},
extensions,
debounce: 10000
});
};
16 changes: 16 additions & 0 deletions packages/editor/src/core/helpers/yjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as Y from "yjs";

/**
* @description apply updates to a doc and return the updated doc in base64(binary) format
* @param {Uint8Array} document
* @param {Uint8Array} updates
* @returns {string} base64(binary) form of the updated doc
*/
export const applyUpdates = (document: Uint8Array, updates: Uint8Array): Uint8Array => {
const yDoc = new Y.Doc();
Y.applyUpdate(yDoc, document);
Y.applyUpdate(yDoc, updates);

const encodedDoc = Y.encodeStateAsUpdate(yDoc);
return encodedDoc;
};
9 changes: 5 additions & 4 deletions packages/editor/src/core/hooks/use-collaborative-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
editorProps,
editorClassName,
enableHistory: false,
fileHandler,
handleEditorReady,
forwardedRef,
mentionHandler,
extensions: [
SideMenuExtension({
aiEnabled: !disabledExtensions?.includes("ai"),
Expand All @@ -88,7 +84,12 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
userDetails: user,
}),
],
fileHandler,
handleEditorReady,
forwardedRef,
mentionHandler,
placeholder,
provider,
tabIndex,
});

Expand Down
22 changes: 19 additions & 3 deletions packages/editor/src/core/hooks/use-editor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useImperativeHandle, useRef, MutableRefObject, useState, useEffect } from "react";
import { HocuspocusProvider } from "@hocuspocus/provider";
import { DOMSerializer } from "@tiptap/pm/model";
import { Selection } from "@tiptap/pm/state";
import { EditorProps } from "@tiptap/pm/view";
import { useEditor as useTiptapEditor, Editor } from "@tiptap/react";
import * as Y from "yjs";
// components
import { getEditorMenuItems } from "@/components/menus";
// extensions
Expand Down Expand Up @@ -32,6 +34,7 @@ export interface CustomEditorProps {
};
onChange?: (json: object, html: string) => void;
placeholder?: string | ((isFocused: boolean, value: string) => string);
provider?: HocuspocusProvider;
tabIndex?: number;
// undefined when prop is not passed, null if intentionally passed to stop
// swr syncing
Expand All @@ -52,6 +55,7 @@ export const useEditor = (props: CustomEditorProps) => {
mentionHandler,
onChange,
placeholder,
provider,
tabIndex,
value,
} = props;
Expand Down Expand Up @@ -186,9 +190,16 @@ export const useEditor = (props: CustomEditorProps) => {
const markdownOutput = editorRef.current?.storage.markdown.getMarkdown();
return markdownOutput;
},
getHTML: (): string => {
const htmlOutput = editorRef.current?.getHTML() ?? "<p></p>";
return htmlOutput;
getDocument: () => {
const documentBinary = provider?.document ? Y.encodeStateAsUpdate(provider?.document) : null;
const documentHTML = editorRef.current?.getHTML() ?? "<p></p>";
const documentJSON = editorRef.current?.getJSON() ?? null;

return {
binary: documentBinary,
html: documentHTML,
json: documentJSON,
};
},
scrollSummary: (marking: IMarking): void => {
if (!editorRef.current) return;
Expand Down Expand Up @@ -259,6 +270,11 @@ export const useEditor = (props: CustomEditorProps) => {
words: editorRef?.current?.storage?.characterCount?.words?.() ?? 0,
};
},
setProviderDocument: (value) => {
const document = provider?.document;
if (!document) return;
Y.applyUpdate(document, value);
},
}),
[editorRef, savedSelection]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEdit
const editor = useReadOnlyEditor({
editorProps,
editorClassName,
forwardedRef,
handleEditorReady,
mentionHandler,
extensions: [
...(extensions ?? []),
Collaboration.configure({
document: provider.document,
}),
],
forwardedRef,
handleEditorReady,
mentionHandler,
provider,
});

return { editor, isIndexedDbSynced: true };
Expand Down
37 changes: 25 additions & 12 deletions packages/editor/src/core/hooks/use-read-only-editor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useImperativeHandle, useRef, MutableRefObject, useEffect } from "react";
import { HocuspocusProvider } from "@hocuspocus/provider";
import { EditorProps } from "@tiptap/pm/view";
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
import * as Y from "yjs";
// extensions
import { CoreReadOnlyEditorExtensions } from "@/extensions";
// helpers
Expand All @@ -21,17 +23,21 @@ interface CustomReadOnlyEditorProps {
mentionHandler: {
highlights: () => Promise<IMentionHighlight[]>;
};
provider?: HocuspocusProvider;
}

export const useReadOnlyEditor = ({
initialValue,
editorClassName,
forwardedRef,
extensions = [],
editorProps = {},
handleEditorReady,
mentionHandler,
}: CustomReadOnlyEditorProps) => {
export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
const {
initialValue,
editorClassName,
forwardedRef,
extensions = [],
editorProps = {},
handleEditorReady,
mentionHandler,
provider,
} = props;

const editor = useCustomEditor({
editable: false,
content: typeof initialValue === "string" && initialValue.trim() !== "" ? initialValue : "<p></p>",
Expand Down Expand Up @@ -74,9 +80,16 @@ export const useReadOnlyEditor = ({
const markdownOutput = editorRef.current?.storage.markdown.getMarkdown();
return markdownOutput;
},
getHTML: (): string => {
const htmlOutput = editorRef.current?.getHTML() ?? "<p></p>";
return htmlOutput;
getDocument: () => {
const documentBinary = provider?.document ? Y.encodeStateAsUpdate(provider?.document) : null;
const documentHTML = editorRef.current?.getHTML() ?? "<p></p>";
const documentJSON = editorRef.current?.getJSON() ?? null;

return {
binary: documentBinary,
html: documentHTML,
json: documentJSON,
};
},
scrollSummary: (marking: IMarking): void => {
if (!editorRef.current) return;
Expand Down
8 changes: 7 additions & 1 deletion packages/editor/src/core/types/editor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { JSONContent } from "@tiptap/core";
// helpers
import { IMarking } from "@/helpers/scroll-to-node";
// types
Expand All @@ -16,7 +17,11 @@ import {
// editor refs
export type EditorReadOnlyRefApi = {
getMarkDown: () => string;
getHTML: () => string;
getDocument: () => {
binary: Uint8Array | null;
html: string;
json: JSONContent | null;
};
clearEditor: (emitUpdate?: boolean) => void;
setEditorValue: (content: string) => void;
scrollSummary: (marking: IMarking) => void;
Expand All @@ -38,6 +43,7 @@ export interface EditorRefApi extends EditorReadOnlyRefApi {
isEditorReadyToDiscard: () => boolean;
getSelectedText: () => string | null;
insertText: (contentHTML: string, insertOnNextLine?: boolean) => void;
setProviderDocument: (value: Uint8Array) => void;
}

// editor props
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export { isCellSelection } from "@/extensions/table/table/utilities/is-cell-sele
// helpers
export * from "@/helpers/common";
export * from "@/helpers/editor-commands";
export * from "@/helpers/yjs";
export * from "@/extensions/table/table";

// components
Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/pages.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@ export type TPageVersion = {
updated_at: string;
updated_by: string;
workspace: string;
}

export type TDocumentPayload = {
description_binary: string;
description_html: string;
description: object;
}
1 change: 0 additions & 1 deletion web/core/components/pages/editor/editor-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
},
}}
realtimeConfig={realtimeConfig}
serverHandler={serverHandler}
user={{
id: currentUser?.id ?? "",
name: currentUser?.display_name ?? "",
Expand Down
15 changes: 1 addition & 14 deletions web/core/components/pages/editor/header/extra-options.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { observer } from "mobx-react";
import { CircleAlert } from "lucide-react";
// editor
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
// ui
Expand All @@ -19,13 +18,12 @@ import { IPage } from "@/store/pages/page";
type Props = {
editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void;
hasConnectionFailed: boolean;
page: IPage;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
};

export const PageExtraOptions: React.FC<Props> = observer((props) => {
const { editorRef, handleDuplicatePage, hasConnectionFailed, page, readOnlyEditorRef } = props;
const { editorRef, handleDuplicatePage, page, readOnlyEditorRef } = props;
// derived values
const {
archived_at,
Expand Down Expand Up @@ -79,17 +77,6 @@ export const PageExtraOptions: React.FC<Props> = observer((props) => {
</div>
</Tooltip>
)}
{hasConnectionFailed && isOnline && (
<Tooltip
tooltipHeading="Connection failed"
tooltipContent="All changes made will be saved locally and will be synced when the connection is re-established."
>
<div className="flex-shrink-0 flex h-7 items-center gap-2 rounded-full bg-red-500/20 px-3 py-0.5 text-xs font-medium text-red-500">
<CircleAlert className="flex-shrink-0 size-3" />
<span>Server error</span>
</div>
</Tooltip>
)}
{canCurrentUserFavoritePage && (
<FavoriteStar
selected={is_favorite}
Expand Down
3 changes: 0 additions & 3 deletions web/core/components/pages/editor/header/mobile-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ type Props = {
editorReady: boolean;
editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void;
hasConnectionFailed: boolean;
page: IPage;
readOnlyEditorReady: boolean;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
Expand All @@ -25,7 +24,6 @@ export const PageEditorMobileHeaderRoot: React.FC<Props> = observer((props) => {
editorReady,
editorRef,
handleDuplicatePage,
hasConnectionFailed,
page,
readOnlyEditorReady,
readOnlyEditorRef,
Expand Down Expand Up @@ -53,7 +51,6 @@ export const PageEditorMobileHeaderRoot: React.FC<Props> = observer((props) => {
<PageExtraOptions
editorRef={editorRef}
handleDuplicatePage={handleDuplicatePage}
hasConnectionFailed={hasConnectionFailed}
page={page}
readOnlyEditorRef={readOnlyEditorRef}
/>
Expand Down
4 changes: 0 additions & 4 deletions web/core/components/pages/editor/header/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ type Props = {
editorReady: boolean;
editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void;
hasConnectionFailed: boolean;
page: IPage;
readOnlyEditorReady: boolean;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
Expand All @@ -27,7 +26,6 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
editorReady,
editorRef,
handleDuplicatePage,
hasConnectionFailed,
page,
readOnlyEditorReady,
readOnlyEditorRef,
Expand Down Expand Up @@ -67,7 +65,6 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
<PageExtraOptions
editorRef={editorRef}
handleDuplicatePage={handleDuplicatePage}
hasConnectionFailed={hasConnectionFailed}
page={page}
readOnlyEditorRef={readOnlyEditorRef}
/>
Expand All @@ -79,7 +76,6 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
editorReady={editorReady}
readOnlyEditorReady={readOnlyEditorReady}
handleDuplicatePage={handleDuplicatePage}
hasConnectionFailed={hasConnectionFailed}
page={page}
sidePeekVisible={sidePeekVisible}
setSidePeekVisible={setSidePeekVisible}
Expand Down
Loading