From b729d64bc8240b1f94f566feda295c6b1605559d Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 28 Aug 2025 12:34:40 +0530 Subject: [PATCH 01/10] fix: better refactoring of page root and editor body --- .../pages/(detail)/[pageId]/page.tsx | 40 ++++++++++-- apps/web/ce/components/pages/modals/index.ts | 1 + .../web/ce/components/pages/modals/modals.tsx | 17 +++++ apps/web/ce/hooks/pages/index.ts | 2 + .../pages/use-extended-editor-extensions.ts | 18 +++++ .../hooks/pages/use-pages-pane-extensions.ts | 53 +++++++++++++++ apps/web/ce/hooks/use-editor-flagging.ts | 6 +- apps/web/ce/types/pages/pane-extensions.ts | 15 +++++ .../components/pages/editor/editor-body.tsx | 18 ++++- .../components/pages/editor/header/root.tsx | 1 + .../components/pages/editor/page-root.tsx | 65 +++++++++++-------- .../components/pages/navigation-pane/index.ts | 1 + .../components/pages/navigation-pane/root.tsx | 45 ++++++++++--- .../pages/navigation-pane/types/extensions.ts | 21 ++++++ .../pages/navigation-pane/types/index.ts | 6 ++ .../core/components/pages/version/editor.tsx | 3 + .../components/pages/version/main-content.tsx | 17 ++++- .../core/components/pages/version/root.tsx | 6 +- .../page/project-page-version.service.ts | 10 +++ apps/web/core/store/pages/base-page.ts | 4 ++ .../editor/src/ce/types/editor-extended.ts | 9 +++ packages/editor/src/ce/types/index.ts | 1 + packages/editor/src/core/types/editor.ts | 6 +- packages/editor/src/core/types/index.ts | 3 +- packages/types/src/page/core.ts | 1 + 25 files changed, 319 insertions(+), 50 deletions(-) create mode 100644 apps/web/ce/components/pages/modals/modals.tsx create mode 100644 apps/web/ce/hooks/pages/index.ts create mode 100644 apps/web/ce/hooks/pages/use-extended-editor-extensions.ts create mode 100644 apps/web/ce/hooks/pages/use-pages-pane-extensions.ts create mode 100644 apps/web/ce/types/pages/pane-extensions.ts create mode 100644 apps/web/core/components/pages/navigation-pane/types/extensions.ts create mode 100644 apps/web/core/components/pages/navigation-pane/types/index.ts create mode 100644 packages/editor/src/ce/types/editor-extended.ts 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..1a831b85ace 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 (
@@ -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..e920772500b --- /dev/null +++ b/apps/web/ce/components/pages/modals/modals.tsx @@ -0,0 +1,17 @@ +"use client"; + +import React from "react"; +import { observer } from "mobx-react"; +import type { EditorRefApi } from "@plane/editor"; +// components +import { EPageStoreType } from "@/plane-web/hooks/store"; +// store +import { TPageInstance } from "@/store/pages/base-page"; + +export type TPageModalsProps = { + page: TPageInstance; + storeType: EPageStoreType; + editorRef?: EditorRefApi | null; +}; + +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..eef355b96ad --- /dev/null +++ b/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts @@ -0,0 +1,18 @@ +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 useExtendedEditorExtensions = (params: TExtendedEditorExtensionsHookParams) => null; 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..ace3f7e91cc --- /dev/null +++ b/apps/web/ce/hooks/pages/use-pages-pane-extensions.ts @@ -0,0 +1,53 @@ +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)) || + searchParams.get("paneTab") === "comments"; + + 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..720e43b7717 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: { @@ -19,7 +20,10 @@ export type TEditorFlaggingHookReturnType = { /** * @description extensions disabled in various editors */ -export const useEditorFlagging = (workspaceSlug: string): TEditorFlaggingHookReturnType => ({ +export const useEditorFlagging = ( + workspaceSlug: string, + storeType?: EPageStoreType +): 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..fb46c00f9c6 --- /dev/null +++ b/apps/web/ce/types/pages/pane-extensions.ts @@ -0,0 +1,15 @@ +import { type INavigationPaneExtension as ICoreNavigationPaneExtension } from "@/components/pages/navigation-pane"; + +// EE Union of all possible navigation pane extension data types +export type TNavigationPaneExtensionData = unknown; + +// EE Navigation pane extension configuration with comment support +export interface INavigationPaneExtension< + T extends keyof TNavigationPaneExtensionData = keyof TNavigationPaneExtensionData, +> extends ICoreNavigationPaneExtension { + id: T; + triggerParam: string; + component: any; + data?: TNavigationPaneExtensionData[T]; + width?: number; +} diff --git a/apps/web/core/components/pages/editor/editor-body.tsx b/apps/web/core/components/pages/editor/editor-body.tsx index bb7e934d6af..3cb6c736c63 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,7 @@ 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 @@ -202,7 +215,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..6ff3f7d7996 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, useExtendedEditorExtensions } 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,28 @@ export const PageRoot = observer((props: TPageRootProps) => { }, 0); }, [isContentEditable, setEditorRef]); - const handleRestoreVersion = useCallback(async (descriptionHTML: string) => { + // Get extensions and navigation logic from hook + const { editorExtensionHandlers, navigationPaneExtensions, handleOpenNavigationPane, isNavigationPaneOpen } = + usePagesPaneExtensions({ + page, + editorRef, + }); + + // Get extended editor extensions configuration + const extendedEditorProps = useExtendedEditorExtensions({ + workspaceSlug, + page, + storeType, + fetchEntity: handlers.fetchEntity, + getRedirectionLink: handlers.getRedirectionLink, + extensionHandlers: editorExtensionHandlers, + projectId, + }); + + const handleRestoreVersion = async (descriptionHTML: string) => { editorRef.current?.clearEditor(); editorRef.current?.setEditorValue(descriptionHTML); - }, []); + }; // reset editor ref on unmount useEffect( @@ -98,19 +115,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 +131,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..bfa8a70aada 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("paneTab"); + 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/types/editor-extended.ts b/packages/editor/src/ce/types/editor-extended.ts new file mode 100644 index 00000000000..97ddab994d5 --- /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 = ""; + +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/types/editor.ts b/packages/editor/src/core/types/editor.ts index ed26bbbdac7..48ef1d94c10 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/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 From 268f98723bc4b6a35f1e2974d3eaedeb506b82dc Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 28 Aug 2025 13:29:33 +0530 Subject: [PATCH 02/10] fix: editor sideeffects --- .../src/ce/components/editor-side-effects.ts | 9 +++++ .../editors/document/collaborative-editor.tsx | 34 +++++++++++-------- 2 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 packages/editor/src/ce/components/editor-side-effects.ts diff --git a/packages/editor/src/ce/components/editor-side-effects.ts b/packages/editor/src/ce/components/editor-side-effects.ts new file mode 100644 index 00000000000..9aa5a56bab2 --- /dev/null +++ b/packages/editor/src/ce/components/editor-side-effects.ts @@ -0,0 +1,9 @@ +import { type Editor } from "@tiptap/core"; +import type { IEditorPropsExtended } from "@/types"; + +export const EditorSideEffects = (props: { + editor: Editor; + id: string; + updatePageProperties?: any; + extendedEditorProps?: IEditorPropsExtended; +}) => null; 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..2e085938037 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 { EditorSideEffects } from "@/plane-editor/components/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 ( - + <> + + + ); }; From 9ae11f7e73e6ccc07b9eab974f49583dddcd4eef Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 28 Aug 2025 13:40:09 +0530 Subject: [PATCH 03/10] fix: add comments json field --- .../components/editor/lite-text-editor.tsx | 3 +- apps/space/helpers/string.helper.ts | 18 ----- packages/types/src/editor/editor-content.ts | 20 +++++ packages/types/src/editor/index.ts | 1 + packages/types/src/index.ts | 1 + .../src/issues/activity/issue_comment.ts | 3 +- packages/utils/src/string.ts | 79 ++++++++++++++++++- 7 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 packages/types/src/editor/editor-content.ts create mode 100644 packages/types/src/editor/index.ts diff --git a/apps/space/core/components/editor/lite-text-editor.tsx b/apps/space/core/components/editor/lite-text-editor.tsx index 3223135c99c..f22e8c0c40e 100644 --- a/apps/space/core/components/editor/lite-text-editor.tsx +++ b/apps/space/core/components/editor/lite-text-editor.tsx @@ -2,10 +2,9 @@ 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"; diff --git a/apps/space/helpers/string.helper.ts b/apps/space/helpers/string.helper.ts index cd086300c02..2cca3177a6b 100644 --- a/apps/space/helpers/string.helper.ts +++ b/apps/space/helpers/string.helper.ts @@ -57,24 +57,6 @@ export const isEmptyHtmlString = (htmlString: string, allowedHTMLTags: string[] return cleanText.trim() === ""; }; -/** - * @description this function returns whether a comment is empty or not by checking for the following conditions- - * 1. If comment is undefined - * 2. If comment is an empty string - * 3. If comment is "

" - * @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/packages/types/src/editor/editor-content.ts b/packages/types/src/editor/editor-content.ts new file mode 100644 index 00000000000..ce9dadec85f --- /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]: any; + }[]; + text?: string; + [key: string]: any; +}; + +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/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 ( From 24fb0d9fbb754644441b4ec3934bd366e2c8b6da Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 28 Aug 2025 13:52:55 +0530 Subject: [PATCH 04/10] fix: props name --- apps/web/ce/hooks/pages/use-extended-editor-extensions.ts | 2 +- apps/web/core/components/pages/editor/page-root.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts b/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts index eef355b96ad..bd37c24e341 100644 --- a/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts +++ b/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts @@ -15,4 +15,4 @@ export type TExtendedEditorExtensionsHookParams = { export type TExtendedEditorExtensionsConfig = IEditorPropsExtended; -export const useExtendedEditorExtensions = (params: TExtendedEditorExtensionsHookParams) => null; +export const useExtendedEditorProps = (params: TExtendedEditorExtensionsHookParams) => null; diff --git a/apps/web/core/components/pages/editor/page-root.tsx b/apps/web/core/components/pages/editor/page-root.tsx index 6ff3f7d7996..dce58b0d879 100644 --- a/apps/web/core/components/pages/editor/page-root.tsx +++ b/apps/web/core/components/pages/editor/page-root.tsx @@ -9,7 +9,7 @@ import { usePageFallback } from "@/hooks/use-page-fallback"; import { useQueryParams } from "@/hooks/use-query-params"; // plane web import import { PageModals } from "@/plane-web/components/pages"; -import { usePagesPaneExtensions, useExtendedEditorExtensions } from "@/plane-web/hooks/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"; @@ -92,7 +92,7 @@ export const PageRoot = observer((props: TPageRootProps) => { }); // Get extended editor extensions configuration - const extendedEditorProps = useExtendedEditorExtensions({ + const extendedEditorProps = useExtendedEditorProps({ workspaceSlug, page, storeType, From aaf42a3a37de41e14c16393f2f86d88023233c17 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 28 Aug 2025 18:16:59 +0530 Subject: [PATCH 05/10] fix: review changes --- .../core/components/editor/lite-text-editor.tsx | 3 ++- .../core/components/editor/rich-text-editor.tsx | 3 ++- .../pages/(detail)/[pageId]/page.tsx | 6 +++--- .../pages/use-extended-editor-extensions.ts | 2 +- .../ce/hooks/pages/use-pages-pane-extensions.ts | 5 ++--- apps/web/ce/hooks/use-editor-flagging.ts | 10 ++++++---- apps/web/ce/types/pages/pane-extensions.ts | 17 +++++++++-------- .../core/components/editor/document/editor.tsx | 7 +++++-- .../core/components/editor/lite-text/editor.tsx | 7 +++++-- .../core/components/editor/rich-text/editor.tsx | 7 +++++-- .../components/editor/sticky-editor/editor.tsx | 7 +++++-- .../components/pages/editor/editor-body.tsx | 5 ++++- .../core/components/pages/editor/page-root.tsx | 11 +++++++---- .../src/ce/components/editor-side-effects.ts | 9 ++++++--- packages/editor/src/ce/types/editor-extended.ts | 2 +- .../editors/document/collaborative-editor.tsx | 4 ++-- packages/editor/src/core/types/editor.ts | 2 +- 17 files changed, 66 insertions(+), 41 deletions(-) diff --git a/apps/space/core/components/editor/lite-text-editor.tsx b/apps/space/core/components/editor/lite-text-editor.tsx index f22e8c0c40e..a9ff57a8249 100644 --- a/apps/space/core/components/editor/lite-text-editor.tsx +++ b/apps/space/core/components/editor/lite-text-editor.tsx @@ -11,7 +11,7 @@ import { EditorMentionsRoot } from "./embeds/mentions"; import { IssueCommentToolbar } from "./toolbar"; type LiteTextEditorWrapperProps = MakeOptional< - Omit, + Omit, "disabledExtensions" | "flaggedExtensions" > & { anchor: string; @@ -62,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 { ); - if (!page) return null; + if (!page || !workspaceSlug.toString() || !projectId.toString()) return null; return ( <> @@ -189,8 +189,8 @@ const PageDetailsPage = observer(() => { storeType={storeType} page={page} webhookConnectionParams={webhookConnectionParams} - workspaceSlug={workspaceSlug?.toString() ?? ""} - projectId={projectId?.toString() ?? ""} + workspaceSlug={workspaceSlug.toString()} + projectId={projectId?.toString()} /> diff --git a/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts b/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts index bd37c24e341..28c07175978 100644 --- a/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts +++ b/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts @@ -15,4 +15,4 @@ export type TExtendedEditorExtensionsHookParams = { export type TExtendedEditorExtensionsConfig = IEditorPropsExtended; -export const useExtendedEditorProps = (params: TExtendedEditorExtensionsHookParams) => null; +export const useExtendedEditorProps = (_params: TExtendedEditorExtensionsHookParams) => null; diff --git a/apps/web/ce/hooks/pages/use-pages-pane-extensions.ts b/apps/web/ce/hooks/pages/use-pages-pane-extensions.ts index ace3f7e91cc..cd405c1ca4e 100644 --- a/apps/web/ce/hooks/pages/use-pages-pane-extensions.ts +++ b/apps/web/ce/hooks/pages/use-pages-pane-extensions.ts @@ -16,7 +16,7 @@ export type TPageExtensionHookParams = { editorRef: RefObject; }; -export const usePagesPaneExtensions = (params: TPageExtensionHookParams) => { +export const usePagesPaneExtensions = (_params: TPageExtensionHookParams) => { const router = useAppRouter(); const { updateQueryParams } = useQueryParams(); const searchParams = useSearchParams(); @@ -27,8 +27,7 @@ export const usePagesPaneExtensions = (params: TPageExtensionHookParams) => { ) as TPageNavigationPaneTab | null; const isNavigationPaneOpen = - (!!navigationPaneQueryParam && PAGE_NAVIGATION_PANE_TAB_KEYS.includes(navigationPaneQueryParam)) || - searchParams.get("paneTab") === "comments"; + !!navigationPaneQueryParam && PAGE_NAVIGATION_PANE_TAB_KEYS.includes(navigationPaneQueryParam); const handleOpenNavigationPane = useCallback(() => { const updatedRoute = updateQueryParams({ diff --git a/apps/web/ce/hooks/use-editor-flagging.ts b/apps/web/ce/hooks/use-editor-flagging.ts index 720e43b7717..0fc8a6eb4c6 100644 --- a/apps/web/ce/hooks/use-editor-flagging.ts +++ b/apps/web/ce/hooks/use-editor-flagging.ts @@ -17,13 +17,15 @@ export type TEditorFlaggingHookReturnType = { }; }; +export type TEditorFlaggingHookProps = { + workspaceSlug: string; + storeType?: EPageStoreType; +}; + /** * @description extensions disabled in various editors */ -export const useEditorFlagging = ( - workspaceSlug: string, - storeType?: EPageStoreType -): 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 index fb46c00f9c6..0f64b301907 100644 --- a/apps/web/ce/types/pages/pane-extensions.ts +++ b/apps/web/ce/types/pages/pane-extensions.ts @@ -1,15 +1,16 @@ -import { type INavigationPaneExtension as ICoreNavigationPaneExtension } from "@/components/pages/navigation-pane"; +import { + type INavigationPaneExtension as ICoreNavigationPaneExtension, + type INavigationPaneExtensionComponent, +} from "@/components/pages/navigation-pane"; -// EE Union of all possible navigation pane extension data types -export type TNavigationPaneExtensionData = unknown; +// EE Union/map of extension data types (keyed by extension id) +export type TNavigationPaneExtensionData = Record; -// EE Navigation pane extension configuration with comment support +// EE Navigation pane extension configuration export interface INavigationPaneExtension< T extends keyof TNavigationPaneExtensionData = keyof TNavigationPaneExtensionData, -> extends ICoreNavigationPaneExtension { +> extends Omit, "id" | "data" | "component"> { id: T; - triggerParam: string; - component: any; + component: INavigationPaneExtensionComponent; data?: TNavigationPaneExtensionData[T]; - width?: number; } 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 3cb6c736c63..54db447a7b2 100644 --- a/apps/web/core/components/pages/editor/editor-body.tsx +++ b/apps/web/core/components/pages/editor/editor-body.tsx @@ -106,7 +106,10 @@ export const PageEditorBody: React.FC = observer((props) => { searchEntity: handlers.fetchEntity, }); // editor flaggings - const { document: documentEditorExtensions } = useEditorFlagging(workspaceSlug, storeType); + const { document: documentEditorExtensions } = useEditorFlagging({ + workspaceSlug: workspaceSlug?.toString() ?? "", + storeType: storeType ?? "", + }); // page filters const { fontSize, fontStyle, isFullWidth } = usePageFilters(); // translation diff --git a/apps/web/core/components/pages/editor/page-root.tsx b/apps/web/core/components/pages/editor/page-root.tsx index dce58b0d879..eab07e7c8c8 100644 --- a/apps/web/core/components/pages/editor/page-root.tsx +++ b/apps/web/core/components/pages/editor/page-root.tsx @@ -102,10 +102,13 @@ export const PageRoot = observer((props: TPageRootProps) => { projectId, }); - const handleRestoreVersion = async (descriptionHTML: string) => { - editorRef.current?.clearEditor(); - editorRef.current?.setEditorValue(descriptionHTML); - }; + const handleRestoreVersion = useCallback( + async (descriptionHTML: string) => { + editorRef.current?.clearEditor(); + editorRef.current?.setEditorValue(descriptionHTML); + }, + [editorRef] + ); // reset editor ref on unmount useEffect( diff --git a/packages/editor/src/ce/components/editor-side-effects.ts b/packages/editor/src/ce/components/editor-side-effects.ts index 9aa5a56bab2..36a180a12ee 100644 --- a/packages/editor/src/ce/components/editor-side-effects.ts +++ b/packages/editor/src/ce/components/editor-side-effects.ts @@ -1,9 +1,12 @@ import { type Editor } from "@tiptap/core"; +import type { ReactElement } from "react"; import type { IEditorPropsExtended } from "@/types"; -export const EditorSideEffects = (props: { +export type DocumentEditorSideEffectsProps = { editor: Editor; id: string; - updatePageProperties?: any; + updatePageProperties?: unknown; extendedEditorProps?: IEditorPropsExtended; -}) => null; +}; + +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 index 97ddab994d5..98f9c7ece87 100644 --- a/packages/editor/src/ce/types/editor-extended.ts +++ b/packages/editor/src/ce/types/editor-extended.ts @@ -2,7 +2,7 @@ export type IEditorExtensionOptions = unknown; export type IEditorPropsExtended = unknown; -export type TExtendedEditorCommands = ""; +export type TExtendedEditorCommands = never; export type TExtendedCommandExtraProps = unknown; 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 2e085938037..749ed1d4fd3 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx @@ -13,7 +13,7 @@ import { getEditorClassNames } from "@/helpers/common"; // hooks import { useCollaborativeEditor } from "@/hooks/use-collaborative-editor"; // constants -import { EditorSideEffects } from "@/plane-editor/components/editor-side-effects"; +import { DocumentEditorSideEffects } from "@/plane-editor/components/editor-side-effects"; // types import type { EditorRefApi, ICollaborativeDocumentEditorProps } from "@/types"; @@ -101,7 +101,7 @@ const CollaborativeDocumentEditor: React.FC = return ( <> - + string); tabIndex?: number; value?: string | null; - extendedEditorProps?: IEditorPropsExtended; + extendedEditorProps: IEditorPropsExtended; }; export type ILiteTextEditorProps = IEditorProps; From 920c6ce4fb614affd092112bd46df4314808af73 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 28 Aug 2025 18:19:48 +0530 Subject: [PATCH 06/10] fix: extra checks --- .../(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 42c0f2180cd..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 @@ -176,7 +176,7 @@ const PageDetailsPage = observer(() => { ); - if (!page || !workspaceSlug.toString() || !projectId.toString()) return null; + if (!page || !workspaceSlug || !projectId) return null; return ( <> From b7fc21a1da7ab0f98f60f67f27a22d15d850f1ed Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 28 Aug 2025 18:29:55 +0530 Subject: [PATCH 07/10] fix: type changes --- apps/web/ce/hooks/pages/use-extended-editor-extensions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts b/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts index 28c07175978..028e16c8895 100644 --- a/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts +++ b/apps/web/ce/hooks/pages/use-extended-editor-extensions.ts @@ -15,4 +15,6 @@ export type TExtendedEditorExtensionsHookParams = { export type TExtendedEditorExtensionsConfig = IEditorPropsExtended; -export const useExtendedEditorProps = (_params: TExtendedEditorExtensionsHookParams) => null; +export const useExtendedEditorProps = ( + _params: TExtendedEditorExtensionsHookParams +): TExtendedEditorExtensionsConfig => ({}); From 539c7619b56713d4babd8380f62dcbc96ea8ced1 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 28 Aug 2025 18:52:59 +0530 Subject: [PATCH 08/10] fix: remove editor ref current --- apps/web/ce/components/pages/modals/modals.tsx | 2 -- apps/web/core/components/pages/editor/editor-body.tsx | 6 +++--- apps/web/core/components/pages/editor/page-root.tsx | 2 +- apps/web/core/components/pages/navigation-pane/root.tsx | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/web/ce/components/pages/modals/modals.tsx b/apps/web/ce/components/pages/modals/modals.tsx index e920772500b..780dc853136 100644 --- a/apps/web/ce/components/pages/modals/modals.tsx +++ b/apps/web/ce/components/pages/modals/modals.tsx @@ -2,7 +2,6 @@ import React from "react"; import { observer } from "mobx-react"; -import type { EditorRefApi } from "@plane/editor"; // components import { EPageStoreType } from "@/plane-web/hooks/store"; // store @@ -11,7 +10,6 @@ import { TPageInstance } from "@/store/pages/base-page"; export type TPageModalsProps = { page: TPageInstance; storeType: EPageStoreType; - editorRef?: EditorRefApi | null; }; export const PageModals: React.FC = observer((props) => null); diff --git a/apps/web/core/components/pages/editor/editor-body.tsx b/apps/web/core/components/pages/editor/editor-body.tsx index 54db447a7b2..3ada9bf3ca5 100644 --- a/apps/web/core/components/pages/editor/editor-body.tsx +++ b/apps/web/core/components/pages/editor/editor-body.tsx @@ -107,8 +107,8 @@ export const PageEditorBody: React.FC = observer((props) => { }); // editor flaggings const { document: documentEditorExtensions } = useEditorFlagging({ - workspaceSlug: workspaceSlug?.toString() ?? "", - storeType: storeType ?? "", + workspaceSlug, + storeType, }); // page filters const { fontSize, fontStyle, isFullWidth } = usePageFilters(); @@ -131,7 +131,7 @@ export const PageEditorBody: React.FC = observer((props) => { isOpen={isOpen} onClose={onClose} workspaceId={workspaceId} - workspaceSlug={workspaceSlug?.toString() ?? ""} + workspaceSlug={workspaceSlug} /> ), [editorRef, workspaceId, workspaceSlug] diff --git a/apps/web/core/components/pages/editor/page-root.tsx b/apps/web/core/components/pages/editor/page-root.tsx index eab07e7c8c8..3159691e136 100644 --- a/apps/web/core/components/pages/editor/page-root.tsx +++ b/apps/web/core/components/pages/editor/page-root.tsx @@ -169,7 +169,7 @@ export const PageRoot = observer((props: TPageRootProps) => { }} extensions={navigationPaneExtensions} /> - + ); }); diff --git a/apps/web/core/components/pages/navigation-pane/root.tsx b/apps/web/core/components/pages/navigation-pane/root.tsx index bfa8a70aada..d9de5f977d1 100644 --- a/apps/web/core/components/pages/navigation-pane/root.tsx +++ b/apps/web/core/components/pages/navigation-pane/root.tsx @@ -53,7 +53,7 @@ export const PageNavigationPaneRoot: React.FC = observer((props) => { // Check if any extension is currently active based on query parameters const ActiveExtension = extensions.find((extension) => { - const paneTabValue = searchParams.get("paneTab"); + 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 From f37b4fe3966f9a97c5d6ff318fbb595c0cb6b188 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Thu, 28 Aug 2025 19:00:52 +0530 Subject: [PATCH 09/10] fix: renaming --- .../{editor-side-effects.ts => document-editor-side-effects.ts} | 0 .../core/components/editors/document/collaborative-editor.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/editor/src/ce/components/{editor-side-effects.ts => document-editor-side-effects.ts} (100%) diff --git a/packages/editor/src/ce/components/editor-side-effects.ts b/packages/editor/src/ce/components/document-editor-side-effects.ts similarity index 100% rename from packages/editor/src/ce/components/editor-side-effects.ts rename to packages/editor/src/ce/components/document-editor-side-effects.ts 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 749ed1d4fd3..038184204e6 100644 --- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx +++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx @@ -13,7 +13,7 @@ import { getEditorClassNames } from "@/helpers/common"; // hooks import { useCollaborativeEditor } from "@/hooks/use-collaborative-editor"; // constants -import { DocumentEditorSideEffects } from "@/plane-editor/components/editor-side-effects"; +import { DocumentEditorSideEffects } from "@/plane-editor/components/document-editor-side-effects"; // types import type { EditorRefApi, ICollaborativeDocumentEditorProps } from "@/types"; From 84fddfb8ad3fd1960576bcb4eb3b13762d61c43c Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Mon, 1 Sep 2025 21:05:13 +0530 Subject: [PATCH 10/10] fix: link check --- packages/types/src/editor/editor-content.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/types/src/editor/editor-content.ts b/packages/types/src/editor/editor-content.ts index ce9dadec85f..bb720c2e97a 100644 --- a/packages/types/src/editor/editor-content.ts +++ b/packages/types/src/editor/editor-content.ts @@ -4,15 +4,15 @@ export type JSONContent = { type?: string; - attrs?: Record; + attrs?: Record; content?: JSONContent[]; marks?: { type: string; - attrs?: Record; - [key: string]: any; + attrs?: Record; + [key: string]: unknown; }[]; text?: string; - [key: string]: any; + [key: string]: unknown; }; export type HTMLContent = string;