Skip to content

Commit

Permalink
fix: heading not updating with realtime
Browse files Browse the repository at this point in the history
  • Loading branch information
Palanikannan1437 committed Sep 9, 2024
1 parent f71ca58 commit e3a8eab
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 56 deletions.
2 changes: 2 additions & 0 deletions packages/editor/src/core/extensions/extensions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
CustomQuoteExtension,
CustomTypographyExtension,
DropHandlerExtension,
HeadingListExtension,
ImageExtension,
ListKeymap,
Table,
Expand Down Expand Up @@ -159,4 +160,5 @@ export const CoreEditorExtensions = ({
includeChildren: true,
}),
CharacterCount,
HeadingListExtension,
];
57 changes: 57 additions & 0 deletions packages/editor/src/core/extensions/headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Extension } from "@tiptap/core";
import { Plugin, PluginKey } from "prosemirror-state";

export interface IMarking {
type: "heading";
level: number;
text: string;
sequence: number;
}

export const HeadingListExtension = Extension.create({
name: "headingList",

addStorage() {
return {
headings: [] as IMarking[],
};
},

addProseMirrorPlugins() {
const plugin = new Plugin({
key: new PluginKey("heading-list"),
appendTransaction: (_, __, newState) => {
const headings: IMarking[] = [];
let h1Sequence = 0;
let h2Sequence = 0;
let h3Sequence = 0;

newState.doc.descendants((node) => {
if (node.type.name === "heading") {
const level = node.attrs.level;
const text = node.textContent;

headings.push({
type: "heading",
level: level,
text: text,
sequence: level === 1 ? ++h1Sequence : level === 2 ? ++h2Sequence : ++h3Sequence,
});
}
});

this.storage.headings = headings;

this.editor.emit("update", { editor: this.editor, transaction: newState.tr });

return null;
},
});

return [plugin];
},

getHeadings() {
return this.storage.headings;
},
});
1 change: 1 addition & 0 deletions packages/editor/src/core/extensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export * from "./quote";
export * from "./read-only-extensions";
export * from "./side-menu";
export * from "./slash-commands";
export * from "./headers";
16 changes: 14 additions & 2 deletions packages/editor/src/core/hooks/use-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,24 @@ export const useEditor = (props: CustomEditorProps) => {
const item = getEditorMenuItem(itemName);
return item ? item.isActive() : false;
},
onHeadingChange: (callback: (headings: IMarking[]) => void) => {
// Subscribe to update event emitted from headers extension
editorRef.current?.on("update", () => {
callback(editorRef.current?.storage.headingList.headings);
});
// Return a function to unsubscribe to the continuous transactions of
// the editor on unmounting the component that has subscribed to this
// method
return () => {
editorRef.current?.off("update");
};
},
onStateChange: (callback: () => void) => {
// Subscribe to editor state changes
editorRef.current?.on("transaction", () => {
callback();
});

// Return a function to unsubscribe to the continuous transactions of
// the editor on unmounting the component that has subscribed to this
// method
Expand Down Expand Up @@ -214,7 +227,6 @@ export const useEditor = (props: CustomEditorProps) => {
}
});
const selection = nodesArray.join("");
console.log(selection);
return selection;
},
insertText: (contentHTML, insertOnNextLine) => {
Expand All @@ -236,7 +248,7 @@ export const useEditor = (props: CustomEditorProps) => {
words: editorRef.current?.storage?.characterCount?.words?.() ?? 0,
},
}),
[editorRef, savedSelection, fileHandler.upload]
[editorRef, savedSelection, fileHandler.upload, editor]
);

