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

This commit is contained in:
upstream-mirror
2026-09-15 15:14:58 +05:00
commit 23059a1de3
5252 changed files with 573834 additions and 0 deletions

View 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() ?? ""}
/>
);
});

View 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";

View 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>
);
});

View 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>
);
});