snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
This commit is contained in:
107
apps/web/core/components/pages/version/editor.tsx
Normal file
107
apps/web/core/components/pages/version/editor.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import type { TDisplayConfig } from "@plane/editor";
|
||||
import type { JSONContent, TPageVersion } from "@plane/types";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { isJSONContentEmpty } from "@plane/utils";
|
||||
// components
|
||||
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 type { EPageStoreType } from "@/hooks/store";
|
||||
|
||||
export type TVersionEditorProps = {
|
||||
activeVersion: string | null;
|
||||
versionDetails: TPageVersion | undefined;
|
||||
storeType: EPageStoreType;
|
||||
};
|
||||
|
||||
export const PagesVersionEditor = observer(function PagesVersionEditor(props: TVersionEditorProps) {
|
||||
const { activeVersion, versionDetails } = props;
|
||||
// params
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store hooks
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
// derived values
|
||||
const workspaceDetails = getWorkspaceBySlug(workspaceSlug?.toString() ?? "");
|
||||
// page filters
|
||||
const { fontSize, fontStyle } = usePageFilters();
|
||||
|
||||
const displayConfig: TDisplayConfig = {
|
||||
fontSize,
|
||||
fontStyle,
|
||||
wideLayout: true,
|
||||
};
|
||||
|
||||
if (!versionDetails)
|
||||
return (
|
||||
<div className="size-full px-5">
|
||||
<Loader className="relative space-y-4">
|
||||
<Loader.Item width="50%" height="36px" />
|
||||
<div className="space-y-2">
|
||||
<div className="py-2">
|
||||
<Loader.Item width="100%" height="36px" />
|
||||
</div>
|
||||
<Loader.Item width="80%" height="22px" />
|
||||
<div className="relative flex items-center gap-2">
|
||||
<Loader.Item width="30px" height="30px" />
|
||||
<Loader.Item width="30%" height="22px" />
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<Loader.Item width="60%" height="36px" />
|
||||
</div>
|
||||
<Loader.Item width="70%" height="22px" />
|
||||
<Loader.Item width="30%" height="22px" />
|
||||
<div className="relative flex items-center gap-2">
|
||||
<Loader.Item width="30px" height="30px" />
|
||||
<Loader.Item width="30%" height="22px" />
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<Loader.Item width="50%" height="30px" />
|
||||
</div>
|
||||
<Loader.Item width="100%" height="22px" />
|
||||
<div className="py-2">
|
||||
<Loader.Item width="30%" height="30px" />
|
||||
</div>
|
||||
<Loader.Item width="30%" height="22px" />
|
||||
<div className="relative flex items-center gap-2">
|
||||
<div className="py-2">
|
||||
<Loader.Item width="30px" height="30px" />
|
||||
</div>
|
||||
<Loader.Item width="30%" height="22px" />
|
||||
</div>
|
||||
</div>
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
|
||||
const description = isJSONContentEmpty(versionDetails?.description_json as JSONContent)
|
||||
? versionDetails?.description_html
|
||||
: versionDetails?.description_json;
|
||||
|
||||
if (!description) return null;
|
||||
|
||||
return (
|
||||
<DocumentEditor
|
||||
key={activeVersion ?? ""}
|
||||
editable={false}
|
||||
id={activeVersion ?? ""}
|
||||
value={description}
|
||||
containerClassName="p-0 pb-64 border-none"
|
||||
displayConfig={displayConfig}
|
||||
editorClassName="pl-10"
|
||||
projectId={projectId?.toString()}
|
||||
workspaceId={workspaceDetails?.id ?? ""}
|
||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||
/>
|
||||
);
|
||||
});
|
||||
7
apps/web/core/components/pages/version/index.ts
Normal file
7
apps/web/core/components/pages/version/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export * from "./root";
|
||||
128
apps/web/core/components/pages/version/main-content.tsx
Normal file
128
apps/web/core/components/pages/version/main-content.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { ShowOutline, WarningTriangleOutline } from "@makeplane/propel/icons";
|
||||
// plane imports
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { TPageVersion } from "@plane/types";
|
||||
import { renderFormattedDate, renderFormattedTime } from "@plane/utils";
|
||||
// helpers
|
||||
import type { EPageStoreType } from "@/hooks/store";
|
||||
// local imports
|
||||
import type { TVersionEditorProps } from "./editor";
|
||||
|
||||
type Props = {
|
||||
activeVersion: string | null;
|
||||
editorComponent: React.FC<TVersionEditorProps>;
|
||||
fetchVersionDetails: (pageId: string, versionId: string) => Promise<TPageVersion | undefined>;
|
||||
handleClose: () => void;
|
||||
handleRestore: (descriptionHTML: string) => Promise<void>;
|
||||
pageId: string;
|
||||
restoreEnabled: boolean;
|
||||
storeType: EPageStoreType;
|
||||
};
|
||||
|
||||
export const PageVersionsMainContent = observer(function PageVersionsMainContent(props: Props) {
|
||||
const {
|
||||
activeVersion,
|
||||
editorComponent,
|
||||
fetchVersionDetails,
|
||||
handleClose,
|
||||
handleRestore,
|
||||
pageId,
|
||||
restoreEnabled,
|
||||
storeType,
|
||||
} = props;
|
||||
// states
|
||||
const [isRestoring, setIsRestoring] = useState(false);
|
||||
const [isRetrying, setIsRetrying] = useState(false);
|
||||
|
||||
const {
|
||||
data: versionDetails,
|
||||
error: versionDetailsError,
|
||||
mutate: mutateVersionDetails,
|
||||
} = useSWR(
|
||||
pageId && activeVersion ? `PAGE_VERSION_${activeVersion}` : null,
|
||||
pageId && activeVersion ? () => fetchVersionDetails(pageId, activeVersion) : null
|
||||
);
|
||||
|
||||
const handleRestoreVersion = async () => {
|
||||
if (!restoreEnabled) return;
|
||||
setIsRestoring(true);
|
||||
await handleRestore(versionDetails?.description_html ?? "<p></p>")
|
||||
.then(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Page version restored.",
|
||||
});
|
||||
handleClose();
|
||||
})
|
||||
.catch(() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Failed to restore page version.",
|
||||
})
|
||||
)
|
||||
.finally(() => setIsRestoring(false));
|
||||
};
|
||||
|
||||
const handleRetry = async () => {
|
||||
setIsRetrying(true);
|
||||
await mutateVersionDetails();
|
||||
setIsRetrying(false);
|
||||
};
|
||||
|
||||
const VersionEditor = editorComponent;
|
||||
|
||||
return (
|
||||
<div className="flex flex-grow flex-col overflow-hidden">
|
||||
{versionDetailsError ? (
|
||||
<div className="grid flex-grow place-items-center">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<span className="grid size-11 flex-shrink-0 place-items-center text-tertiary">
|
||||
<WarningTriangleOutline className="size-10" />
|
||||
</span>
|
||||
<div>
|
||||
<h6 className="text-16 font-semibold">Something went wrong!</h6>
|
||||
<p className="text-13 text-tertiary">The version could not be loaded, please try again.</p>
|
||||
</div>
|
||||
<Button variant="link" onClick={handleRetry} loading={isRetrying}>
|
||||
Try again
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex min-h-14 items-center justify-between gap-2 border-b border-subtle px-5 py-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<h6 className="text-14 font-medium">
|
||||
{versionDetails
|
||||
? `${renderFormattedDate(versionDetails.last_saved_at)} ${renderFormattedTime(versionDetails.last_saved_at)}`
|
||||
: "Loading version details"}
|
||||
</h6>
|
||||
<span className="flex flex-shrink-0 items-center gap-1 rounded-sm bg-accent-primary/20 px-1.5 py-1 text-11 font-medium text-accent-primary">
|
||||
<ShowOutline className="size-3 flex-shrink-0" />
|
||||
View only
|
||||
</span>
|
||||
</div>
|
||||
{restoreEnabled && (
|
||||
<Button variant="primary" className="flex-shrink-0" onClick={handleRestoreVersion} loading={isRestoring}>
|
||||
{isRestoring ? "Restoring" : "Restore"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="vertical-scrollbar scrollbar-sm h-full overflow-y-scroll pt-8">
|
||||
<VersionEditor activeVersion={activeVersion} storeType={storeType} versionDetails={versionDetails} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
73
apps/web/core/components/pages/version/root.tsx
Normal file
73
apps/web/core/components/pages/version/root.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
// plane imports
|
||||
import type { TPageVersion } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useQueryParams } from "@/hooks/use-query-params";
|
||||
// plane web imports
|
||||
import type { EPageStoreType } from "@/hooks/store";
|
||||
// local imports
|
||||
import { PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM, PAGE_NAVIGATION_PANE_WIDTH } from "../navigation-pane";
|
||||
import type { TVersionEditorProps } from "./editor";
|
||||
import { PageVersionsMainContent } from "./main-content";
|
||||
|
||||
type Props = {
|
||||
editorComponent: React.FC<TVersionEditorProps>;
|
||||
fetchVersionDetails: (pageId: string, versionId: string) => Promise<TPageVersion | undefined>;
|
||||
handleRestore: (descriptionHTML: string) => Promise<void>;
|
||||
pageId: string;
|
||||
restoreEnabled: boolean;
|
||||
storeType: EPageStoreType;
|
||||
};
|
||||
|
||||
export const PageVersionsOverlay = observer(function PageVersionsOverlay(props: Props) {
|
||||
const { editorComponent, fetchVersionDetails, handleRestore, pageId, restoreEnabled, storeType } = props;
|
||||
// navigation
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
// query params
|
||||
const { updateQueryParams } = useQueryParams();
|
||||
// derived values
|
||||
const activeVersion = searchParams.get(PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM);
|
||||
const isOpen = !!activeVersion;
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
const updatedRoute = updateQueryParams({
|
||||
paramsToRemove: [PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM],
|
||||
});
|
||||
router.push(updatedRoute);
|
||||
}, [router, updateQueryParams]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-0 z-[16] flex h-full overflow-hidden bg-surface-1 opacity-0 transition-opacity",
|
||||
{
|
||||
"pointer-events-auto opacity-100": isOpen,
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
width: `calc(100% - ${PAGE_NAVIGATION_PANE_WIDTH}px)`,
|
||||
}}
|
||||
>
|
||||
<PageVersionsMainContent
|
||||
activeVersion={activeVersion}
|
||||
editorComponent={editorComponent}
|
||||
fetchVersionDetails={fetchVersionDetails}
|
||||
handleClose={handleClose}
|
||||
handleRestore={handleRestore}
|
||||
pageId={pageId}
|
||||
restoreEnabled={restoreEnabled}
|
||||
storeType={storeType}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user