if (!editor) {
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/core/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface EditorRefApi extends EditorReadOnlyRefApi {
executeMenuItemCommand: (itemKey: TEditorCommands) => void;
isMenuItemActive: (itemKey: TEditorCommands) => boolean;
onStateChange: (callback: () => void) => () => void;
onHeadingChange: (callback: (headings: IMarking[]) => void) => () => void;
setFocusAtPosition: (position: number) => void;
isEditorReadyToDiscard: () => boolean;
getSelectedText: () => string | null;
Expand Down
20 changes: 5 additions & 15 deletions web/core/components/pages/editor/editor-body.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo } from "react";
import { useCallback, useMemo } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// document-editor
Expand Down Expand Up @@ -38,14 +38,13 @@ const fileService = new FileService();

type Props = {
editorRef: React.RefObject<EditorRefApi>;
editorReady: boolean;
handleConnectionStatus: (status: boolean) => void;
handleEditorReady: (value: boolean) => void;
handleReadOnlyEditorReady: (value: boolean) => void;
markings: IMarking[];
page: IPage;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
sidePeekVisible: boolean;
updateMarkings: (description_html: string) => void;
};

export const PageEditorBody: React.FC<Props> = observer((props) => {
Expand All @@ -54,11 +53,9 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
handleConnectionStatus,
handleEditorReady,
handleReadOnlyEditorReady,
markings,
page,
readOnlyEditorRef,
sidePeekVisible,
updateMarkings,
} = props;
// router
const { workspaceSlug, projectId } = useParams();
Expand All @@ -70,10 +67,9 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
project: { getProjectMemberIds },
} = useMember();
// derived values
const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "") : "";
const workspaceId = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "" : "";
const pageId = page?.id;
const pageTitle = page?.name ?? "";
const pageDescription = page?.description_html;
const { isContentEditable, updateTitle, setIsSubmitting } = page;
const projectMemberIds = projectId ? getProjectMemberIds(projectId.toString()) : [];
const projectMemberDetails = projectMemberIds?.map((id) => getUserDetails(id) as IUserLite);
Expand Down Expand Up @@ -104,6 +100,7 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
const handleServerConnect = useCallback(() => {
handleConnectionStatus(false);
}, []);

const handleServerError = useCallback(() => {
handleConnectionStatus(true);
}, []);
Expand All @@ -116,10 +113,6 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
[]
);

useEffect(() => {
updateMarkings(pageDescription ?? "<p></p>");
}, [pageDescription, updateMarkings]);

