[WEB-1116] fix: page outline not reflecting changes in realtime (#5567)
* fix: svg not supported in image uploads * fix: svg image file error message fixed * fix: heading not updating with realtime * chore: add read-only editor support * fix: headings show on initial render * fix: types and imports --------- Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
This commit is contained in:
parent
b6e813cb9a
commit
a05876552c
17 changed files with 145 additions and 57 deletions
|
|
@ -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
|
||||
|
|
@ -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) => {
|
||||
|
|
@ -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();
|
||||
|
|
@ -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);
|
||||
|
|
@ -104,6 +100,7 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
|
|||
const handleServerConnect = useCallback(() => {
|
||||
handleConnectionStatus(false);
|
||||
}, []);
|
||||
|
||||
const handleServerError = useCallback(() => {
|
||||
handleConnectionStatus(true);
|
||||
}, []);
|
||||
|
|
@ -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`,
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ type Props = {
|
|||
editorRef: React.RefObject<EditorRefApi>;
|
||||
handleDuplicatePage: () => void;
|
||||
hasConnectionFailed: boolean;
|
||||
markings: IMarking[];
|
||||
page: IPage;
|
||||
readOnlyEditorReady: boolean;
|
||||
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
|
||||
|
|
@ -27,7 +26,6 @@ export const PageEditorMobileHeaderRoot: React.FC<Props> = observer((props) => {
|
|||
editorRef,
|
||||
handleDuplicatePage,
|
||||
hasConnectionFailed,
|
||||
markings,
|
||||
page,
|
||||
readOnlyEditorReady,
|
||||
readOnlyEditorRef,
|
||||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
@ -15,7 +15,6 @@ type Props = {
|
|||
editorRef: React.RefObject<EditorRefApi>;
|
||||
handleDuplicatePage: () => void;
|
||||
hasConnectionFailed: boolean;
|
||||
markings: IMarking[];
|
||||
page: IPage;
|
||||
readOnlyEditorReady: boolean;
|
||||
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
|
||||
|
|
@ -29,7 +28,6 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
|
|||
editorRef,
|
||||
handleDuplicatePage,
|
||||
hasConnectionFailed,
|
||||
markings,
|
||||
page,
|
||||
readOnlyEditorReady,
|
||||
readOnlyEditorRef,
|
||||
|
|
@ -47,20 +45,21 @@ 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) && (
|
||||
<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>
|
||||
)}
|
||||
{(editorReady || readOnlyEditorReady) && isContentEditable && editorRef.current && (
|
||||
<PageToolbar editorRef={editorRef?.current} />
|
||||
)}
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
// editor
|
||||
import { EditorReadOnlyRefApi, EditorRefApi, useEditorMarkings } from "@plane/editor";
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
// types
|
||||
import { TPage } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
@ -127,7 +126,6 @@ export const PageRoot = observer((props: TPageRootProps) => {
|
|||
editorRef={editorRef}
|
||||
handleDuplicatePage={handleDuplicatePage}
|
||||
hasConnectionFailed={hasConnectionFailed}
|
||||
markings={markings}
|
||||
page={page}
|
||||
readOnlyEditorReady={readOnlyEditorReady}
|
||||
readOnlyEditorRef={readOnlyEditorRef}
|
||||
|
|
@ -135,15 +133,14 @@ export const PageRoot = observer((props: TPageRootProps) => {
|
|||
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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,27 @@
|
|||
// types
|
||||
import { useState, useEffect } from "react";
|
||||
// plane editor
|
||||
import { EditorReadOnlyRefApi, EditorRefApi, IMarking } from "@plane/editor";
|
||||
// components
|
||||
import { OutlineHeading1, OutlineHeading2, OutlineHeading3 } from "./heading-components";
|
||||
|
||||
type Props = {
|
||||
editorRef: EditorRefApi | EditorReadOnlyRefApi | null;
|
||||
markings: IMarking[];
|
||||
setSidePeekVisible?: (sidePeekState: boolean) => void;
|
||||
};
|
||||
|
||||
export const PageContentBrowser: React.FC<Props> = (props) => {
|
||||
const { editorRef, markings, setSidePeekVisible } = props;
|
||||
const { editorRef, setSidePeekVisible } = props;
|
||||
// states
|
||||
const [headings, setHeadings] = useState<IMarking[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = editorRef?.onHeadingChange(setHeadings);
|
||||
// for initial render of this component to get the editor headings
|
||||
setHeadings(editorRef?.getHeadings() ?? []);
|
||||
return () => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
};
|
||||
}, [editorRef]);
|
||||
|
||||
const handleOnClick = (marking: IMarking) => {
|
||||
editorRef?.scrollSummary(marking);
|
||||
|
|
@ -27,8 +39,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 (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useState } from "react";
|
|||
import { usePopper } from "react-popper";
|
||||
import { List } from "lucide-react";
|
||||
// document editor
|
||||
import { EditorReadOnlyRefApi, EditorRefApi, IMarking } from "@plane/editor";
|
||||
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// components
|
||||
|
|
@ -11,13 +11,12 @@ import { PageContentBrowser } from "./content-browser";
|
|||
type Props = {
|
||||
editorRef: EditorRefApi | EditorReadOnlyRefApi | null;
|
||||
isFullWidth: boolean;
|
||||
markings: IMarking[];
|
||||
sidePeekVisible: boolean;
|
||||
setSidePeekVisible: (sidePeekState: boolean) => void;
|
||||
};
|
||||
|
||||
export const PageSummaryPopover: React.FC<Props> = (props) => {
|
||||
const { editorRef, markings, sidePeekVisible, setSidePeekVisible } = props;
|
||||
const { editorRef, sidePeekVisible, setSidePeekVisible } = props;
|
||||
// refs
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
|
@ -54,7 +53,7 @@ export const PageSummaryPopover: React.FC<Props> = (props) => {
|
|||
style={summaryPopoverStyles.popper}
|
||||
{...summaryPopoverAttributes.popper}
|
||||
>
|
||||
<PageContentBrowser setSidePeekVisible={setSidePeekVisible} editorRef={editorRef} markings={markings} />
|
||||
<PageContentBrowser setSidePeekVisible={setSidePeekVisible} editorRef={editorRef} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -66,7 +65,7 @@ export const PageSummaryPopover: React.FC<Props> = (props) => {
|
|||
style={summaryPopoverStyles.popper}
|
||||
{...summaryPopoverAttributes.popper}
|
||||
>
|
||||
<PageContentBrowser editorRef={editorRef} markings={markings} />
|
||||
<PageContentBrowser editorRef={editorRef} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue