diff --git a/apps/space/core/components/editor/lite-text-editor.tsx b/apps/space/core/components/editor/lite-text-editor.tsx
index 3223135c99c..a9ff57a8249 100644
--- a/apps/space/core/components/editor/lite-text-editor.tsx
+++ b/apps/space/core/components/editor/lite-text-editor.tsx
@@ -2,17 +2,16 @@ import React from "react";
// plane imports
import { type EditorRefApi, type ILiteTextEditorProps, LiteTextEditorWithRef, type TFileHandler } from "@plane/editor";
import type { MakeOptional } from "@plane/types";
-import { cn } from "@plane/utils";
+import { cn, isCommentEmpty } from "@plane/utils";
// helpers
import { getEditorFileHandlers } from "@/helpers/editor.helper";
-import { isCommentEmpty } from "@/helpers/string.helper";
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
// local imports
import { EditorMentionsRoot } from "./embeds/mentions";
import { IssueCommentToolbar } from "./toolbar";
type LiteTextEditorWrapperProps = MakeOptional<
- Omit,
+ Omit,
"disabledExtensions" | "flaggedExtensions"
> & {
anchor: string;
@@ -63,6 +62,7 @@ export const LiteTextEditor = React.forwardRef ,
}}
+ extendedEditorProps={{}}
{...rest}
// overriding the containerClassName to add relative class passed
containerClassName={cn(containerClassName, "relative")}
diff --git a/apps/space/core/components/editor/rich-text-editor.tsx b/apps/space/core/components/editor/rich-text-editor.tsx
index 8d0818353dc..ccd474a3050 100644
--- a/apps/space/core/components/editor/rich-text-editor.tsx
+++ b/apps/space/core/components/editor/rich-text-editor.tsx
@@ -12,7 +12,7 @@ import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
import { EditorMentionsRoot } from "./embeds/mentions";
type RichTextEditorWrapperProps = MakeOptional<
- Omit,
+ Omit,
"disabledExtensions" | "flaggedExtensions"
> & {
anchor: string;
@@ -56,6 +56,7 @@ export const RichTextEditor = forwardRef
"
- * @param {string | undefined} comment
- * @returns {boolean}
- */
-export const isCommentEmpty = (comment: string | undefined): boolean => {
- // return true if comment is undefined
- if (!comment) return true;
- return (
- comment?.trim() === "" ||
- comment === "" ||
- isEmptyHtmlString(comment ?? "", ["img", "mention-component", "image-component", "embed-component"])
- );
-};
-
export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " ");
export const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx
index 94cdfd27966..1ae3317b95c 100644
--- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx
+++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useCallback, useMemo } from "react";
+import { useCallback, useEffect, useMemo } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useParams } from "next/navigation";
@@ -20,6 +20,7 @@ import { PageRoot, TPageRootConfig, TPageRootHandlers } from "@/components/pages
import { useEditorConfig } from "@/hooks/editor";
import { useEditorAsset } from "@/hooks/store/use-editor-asset";
import { useWorkspace } from "@/hooks/store/use-workspace";
+import { useAppRouter } from "@/hooks/use-app-router";
// plane web hooks
import { EPageStoreType, usePage, usePageStore } from "@/plane-web/hooks/store";
// plane web services
@@ -30,13 +31,17 @@ const workspaceService = new WorkspaceService();
const projectPageService = new ProjectPageService();
const projectPageVersionService = new ProjectPageVersionService();
+const storeType = EPageStoreType.PROJECT;
+
const PageDetailsPage = observer(() => {
+ // router
+ const router = useAppRouter();
const { workspaceSlug, projectId, pageId } = useParams();
// store hooks
- const { createPage, fetchPageDetails } = usePageStore(EPageStoreType.PROJECT);
+ const { createPage, fetchPageDetails } = usePageStore(storeType);
const page = usePage({
pageId: pageId?.toString() ?? "",
- storeType: EPageStoreType.PROJECT,
+ storeType,
});
const { getWorkspaceBySlug } = useWorkspace();
const { uploadEditorAsset } = useEditorAsset();
@@ -88,10 +93,25 @@ const PageDetailsPage = observer(() => {
versionId
);
},
- getRedirectionLink: (pageId) => `/${workspaceSlug}/projects/${projectId}/pages/${pageId}`,
+ restoreVersion: async (pageId, versionId) => {
+ if (!workspaceSlug || !projectId) return;
+ await projectPageVersionService.restoreVersion(
+ workspaceSlug.toString(),
+ projectId.toString(),
+ pageId,
+ versionId
+ );
+ },
+ getRedirectionLink: (pageId) => {
+ if (pageId) {
+ return `/${workspaceSlug}/projects/${projectId}/pages/${pageId}`;
+ } else {
+ return `/${workspaceSlug}/projects/${projectId}/pages`;
+ }
+ },
updateDescription: updateDescription ?? (async () => {}),
}),
- [createPage, fetchEntityCallback, id, projectId, updateDescription, workspaceSlug]
+ [createPage, fetchEntityCallback, id, updateDescription, workspaceSlug, projectId]
);
// page root config
const pageRootConfig: TPageRootConfig = useMemo(
@@ -115,7 +135,7 @@ const PageDetailsPage = observer(() => {
workspaceSlug: workspaceSlug?.toString() ?? "",
}),
}),
- [getEditorFileHandlers, id, projectId, uploadEditorAsset, workspaceId, workspaceSlug]
+ [getEditorFileHandlers, id, uploadEditorAsset, projectId, workspaceId, workspaceSlug]
);
const webhookConnectionParams: TWebhookConnectionQueryParams = useMemo(
@@ -127,6 +147,12 @@ const PageDetailsPage = observer(() => {
[projectId, workspaceSlug]
);
+ useEffect(() => {
+ if (page?.deleted_at && page?.id) {
+ router.push(pageRootHandlers.getRedirectionLink());
+ }
+ }, [page?.deleted_at, page?.id, router, pageRootHandlers]);
+
if ((!page || !id) && !pageDetailsError)
return (
@@ -150,7 +176,7 @@ const PageDetailsPage = observer(() => {
);
- if (!page) return null;
+ if (!page || !workspaceSlug || !projectId) return null;
return (
<>
@@ -160,9 +186,11 @@ const PageDetailsPage = observer(() => {
diff --git a/apps/web/ce/components/pages/modals/index.ts b/apps/web/ce/components/pages/modals/index.ts
index da78df1c843..c1c5c24d226 100644
--- a/apps/web/ce/components/pages/modals/index.ts
+++ b/apps/web/ce/components/pages/modals/index.ts
@@ -1 +1,2 @@
export * from "./move-page-modal";
+export * from "./modals";
diff --git a/apps/web/ce/components/pages/modals/modals.tsx b/apps/web/ce/components/pages/modals/modals.tsx
new file mode 100644
index 00000000000..780dc853136
--- /dev/null
+++ b/apps/web/ce/components/pages/modals/modals.tsx
@@ -0,0 +1,15 @@
+"use client";
+
+import React from "react";
+import { observer } from "mobx-react";
+// components
+import { EPageStoreType } from "@/plane-web/hooks/store";
+// store
+import { TPageInstance } from "@/store/pages/base-page";
+
+export type TPageModalsProps = {
+ page: TPageInstance;
+ storeType: EPageStoreType;
+};
+
+export const PageModals: React.FC = observer((props) => null);
diff --git a/apps/web/ce/hooks/pages/index.ts b/apps/web/ce/hooks/pages/index.ts
new file mode 100644
index 00000000000..e67eaa79757
--- /dev/null
+++ b/apps/web/ce/hooks/pages/index.ts
@@ -0,0 +1,2 @@
+export * from "./use-pages-pane-extensions";
+export * from "./use-extended-editor-extensions";
diff --git a/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts b/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts
new file mode 100644
index 00000000000..028e16c8895
--- /dev/null
+++ b/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts
@@ -0,0 +1,20 @@
+import type { IEditorPropsExtended } from "@plane/editor";
+import type { TSearchEntityRequestPayload, TSearchResponse } from "@plane/types";
+import type { TPageInstance } from "@/store/pages/base-page";
+import { EPageStoreType } from "../store";
+
+export type TExtendedEditorExtensionsHookParams = {
+ workspaceSlug: string;
+ page: TPageInstance;
+ storeType: EPageStoreType;
+ fetchEntity: (payload: TSearchEntityRequestPayload) => Promise;
+ getRedirectionLink: (pageId?: string) => string;
+ extensionHandlers?: Map;
+ projectId?: string;
+};
+
+export type TExtendedEditorExtensionsConfig = IEditorPropsExtended;
+
+export const useExtendedEditorProps = (
+ _params: TExtendedEditorExtensionsHookParams
+): TExtendedEditorExtensionsConfig => ({});
diff --git a/apps/web/ce/hooks/pages/use-pages-pane-extensions.ts b/apps/web/ce/hooks/pages/use-pages-pane-extensions.ts
new file mode 100644
index 00000000000..cd405c1ca4e
--- /dev/null
+++ b/apps/web/ce/hooks/pages/use-pages-pane-extensions.ts
@@ -0,0 +1,52 @@
+import { useCallback, useMemo, type RefObject } from "react";
+import { useSearchParams } from "next/navigation";
+import type { EditorRefApi } from "@plane/editor";
+import {
+ PAGE_NAVIGATION_PANE_TAB_KEYS,
+ PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM,
+} from "@/components/pages/navigation-pane";
+import { useAppRouter } from "@/hooks/use-app-router";
+import { useQueryParams } from "@/hooks/use-query-params";
+import type { TPageNavigationPaneTab } from "@/plane-web/components/pages/navigation-pane";
+import { INavigationPaneExtension } from "@/plane-web/types/pages/pane-extensions";
+import type { TPageInstance } from "@/store/pages/base-page";
+
+export type TPageExtensionHookParams = {
+ page: TPageInstance;
+ editorRef: RefObject;
+};
+
+export const usePagesPaneExtensions = (_params: TPageExtensionHookParams) => {
+ const router = useAppRouter();
+ const { updateQueryParams } = useQueryParams();
+ const searchParams = useSearchParams();
+
+ // Generic navigation pane logic - hook manages feature-specific routing
+ const navigationPaneQueryParam = searchParams.get(
+ PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM
+ ) as TPageNavigationPaneTab | null;
+
+ const isNavigationPaneOpen =
+ !!navigationPaneQueryParam && PAGE_NAVIGATION_PANE_TAB_KEYS.includes(navigationPaneQueryParam);
+
+ const handleOpenNavigationPane = useCallback(() => {
+ const updatedRoute = updateQueryParams({
+ paramsToAdd: { [PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM]: "outline" },
+ });
+ router.push(updatedRoute);
+ }, [router, updateQueryParams]);
+
+ const editorExtensionHandlers: Map = useMemo(() => {
+ const map: Map = new Map();
+ return map;
+ }, []);
+
+ const navigationPaneExtensions: INavigationPaneExtension[] = [];
+
+ return {
+ editorExtensionHandlers,
+ navigationPaneExtensions,
+ handleOpenNavigationPane,
+ isNavigationPaneOpen,
+ };
+};
diff --git a/apps/web/ce/hooks/use-editor-flagging.ts b/apps/web/ce/hooks/use-editor-flagging.ts
index ef5a002d642..0fc8a6eb4c6 100644
--- a/apps/web/ce/hooks/use-editor-flagging.ts
+++ b/apps/web/ce/hooks/use-editor-flagging.ts
@@ -1,5 +1,6 @@
// editor
import type { TExtensions } from "@plane/editor";
+import { EPageStoreType } from "@/plane-web/hooks/store";
export type TEditorFlaggingHookReturnType = {
document: {
@@ -16,10 +17,15 @@ export type TEditorFlaggingHookReturnType = {
};
};
+export type TEditorFlaggingHookProps = {
+ workspaceSlug: string;
+ storeType?: EPageStoreType;
+};
+
/**
* @description extensions disabled in various editors
*/
-export const useEditorFlagging = (workspaceSlug: string): TEditorFlaggingHookReturnType => ({
+export const useEditorFlagging = (props: TEditorFlaggingHookProps): TEditorFlaggingHookReturnType => ({
document: {
disabled: ["ai", "collaboration-cursor"],
flagged: [],
diff --git a/apps/web/ce/types/pages/pane-extensions.ts b/apps/web/ce/types/pages/pane-extensions.ts
new file mode 100644
index 00000000000..0f64b301907
--- /dev/null
+++ b/apps/web/ce/types/pages/pane-extensions.ts
@@ -0,0 +1,16 @@
+import {
+ type INavigationPaneExtension as ICoreNavigationPaneExtension,
+ type INavigationPaneExtensionComponent,
+} from "@/components/pages/navigation-pane";
+
+// EE Union/map of extension data types (keyed by extension id)
+export type TNavigationPaneExtensionData = Record;
+
+// EE Navigation pane extension configuration
+export interface INavigationPaneExtension<
+ T extends keyof TNavigationPaneExtensionData = keyof TNavigationPaneExtensionData,
+> extends Omit, "id" | "data" | "component"> {
+ id: T;
+ component: INavigationPaneExtensionComponent;
+ data?: TNavigationPaneExtensionData[T];
+}
diff --git a/apps/web/core/components/editor/document/editor.tsx b/apps/web/core/components/editor/document/editor.tsx
index 910d891083b..42969d0515a 100644
--- a/apps/web/core/components/editor/document/editor.tsx
+++ b/apps/web/core/components/editor/document/editor.tsx
@@ -13,7 +13,7 @@ import { useIssueEmbed } from "@/plane-web/hooks/use-issue-embed";
import { EditorMentionsRoot } from "../embeds/mentions";
type DocumentEditorWrapperProps = MakeOptional<
- Omit,
+ Omit,
"disabledExtensions" | "editable" | "flaggedExtensions"
> & {
embedHandler?: Partial;
@@ -45,7 +45,9 @@ export const DocumentEditor = forwardRef await props.searchMentionCallback(payload) : async () => ({}),
@@ -83,6 +85,7 @@ export const DocumentEditor = forwardRef
diff --git a/apps/web/core/components/editor/lite-text/editor.tsx b/apps/web/core/components/editor/lite-text/editor.tsx
index feb99ea6e0b..42d6f0bd95c 100644
--- a/apps/web/core/components/editor/lite-text/editor.tsx
+++ b/apps/web/core/components/editor/lite-text/editor.tsx
@@ -19,7 +19,7 @@ import { WorkspaceService } from "@/plane-web/services";
const workspaceService = new WorkspaceService();
type LiteTextEditorWrapperProps = MakeOptional<
- Omit,
+ Omit,
"disabledExtensions" | "flaggedExtensions"
> & {
workspaceSlug: string;
@@ -68,7 +68,9 @@ export const LiteTextEditor = React.forwardRef
{showToolbar && editable && (
diff --git a/apps/web/core/components/editor/rich-text/editor.tsx b/apps/web/core/components/editor/rich-text/editor.tsx
index aaaa0898dbf..67abc21d22a 100644
--- a/apps/web/core/components/editor/rich-text/editor.tsx
+++ b/apps/web/core/components/editor/rich-text/editor.tsx
@@ -12,7 +12,7 @@ import { useMember } from "@/hooks/store/use-member";
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
type RichTextEditorWrapperProps = MakeOptional<
- Omit,
+ Omit,
"disabledExtensions" | "editable" | "flaggedExtensions"
> & {
workspaceSlug: string;
@@ -42,7 +42,9 @@ export const RichTextEditor = forwardRef await props.searchMentionCallback(payload) : async () => ({}),
@@ -73,6 +75,7 @@ export const RichTextEditor = forwardRef
diff --git a/apps/web/core/components/editor/sticky-editor/editor.tsx b/apps/web/core/components/editor/sticky-editor/editor.tsx
index aa86d1ed586..23726652984 100644
--- a/apps/web/core/components/editor/sticky-editor/editor.tsx
+++ b/apps/web/core/components/editor/sticky-editor/editor.tsx
@@ -15,7 +15,7 @@ import { StickyEditorToolbar } from "./toolbar";
interface StickyEditorWrapperProps
extends Omit<
- ILiteTextEditorProps,
+ Omit,
"disabledExtensions" | "editable" | "flaggedExtensions" | "fileHandler" | "mentionHandler"
> {
workspaceSlug: string;
@@ -51,7 +51,9 @@ export const StickyEditor = React.forwardRef(ref: React.ForwardedRef): ref is React.MutableRefObject {
@@ -79,6 +81,7 @@ export const StickyEditor = React.forwardRef <>>,
}}
+ extendedEditorProps={{}}
containerClassName={cn(containerClassName, "relative")}
{...rest}
/>
diff --git a/apps/web/core/components/pages/editor/editor-body.tsx b/apps/web/core/components/pages/editor/editor-body.tsx
index bb7e934d6af..3ada9bf3ca5 100644
--- a/apps/web/core/components/pages/editor/editor-body.tsx
+++ b/apps/web/core/components/pages/editor/editor-body.tsx
@@ -24,8 +24,13 @@ import { useUser } from "@/hooks/store/user";
import { usePageFilters } from "@/hooks/use-page-filters";
// plane web components
import { EditorAIMenu } from "@/plane-web/components/pages";
+// plane web types
+import type { TExtendedEditorExtensionsConfig } from "@/plane-web/hooks/pages";
+// plane web store
+import { EPageStoreType } from "@/plane-web/hooks/store";
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
+
import { useIssueEmbed } from "@/plane-web/hooks/use-issue-embed";
// store
import type { TPageInstance } from "@/store/pages/base-page";
@@ -41,6 +46,7 @@ export type TEditorBodyConfig = {
export type TEditorBodyHandlers = {
fetchEntity: (payload: TSearchEntityRequestPayload) => Promise;
+ getRedirectionLink: (pageId?: string) => string;
};
type Props = {
@@ -54,7 +60,11 @@ type Props = {
isNavigationPaneOpen: boolean;
page: TPageInstance;
webhookConnectionParams: TWebhookConnectionQueryParams;
+ projectId: string;
workspaceSlug: string;
+ storeType: EPageStoreType;
+
+ extendedEditorProps: TExtendedEditorExtensionsConfig;
};
export const PageEditorBody: React.FC = observer((props) => {
@@ -67,8 +77,11 @@ export const PageEditorBody: React.FC = observer((props) => {
handlers,
isNavigationPaneOpen,
page,
+ storeType,
webhookConnectionParams,
+ projectId,
workspaceSlug,
+ extendedEditorProps,
} = props;
// store hooks
const { data: currentUser } = useUser();
@@ -93,7 +106,10 @@ export const PageEditorBody: React.FC = observer((props) => {
searchEntity: handlers.fetchEntity,
});
// editor flaggings
- const { document: documentEditorExtensions } = useEditorFlagging(workspaceSlug);
+ const { document: documentEditorExtensions } = useEditorFlagging({
+ workspaceSlug,
+ storeType,
+ });
// page filters
const { fontSize, fontStyle, isFullWidth } = usePageFilters();
// translation
@@ -115,7 +131,7 @@ export const PageEditorBody: React.FC = observer((props) => {
isOpen={isOpen}
onClose={onClose}
workspaceId={workspaceId}
- workspaceSlug={workspaceSlug?.toString() ?? ""}
+ workspaceSlug={workspaceSlug}
/>
),
[editorRef, workspaceId, workspaceSlug]
@@ -202,7 +218,7 @@ export const PageEditorBody: React.FC = observer((props) => {
)}
-
+
= observer((props) => {
menu: getAIMenu,
}}
onAssetChange={updateAssetsList}
+ extendedEditorProps={extendedEditorProps}
/>
diff --git a/apps/web/core/components/pages/editor/header/root.tsx b/apps/web/core/components/pages/editor/header/root.tsx
index 17092d8c564..85e8546c0fa 100644
--- a/apps/web/core/components/pages/editor/header/root.tsx
+++ b/apps/web/core/components/pages/editor/header/root.tsx
@@ -11,6 +11,7 @@ import { PageEditorHeaderLogoPicker } from "./logo-picker";
type Props = {
page: TPageInstance;
+ projectId: string;
};
export const PageEditorHeaderRoot: React.FC
= observer((props) => {
diff --git a/apps/web/core/components/pages/editor/page-root.tsx b/apps/web/core/components/pages/editor/page-root.tsx
index d43c319c1a4..3159691e136 100644
--- a/apps/web/core/components/pages/editor/page-root.tsx
+++ b/apps/web/core/components/pages/editor/page-root.tsx
@@ -1,6 +1,5 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
-import { useSearchParams } from "next/navigation";
// plane imports
import type { EditorRefApi } from "@plane/editor";
import type { TDocumentPayload, TPage, TPageVersion, TWebhookConnectionQueryParams } from "@plane/types";
@@ -9,19 +8,20 @@ import { useAppRouter } from "@/hooks/use-app-router";
import { usePageFallback } from "@/hooks/use-page-fallback";
import { useQueryParams } from "@/hooks/use-query-params";
// plane web import
-import type { TPageNavigationPaneTab } from "@/plane-web/components/pages/navigation-pane";
+import { PageModals } from "@/plane-web/components/pages";
+import { usePagesPaneExtensions, useExtendedEditorProps } from "@/plane-web/hooks/pages";
+import { EPageStoreType } from "@/plane-web/hooks/store";
// store
import type { TPageInstance } from "@/store/pages/base-page";
// local imports
import {
- PAGE_NAVIGATION_PANE_TAB_KEYS,
PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM,
PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM,
PageNavigationPaneRoot,
} from "../navigation-pane";
import { PageVersionsOverlay } from "../version";
import { PagesVersionEditor } from "../version/editor";
-import { PageEditorBody, TEditorBodyConfig, TEditorBodyHandlers } from "./editor-body";
+import { PageEditorBody, type TEditorBodyConfig, type TEditorBodyHandlers } from "./editor-body";
import { PageEditorToolbarRoot } from "./toolbar";
export type TPageRootHandlers = {
@@ -29,7 +29,7 @@ export type TPageRootHandlers = {
fetchAllVersions: (pageId: string) => Promise;
fetchDescriptionBinary: () => Promise;
fetchVersionDetails: (pageId: string, versionId: string) => Promise;
- getRedirectionLink: (pageId: string) => string;
+ restoreVersion: (pageId: string, versionId: string) => Promise;
updateDescription: (document: TDocumentPayload) => Promise;
} & TEditorBodyHandlers;
@@ -39,12 +39,14 @@ type TPageRootProps = {
config: TPageRootConfig;
handlers: TPageRootHandlers;
page: TPageInstance;
+ storeType: EPageStoreType;
webhookConnectionParams: TWebhookConnectionQueryParams;
+ projectId: string;
workspaceSlug: string;
};
export const PageRoot = observer((props: TPageRootProps) => {
- const { config, handlers, page, webhookConnectionParams, workspaceSlug } = props;
+ const { config, handlers, page, projectId, storeType, webhookConnectionParams, workspaceSlug } = props;
// states
const [editorReady, setEditorReady] = useState(false);
const [hasConnectionFailed, setHasConnectionFailed] = useState(false);
@@ -52,8 +54,6 @@ export const PageRoot = observer((props: TPageRootProps) => {
const editorRef = useRef(null);
// router
const router = useAppRouter();
- // search params
- const searchParams = useSearchParams();
// derived values
const {
isContentEditable,
@@ -66,7 +66,6 @@ export const PageRoot = observer((props: TPageRootProps) => {
hasConnectionFailed,
updatePageDescription: handlers.updateDescription,
});
- // update query params
const { updateQueryParams } = useQueryParams();
const handleEditorReady = useCallback(
@@ -85,10 +84,31 @@ export const PageRoot = observer((props: TPageRootProps) => {
}, 0);
}, [isContentEditable, setEditorRef]);
- const handleRestoreVersion = useCallback(async (descriptionHTML: string) => {
- editorRef.current?.clearEditor();
- editorRef.current?.setEditorValue(descriptionHTML);
- }, []);
+ // Get extensions and navigation logic from hook
+ const { editorExtensionHandlers, navigationPaneExtensions, handleOpenNavigationPane, isNavigationPaneOpen } =
+ usePagesPaneExtensions({
+ page,
+ editorRef,
+ });
+
+ // Get extended editor extensions configuration
+ const extendedEditorProps = useExtendedEditorProps({
+ workspaceSlug,
+ page,
+ storeType,
+ fetchEntity: handlers.fetchEntity,
+ getRedirectionLink: handlers.getRedirectionLink,
+ extensionHandlers: editorExtensionHandlers,
+ projectId,
+ });
+
+ const handleRestoreVersion = useCallback(
+ async (descriptionHTML: string) => {
+ editorRef.current?.clearEditor();
+ editorRef.current?.setEditorValue(descriptionHTML);
+ },
+ [editorRef]
+ );
// reset editor ref on unmount
useEffect(
@@ -98,19 +118,6 @@ export const PageRoot = observer((props: TPageRootProps) => {
[setEditorRef]
);
- const navigationPaneQueryParam = searchParams.get(
- PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM
- ) as TPageNavigationPaneTab | null;
- const isValidNavigationPaneTab =
- !!navigationPaneQueryParam && PAGE_NAVIGATION_PANE_TAB_KEYS.includes(navigationPaneQueryParam);
-
- const handleOpenNavigationPane = useCallback(() => {
- const updatedRoute = updateQueryParams({
- paramsToAdd: { [PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM]: "outline" },
- });
- router.push(updatedRoute);
- }, [router, updateQueryParams]);
-
const handleCloseNavigationPane = useCallback(() => {
const updatedRoute = updateQueryParams({
paramsToRemove: [PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM, PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM],
@@ -127,10 +134,11 @@ export const PageRoot = observer((props: TPageRootProps) => {
handleRestore={handleRestoreVersion}
pageId={page.id ?? ""}
restoreEnabled={isContentEditable}
+ storeType={storeType}
/>
{
handleEditorReady={handleEditorReady}
handleOpenNavigationPane={handleOpenNavigationPane}
handlers={handlers}
- isNavigationPaneOpen={isValidNavigationPaneTab}
+ isNavigationPaneOpen={isNavigationPaneOpen}
page={page}
+ projectId={projectId}
+ storeType={storeType}
webhookConnectionParams={webhookConnectionParams}
workspaceSlug={workspaceSlug}
+ extendedEditorProps={extendedEditorProps}
/>
+
);
});
diff --git a/apps/web/core/components/pages/navigation-pane/index.ts b/apps/web/core/components/pages/navigation-pane/index.ts
index fc8595eaafc..cbb4ee7b458 100644
--- a/apps/web/core/components/pages/navigation-pane/index.ts
+++ b/apps/web/core/components/pages/navigation-pane/index.ts
@@ -2,6 +2,7 @@
import { ORDERED_PAGE_NAVIGATION_TABS_LIST } from "@/plane-web/components/pages/navigation-pane";
export * from "./root";
+export * from "./types";
export const PAGE_NAVIGATION_PANE_WIDTH = 294;
diff --git a/apps/web/core/components/pages/navigation-pane/root.tsx b/apps/web/core/components/pages/navigation-pane/root.tsx
index 688e45078f0..d9de5f977d1 100644
--- a/apps/web/core/components/pages/navigation-pane/root.tsx
+++ b/apps/web/core/components/pages/navigation-pane/root.tsx
@@ -11,11 +11,14 @@ import { useQueryParams } from "@/hooks/use-query-params";
// plane web components
import { TPageNavigationPaneTab } from "@/plane-web/components/pages/navigation-pane";
// store
+import { type EPageStoreType } from "@/plane-web/hooks/store";
import type { TPageInstance } from "@/store/pages/base-page";
// local imports
import { TPageRootHandlers } from "../editor/page-root";
import { PageNavigationPaneTabPanelsRoot } from "./tab-panels/root";
import { PageNavigationPaneTabsList } from "./tabs-list";
+import { INavigationPaneExtension } from "./types/extensions";
+
import {
PAGE_NAVIGATION_PANE_TAB_KEYS,
PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM,
@@ -28,10 +31,14 @@ type Props = {
isNavigationPaneOpen: boolean;
page: TPageInstance;
versionHistory: Pick;
+ // Generic extension system for additional navigation pane content
+ extensions?: INavigationPaneExtension[];
+ storeType: EPageStoreType;
};
export const PageNavigationPaneRoot: React.FC = observer((props) => {
- const { handleClose, isNavigationPaneOpen, page, versionHistory } = props;
+ const { handleClose, isNavigationPaneOpen, page, versionHistory, extensions = [], storeType } = props;
+
// navigation
const router = useRouter();
const searchParams = useSearchParams();
@@ -43,6 +50,21 @@ export const PageNavigationPaneRoot: React.FC = observer((props) => {
) as TPageNavigationPaneTab | null;
const activeTab: TPageNavigationPaneTab = navigationPaneQueryParam || "outline";
const selectedIndex = PAGE_NAVIGATION_PANE_TAB_KEYS.indexOf(activeTab);
+
+ // Check if any extension is currently active based on query parameters
+ const ActiveExtension = extensions.find((extension) => {
+ const paneTabValue = searchParams.get(PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM);
+ const hasVersionParam = searchParams.get(PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM);
+
+ // Extension is active ONLY when paneTab matches AND no regular navigation params are present
+ return paneTabValue === extension.triggerParam && !hasVersionParam;
+ });
+
+ // Don't show tabs when an extension is active
+ const showNavigationTabs = !ActiveExtension && isNavigationPaneOpen;
+
+ // Use extension width if available, otherwise fall back to default
+ const paneWidth = ActiveExtension?.width ?? PAGE_NAVIGATION_PANE_WIDTH;
// translation
const { t } = useTranslation();
@@ -61,10 +83,10 @@ export const PageNavigationPaneRoot: React.FC = observer((props) => {
return (
);
});
diff --git a/apps/web/core/components/pages/navigation-pane/types/extensions.ts b/apps/web/core/components/pages/navigation-pane/types/extensions.ts
new file mode 100644
index 00000000000..7460483b9c7
--- /dev/null
+++ b/apps/web/core/components/pages/navigation-pane/types/extensions.ts
@@ -0,0 +1,21 @@
+import { ReactNode } from "react";
+import { EPageStoreType } from "@/plane-web/hooks/store";
+import { TPageInstance } from "@/store/pages/base-page";
+
+export interface INavigationPaneExtensionProps {
+ page: TPageInstance;
+ extensionData?: T;
+ storeType: EPageStoreType;
+}
+
+export interface INavigationPaneExtensionComponent {
+ (props: INavigationPaneExtensionProps): ReactNode;
+}
+
+export interface INavigationPaneExtension {
+ id: string;
+ triggerParam: string;
+ component: INavigationPaneExtensionComponent;
+ data?: T;
+ width?: number;
+}
diff --git a/apps/web/core/components/pages/navigation-pane/types/index.ts b/apps/web/core/components/pages/navigation-pane/types/index.ts
new file mode 100644
index 00000000000..fd10e53821c
--- /dev/null
+++ b/apps/web/core/components/pages/navigation-pane/types/index.ts
@@ -0,0 +1,6 @@
+// Export generic extension system interfaces
+export type {
+ INavigationPaneExtensionProps,
+ INavigationPaneExtensionComponent,
+ INavigationPaneExtension,
+} from "./extensions";
diff --git a/apps/web/core/components/pages/version/editor.tsx b/apps/web/core/components/pages/version/editor.tsx
index f09f3724ea7..412b80c0c07 100644
--- a/apps/web/core/components/pages/version/editor.tsx
+++ b/apps/web/core/components/pages/version/editor.tsx
@@ -9,10 +9,13 @@ import { DocumentEditor } from "@/components/editor/document/editor";
// hooks
import { useWorkspace } from "@/hooks/store/use-workspace";
import { usePageFilters } from "@/hooks/use-page-filters";
+// plane web hooks
+import { EPageStoreType } from "@/plane-web/hooks/store";
export type TVersionEditorProps = {
activeVersion: string | null;
versionDetails: TPageVersion | undefined;
+ storeType: EPageStoreType;
};
export const PagesVersionEditor: React.FC = observer((props) => {
diff --git a/apps/web/core/components/pages/version/main-content.tsx b/apps/web/core/components/pages/version/main-content.tsx
index 40c861b698a..100fa9b0a93 100644
--- a/apps/web/core/components/pages/version/main-content.tsx
+++ b/apps/web/core/components/pages/version/main-content.tsx
@@ -6,6 +6,8 @@ import { EyeIcon, TriangleAlert } from "lucide-react";
import { TPageVersion } from "@plane/types";
import { Button, setToast, TOAST_TYPE } from "@plane/ui";
import { renderFormattedDate, renderFormattedTime } from "@plane/utils";
+// helpers
+import { EPageStoreType } from "@/plane-web/hooks/store";
// local imports
import { TVersionEditorProps } from "./editor";
@@ -17,11 +19,20 @@ type Props = {
handleRestore: (descriptionHTML: string) => Promise;
pageId: string;
restoreEnabled: boolean;
+ storeType: EPageStoreType;
};
export const PageVersionsMainContent: React.FC = observer((props) => {
- const { activeVersion, editorComponent, fetchVersionDetails, handleClose, handleRestore, pageId, restoreEnabled } =
- props;
+ const {
+ activeVersion,
+ editorComponent,
+ fetchVersionDetails,
+ handleClose,
+ handleRestore,
+ pageId,
+ restoreEnabled,
+ storeType,
+ } = props;
// states
const [isRestoring, setIsRestoring] = useState(false);
const [isRetrying, setIsRetrying] = useState(false);
@@ -107,7 +118,7 @@ export const PageVersionsMainContent: React.FC = observer((props) => {
)}
-
+
>
)}
diff --git a/apps/web/core/components/pages/version/root.tsx b/apps/web/core/components/pages/version/root.tsx
index df2f39942dc..4ad00f3cc7d 100644
--- a/apps/web/core/components/pages/version/root.tsx
+++ b/apps/web/core/components/pages/version/root.tsx
@@ -6,6 +6,8 @@ import { TPageVersion } from "@plane/types";
import { cn } from "@plane/utils";
// hooks
import { useQueryParams } from "@/hooks/use-query-params";
+// plane web imports
+import { EPageStoreType } from "@/plane-web/hooks/store";
// local imports
import { PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM, PAGE_NAVIGATION_PANE_WIDTH } from "../navigation-pane";
import { TVersionEditorProps } from "./editor";
@@ -17,10 +19,11 @@ type Props = {
handleRestore: (descriptionHTML: string) => Promise;
pageId: string;
restoreEnabled: boolean;
+ storeType: EPageStoreType;
};
export const PageVersionsOverlay: React.FC = observer((props) => {
- const { editorComponent, fetchVersionDetails, handleRestore, pageId, restoreEnabled } = props;
+ const { editorComponent, fetchVersionDetails, handleRestore, pageId, restoreEnabled, storeType } = props;
// navigation
const router = useRouter();
const searchParams = useSearchParams();
@@ -57,6 +60,7 @@ export const PageVersionsOverlay: React.FC = observer((props) => {
handleRestore={handleRestore}
pageId={pageId}
restoreEnabled={restoreEnabled}
+ storeType={storeType}
/>
);
diff --git a/apps/web/core/services/page/project-page-version.service.ts b/apps/web/core/services/page/project-page-version.service.ts
index 2f973f0fa10..1491cf37f98 100644
--- a/apps/web/core/services/page/project-page-version.service.ts
+++ b/apps/web/core/services/page/project-page-version.service.ts
@@ -30,4 +30,14 @@ export class ProjectPageVersionService extends APIService {
throw error?.response?.data;
});
}
+
+ async restoreVersion(workspaceSlug: string, projectId: string, pageId: string, versionId: string): Promise {
+ return this.post(
+ `/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/versions/${versionId}/restore/`
+ )
+ .then((response) => response?.data)
+ .catch((error) => {
+ throw error?.response?.data;
+ });
+ }
}
diff --git a/apps/web/core/store/pages/base-page.ts b/apps/web/core/store/pages/base-page.ts
index f80856bac4d..2835da3a4d3 100644
--- a/apps/web/core/store/pages/base-page.ts
+++ b/apps/web/core/store/pages/base-page.ts
@@ -93,6 +93,7 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
updated_by: string | undefined;
created_at: Date | undefined;
updated_at: Date | undefined;
+ deleted_at: Date | undefined;
// helpers
oldName: string = "";
// services
@@ -130,6 +131,7 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
this.created_at = page?.created_at || undefined;
this.updated_at = page?.updated_at || undefined;
this.oldName = page?.name || "";
+ this.deleted_at = page?.deleted_at || undefined;
makeObservable(this, {
// loaders
@@ -153,6 +155,7 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
updated_by: observable.ref,
created_at: observable.ref,
updated_at: observable.ref,
+ deleted_at: observable.ref,
// helpers
oldName: observable.ref,
setIsSubmitting: action,
@@ -227,6 +230,7 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
updated_by: this.updated_by,
created_at: this.created_at,
updated_at: this.updated_at,
+ deleted_at: this.deleted_at,
...this.asJSONExtended,
};
}
diff --git a/packages/editor/src/ce/components/document-editor-side-effects.ts b/packages/editor/src/ce/components/document-editor-side-effects.ts
new file mode 100644
index 00000000000..36a180a12ee
--- /dev/null
+++ b/packages/editor/src/ce/components/document-editor-side-effects.ts
@@ -0,0 +1,12 @@
+import { type Editor } from "@tiptap/core";
+import type { ReactElement } from "react";
+import type { IEditorPropsExtended } from "@/types";
+
+export type DocumentEditorSideEffectsProps = {
+ editor: Editor;
+ id: string;
+ updatePageProperties?: unknown;
+ extendedEditorProps?: IEditorPropsExtended;
+};
+
+export const DocumentEditorSideEffects = (_props: DocumentEditorSideEffectsProps): ReactElement | null => null;
diff --git a/packages/editor/src/ce/types/editor-extended.ts b/packages/editor/src/ce/types/editor-extended.ts
new file mode 100644
index 00000000000..98f9c7ece87
--- /dev/null
+++ b/packages/editor/src/ce/types/editor-extended.ts
@@ -0,0 +1,9 @@
+export type IEditorExtensionOptions = unknown;
+
+export type IEditorPropsExtended = unknown;
+
+export type TExtendedEditorCommands = never;
+
+export type TExtendedCommandExtraProps = unknown;
+
+export type TExtendedEditorRefApi = unknown;
diff --git a/packages/editor/src/ce/types/index.ts b/packages/editor/src/ce/types/index.ts
index f30596cb00f..7efa23c054e 100644
--- a/packages/editor/src/ce/types/index.ts
+++ b/packages/editor/src/ce/types/index.ts
@@ -1 +1,2 @@
export * from "./issue-embed";
+export * from "./editor-extended";
diff --git a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx
index 535f1b6ba13..038184204e6 100644
--- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx
+++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx
@@ -12,6 +12,8 @@ import { WorkItemEmbedExtension } from "@/extensions";
import { getEditorClassNames } from "@/helpers/common";
// hooks
import { useCollaborativeEditor } from "@/hooks/use-collaborative-editor";
+// constants
+import { DocumentEditorSideEffects } from "@/plane-editor/components/document-editor-side-effects";
// types
import type { EditorRefApi, ICollaborativeDocumentEditorProps } from "@/types";
@@ -27,6 +29,7 @@ const CollaborativeDocumentEditor: React.FC =
editable,
editorClassName = "",
editorProps,
+ extendedEditorProps,
embedHandler,
fileHandler,
flaggedExtensions,
@@ -97,20 +100,23 @@ const CollaborativeDocumentEditor: React.FC =
if (!editor) return null;
return (
-
+ <>
+
+
+ >
);
};
diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts
index ed26bbbdac7..33fb71f6c3a 100644
--- a/packages/editor/src/core/types/editor.ts
+++ b/packages/editor/src/core/types/editor.ts
@@ -5,6 +5,8 @@ import type { EditorProps, EditorView } from "@tiptap/pm/view";
import type { NodeViewProps as TNodeViewProps } from "@tiptap/react";
// extension types
import type { TTextAlign } from "@/extensions";
+// plane editor imports
+import type { IEditorPropsExtended, TExtendedEditorCommands } from "@/plane-editor/types/editor-extended";
// types
import type {
IMarking,
@@ -50,7 +52,8 @@ export type TEditorCommands =
| "callout"
| "attachment"
| "emoji"
- | "external-embed";
+ | "external-embed"
+ | TExtendedEditorCommands;
export type TCommandExtraProps = {
image: {
@@ -157,6 +160,7 @@ export type IEditorProps = {
placeholder?: string | ((isFocused: boolean, value: string) => string);
tabIndex?: number;
value?: string | null;
+ extendedEditorProps: IEditorPropsExtended;
};
export type ILiteTextEditorProps = IEditorProps;
diff --git a/packages/editor/src/core/types/index.ts b/packages/editor/src/core/types/index.ts
index cfa67ba9732..d3a12bfb5ce 100644
--- a/packages/editor/src/core/types/index.ts
+++ b/packages/editor/src/core/types/index.ts
@@ -8,5 +8,6 @@ export * from "./extensions";
export * from "./hook";
export * from "./mention";
export * from "./slash-commands-suggestion";
-export * from "@/plane-editor/types";
export * from "./document-collaborative-events";
+
+export * from "@/plane-editor/types";
diff --git a/packages/types/src/editor/editor-content.ts b/packages/types/src/editor/editor-content.ts
new file mode 100644
index 00000000000..bb720c2e97a
--- /dev/null
+++ b/packages/types/src/editor/editor-content.ts
@@ -0,0 +1,20 @@
+/**
+ * Editor content types - locally defined to avoid external dependencies
+ */
+
+export type JSONContent = {
+ type?: string;
+ attrs?: Record;
+ content?: JSONContent[];
+ marks?: {
+ type: string;
+ attrs?: Record;
+ [key: string]: unknown;
+ }[];
+ text?: string;
+ [key: string]: unknown;
+};
+
+export type HTMLContent = string;
+
+export type Content = HTMLContent | JSONContent | JSONContent[] | null;
diff --git a/packages/types/src/editor/index.ts b/packages/types/src/editor/index.ts
new file mode 100644
index 00000000000..841ec3255e6
--- /dev/null
+++ b/packages/types/src/editor/index.ts
@@ -0,0 +1 @@
+export type { JSONContent, HTMLContent, Content } from "./editor-content";
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index ae35062e58a..2a63141b148 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -28,6 +28,7 @@ export * from "./waitlist";
export * from "./webhook";
export * from "./workspace-views";
export * from "./common";
+export * from "./editor";
export * from "./pragmatic";
export * from "./publish";
export * from "./search";
diff --git a/packages/types/src/issues/activity/issue_comment.ts b/packages/types/src/issues/activity/issue_comment.ts
index 9507a493492..f5db3679e28 100644
--- a/packages/types/src/issues/activity/issue_comment.ts
+++ b/packages/types/src/issues/activity/issue_comment.ts
@@ -1,3 +1,4 @@
+import { JSONContent } from "../../editor";
import { EIssueCommentAccessSpecifier } from "../../enums";
import { TFileSignedURLResponse } from "../../file";
import { IUserLite } from "../../users";
@@ -34,7 +35,7 @@ export type TIssueComment = {
comment_reactions: any[];
comment_stripped: string;
comment_html: string;
- comment_json: object;
+ comment_json: JSONContent;
external_id: string | undefined;
external_source: string | undefined;
access: EIssueCommentAccessSpecifier;
diff --git a/packages/types/src/page/core.ts b/packages/types/src/page/core.ts
index 6e399c7687e..9351d7d2728 100644
--- a/packages/types/src/page/core.ts
+++ b/packages/types/src/page/core.ts
@@ -21,6 +21,7 @@ export type TPage = {
updated_by: string | undefined;
workspace: string | undefined;
logo_props: TLogoProps | undefined;
+ deleted_at: Date | undefined;
} & TPageExtended;
// page filters
diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts
index 062a162abec..a2334a99e4c 100644
--- a/packages/utils/src/string.ts
+++ b/packages/utils/src/string.ts
@@ -1,3 +1,4 @@
+import type { Content, JSONContent } from "@plane/types";
import DOMPurify from "isomorphic-dompurify";
/**
@@ -161,14 +162,86 @@ export const isEmptyHtmlString = (htmlString: string, allowedHTMLTags: string[]
};
/**
- * @description this function returns whether a comment is empty or not by checking for the following conditions-
- * 1. If comment is undefined
+ * @description
+ * Check if a JSONContent object is empty
+ * @param {JSONContent} content
+ * @returns {boolean}
+ */
+const isJSONContentEmpty = (content: JSONContent): boolean => {
+ // If it has text, check if text is meaningful
+ if (content.text !== undefined) {
+ return !content.text || content.text.trim() === "";
+ }
+
+ // If it has no content array, consider it empty
+ if (!content.content || content.content.length === 0) {
+ // Special case: empty paragraph nodes should be considered empty
+ if (content.type === "paragraph" || content.type === "doc") {
+ return true;
+ }
+ // For other node types without content (like hard breaks), check if they're meaningful
+ return (
+ content.type !== "hardBreak" &&
+ content.type !== "image" &&
+ content.type !== "mention-component" &&
+ content.type !== "image-component"
+ );
+ }
+
+ // Check if all nested content is empty
+ return content.content.every(isJSONContentEmpty);
+};
+
+/**
+ * @description
+ * This function will check if the comment is empty or not.
+ * It returns true if comment is empty.
+ * Now supports TipTap Content types (HTMLContent, JSONContent, JSONContent[], null)
+ *
+ * For HTML content:
+ * 1. If comment is undefined/null
* 2. If comment is an empty string
* 3. If comment is ""
+ * 4. If comment contains only empty HTML tags
+ *
+ * For JSON content:
+ * 1. If content is null/undefined
+ * 2. If content has no meaningful text or nested content
+ * 3. If all nested content is empty
+ *
+ * @param {Content} comment - TipTap Content type
+ * @returns {boolean}
+ */
+export const isCommentEmpty = (comment: Content | undefined): boolean => {
+ // Handle null/undefined
+ if (!comment) return true;
+
+ // Handle HTMLContent (string)
+ if (typeof comment === "string") {
+ return (
+ comment.trim() === "" ||
+ comment === "" ||
+ isEmptyHtmlString(comment, ["img", "mention-component", "image-component"])
+ );
+ }
+
+ // Handle JSONContent[] (array)
+ if (Array.isArray(comment)) {
+ return comment.length === 0 || comment.every(isJSONContentEmpty);
+ }
+
+ // Handle JSONContent (object)
+ return isJSONContentEmpty(comment);
+};
+
+/**
+ * @description
+ * Legacy function for backward compatibility with string comments
* @param {string | undefined} comment
* @returns {boolean}
+ * @deprecated Use isCommentEmpty with Content type instead
*/
-export const isCommentEmpty = (comment: string | undefined): boolean => {
+export const isStringCommentEmpty = (comment: string | undefined): boolean => {
// return true if comment is undefined
if (!comment) return true;
return (