const realtimeConfig: TRealtimeConfig = useMemo(
() => ({
url: `${LIVE_URL}/collaboration`,
Expand All @@ -144,10 +137,7 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
})}
>
{!isFullWidth && (
<PageContentBrowser
editorRef={(isContentEditable ? editorRef : readOnlyEditorRef)?.current}
markings={markings}
/>
<PageContentBrowser editorRef={(isContentEditable ? editorRef : readOnlyEditorRef)?.current} />
)}
</Row>
<div
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 @@ -13,7 +13,6 @@ type Props = {
editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void;
hasConnectionFailed: boolean;
markings: IMarking[];
page: IPage;
readOnlyEditorReady: boolean;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
Expand All @@ -27,7 +26,6 @@ export const PageEditorMobileHeaderRoot: React.FC<Props> = observer((props) => {
editorRef,
handleDuplicatePage,
hasConnectionFailed,
markings,
page,
readOnlyEditorReady,
readOnlyEditorRef,
Expand All @@ -48,7 +46,6 @@ export const PageEditorMobileHeaderRoot: React.FC<Props> = observer((props) => {
<PageSummaryPopover
editorRef={isContentEditable ? editorRef.current : readOnlyEditorRef.current}
isFullWidth={isFullWidth}
markings={markings}
sidePeekVisible={sidePeekVisible}
setSidePeekVisible={setSidePeekVisible}
/>
Expand Down
36 changes: 17 additions & 19 deletions web/core/components/pages/editor/header/root.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { observer } from "mobx-react";
import { EditorReadOnlyRefApi, EditorRefApi, IMarking } from "@plane/editor";
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
// components
import { Header, EHeaderVariant } from "@plane/ui";
import { PageEditorMobileHeaderRoot, PageExtraOptions, PageSummaryPopover, PageToolbar } from "@/components/pages";
Expand All @@ -15,7 +15,6 @@ type Props = {
editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void;
hasConnectionFailed: boolean;
markings: IMarking[];
page: IPage;
readOnlyEditorReady: boolean;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
Expand All @@ -29,7 +28,6 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
editorRef,
handleDuplicatePage,
hasConnectionFailed,
markings,
page,
readOnlyEditorReady,
readOnlyEditorRef,
Expand All @@ -47,22 +45,23 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
<>
<Header variant={EHeaderVariant.SECONDARY} showOnMobile={false}>
<Header.LeftItem className="gap-0 w-full">
<div
className={cn("flex-shrink-0 my-auto", {
"w-40 lg:w-56": !isFullWidth,
"w-[5%]": isFullWidth,
})}
>
<PageSummaryPopover
editorRef={isContentEditable ? editorRef.current : readOnlyEditorRef.current}
isFullWidth={isFullWidth}
markings={markings}
sidePeekVisible={sidePeekVisible}
setSidePeekVisible={setSidePeekVisible}
/>
</div>
{(editorReady || readOnlyEditorReady) && isContentEditable && editorRef.current && (
<PageToolbar editorRef={editorRef?.current} />
<>
<div
className={cn("flex-shrink-0 my-auto", {
"w-40 lg:w-56": !isFullWidth,
"w-[5%]": isFullWidth,
})}
>
<PageSummaryPopover
editorRef={isContentEditable ? editorRef.current : readOnlyEditorRef.current}
isFullWidth={isFullWidth}
sidePeekVisible={sidePeekVisible}
setSidePeekVisible={setSidePeekVisible}
/>
</div>
<PageToolbar editorRef={editorRef?.current} />
</>
)}
</Header.LeftItem>
<PageExtraOptions
Expand All @@ -79,7 +78,6 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
readOnlyEditorRef={readOnlyEditorRef}
editorReady={editorReady}
readOnlyEditorReady={readOnlyEditorReady}
markings={markings}
handleDuplicatePage={handleDuplicatePage}
hasConnectionFailed={hasConnectionFailed}
page={page}
Expand Down
9 changes: 3 additions & 6 deletions web/core/components/pages/editor/page-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useSearchParams } from "next/navigation";
// editor
import { EditorRefApi, useEditorMarkings } from "@plane/editor";
import { EditorRefApi } from "@plane/editor";
// types
import { TPage } from "@plane/types";
// ui
Expand Down Expand Up @@ -44,8 +44,7 @@ export const PageRoot = observer((props: TPageRootProps) => {
const { createPage } = useProjectPages();
// derived values
const { access, description_html, name, isContentEditable } = page;
// editor markings hook
const { markings, updateMarkings } = useEditorMarkings();

// update query params
const { updateQueryParams } = useQueryParams();

Expand Down Expand Up @@ -123,23 +122,21 @@ export const PageRoot = observer((props: TPageRootProps) => {
editorRef={editorRef}
handleDuplicatePage={handleDuplicatePage}
hasConnectionFailed={hasConnectionFailed}
markings={markings}
page={page}
readOnlyEditorReady={readOnlyEditorReady}
readOnlyEditorRef={readOnlyEditorRef}
setSidePeekVisible={(state) => setSidePeekVisible(state)}
sidePeekVisible={sidePeekVisible}
/>
<PageEditorBody
editorReady={editorReady}
editorRef={editorRef}
handleConnectionStatus={(status) => setHasConnectionFailed(status)}
handleEditorReady={(val) => setEditorReady(val)}
handleReadOnlyEditorReady={() => setReadOnlyEditorReady(true)}
markings={markings}
page={page}
readOnlyEditorRef={readOnlyEditorRef}
sidePeekVisible={sidePeekVisible}
updateMarkings={updateMarkings}
/>
</>
);
Expand Down
18 changes: 13 additions & 5 deletions web/core/components/pages/editor/summary/content-browser.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
// types
import { useState, useEffect } from "react";
import { EditorReadOnlyRefApi, EditorRefApi, IMarking } from "@plane/editor";
import { OutlineHeading1, OutlineHeading2, OutlineHeading3 } from "./heading-components";

type Props = {
editorRef: EditorRefApi | EditorReadOnlyRefApi | null;
markings: IMarking[];
editorRef: EditorRefApi & EditorReadOnlyRefApi;
setSidePeekVisible?: (sidePeekState: boolean) => void;
};

export const PageContentBrowser: React.FC<Props> = (props) => {
const { editorRef, markings, setSidePeekVisible } = props;
const { editorRef, setSidePeekVisible } = props;
const [headings, setHeadings] = useState<IMarking[]>([]);

useEffect(() => {
const unsubscribe = editorRef?.onHeadingChange(setHeadings);
return () => {
if (unsubscribe) unsubscribe();
};
}, [editorRef]);

const handleOnClick = (marking: IMarking) => {
editorRef?.scrollSummary(marking);
Expand All @@ -27,8 +35,8 @@ export const PageContentBrowser: React.FC<Props> = (props) => {
return (
<div className="h-full flex flex-col overflow-hidden">
<div className="h-full flex flex-col items-start gap-y-2 overflow-y-auto mt-2">
{markings.length !== 0 ? (
markings.map((marking) => {
{headings && headings.length !== 0 ? (
headings.map((marking) => {
const Component = HeadingComponent[marking.level];
if (!Component) return null;
return (
Expand Down
Loading

0 comments on commit e3a8eab

Please sign in to comment.