Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/editor/src/core/components/menus/menu-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export const ImageItem = (editor: Editor) =>
({
key: "image",
name: "Image",
isActive: () => editor?.isActive("image"),
isActive: () => editor?.isActive("image") || editor?.isActive("imageComponent"),
command: (savedSelection: Selection | null) =>
editor?.commands.setImageUpload({ event: "insert", pos: savedSelection?.from }),
icon: ImageIcon,
Expand Down
4 changes: 2 additions & 2 deletions packages/editor/src/core/extensions/drop.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Extension } from "@tiptap/core";
import { Plugin, PluginKey } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Plugin, PluginKey } from "@tiptap/pm/state";
import { EditorView } from "@tiptap/pm/view";

export const DropHandlerExtension = () =>
Extension.create({
Expand Down
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 @@ -19,6 +19,7 @@ import {
CustomQuoteExtension,
CustomTypographyExtension,
DropHandlerExtension,
HeadingListExtension,
ImageExtension,
ListKeymap,
Table,
Expand Down Expand Up @@ -166,4 +167,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 "@tiptap/pm/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 @@ -19,3 +19,4 @@ export * from "./quote";
export * from "./read-only-extensions";
export * from "./side-menu";
export * from "./slash-commands";
export * from "./headers";
2 changes: 2 additions & 0 deletions packages/editor/src/core/extensions/read-only-extensions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
TableRow,
Table,
CustomMention,
HeadingListExtension,
CustomReadOnlyImageExtension,
} from "@/extensions";
// helpers
Expand Down Expand Up @@ -108,4 +109,5 @@ export const CoreReadOnlyEditorExtensions = (mentionConfig: {
readonly: true,
}),
CharacterCount,
HeadingListExtension,
];
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fragment, Node as ProsemirrorNode, NodeType } from "prosemirror-model";
import { Fragment, Node as ProsemirrorNode, NodeType } from "@tiptap/pm/model";

export function createCell(
cellType: NodeType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NodeType, Schema } from "prosemirror-model";
import { NodeType, Schema } from "@tiptap/pm/model";

export function getTableNodeTypes(schema: Schema): { [key: string]: NodeType } {
if (schema.cached.tableNodeTypes) {
Expand Down
16 changes: 16 additions & 0 deletions packages/editor/src/core/hooks/use-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,27 @@ 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");
};
},
getHeadings: () => {
return editorRef?.current?.storage.headingList.headings;
},
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
15 changes: 15 additions & 0 deletions packages/editor/src/core/hooks/use-read-only-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ export const useReadOnlyEditor = ({
words: editorRef?.current?.storage?.characterCount?.words?.() ?? 0,
};
},
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");
};
},
getHeadings: () => {
return editorRef?.current?.storage.headingList.headings;
},
}));

if (!editor) {
Expand Down
2 changes: 2 additions & 0 deletions packages/editor/src/core/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export type EditorReadOnlyRefApi = {
paragraphs: number;
words: number;
};
onHeadingChange: (callback: (headings: IMarking[]) => void) => () => void;
getHeadings: () => IMarking[];
};

export interface EditorRefApi extends EditorReadOnlyRefApi {
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
34 changes: 16 additions & 18 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,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} />
)}
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 { EditorReadOnlyRefApi, 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 @@ -127,23 +126,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
Loading