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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ const PageDetailsPage = observer(() => {
config={pageRootConfig}
handlers={pageRootHandlers}
page={page}
storeType={EPageStoreType.PROJECT}
webhookConnectionParams={webhookConnectionParams}
workspaceSlug={workspaceSlug?.toString() ?? ""}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { ICustomSearchSelectOption } from "@plane/types";
import { Breadcrumbs, Header, CustomSearchSelect } from "@plane/ui";
// components
import { BreadcrumbLink, PageAccessIcon, SwitcherLabel } from "@/components/common";
import { PageEditInformationPopover } from "@/components/pages";
import { PageHeaderActions } from "@/components/pages/header/actions";
// helpers
// hooks
import { getPageName } from "@/helpers/page.helper";
// hooks
import { useProject } from "@/hooks/store";
// plane web components
import { useAppRouter } from "@/hooks/use-app-router";
Expand All @@ -24,21 +24,22 @@ export interface IPagesHeaderProps {
showButton?: boolean;
}

const storeType = EPageStoreType.PROJECT;

export const PageDetailsHeader = observer(() => {
// router
const router = useAppRouter();
const { workspaceSlug, pageId, projectId } = useParams();
// store hooks
const { currentProjectDetails, loader } = useProject();
const { getPageById, getCurrentProjectPageIds } = usePageStore(storeType);
const page = usePage({
pageId: pageId?.toString() ?? "",
storeType: EPageStoreType.PROJECT,
storeType,
});
const { getPageById, getCurrentProjectPageIds } = usePageStore(EPageStoreType.PROJECT);
// derived values
const projectPageIds = getCurrentProjectPageIds(projectId?.toString());

if (!page) return null;
const switcherOptions = projectPageIds
.map((id) => {
const _page = id === pageId ? page : getPageById(id);
Expand Down Expand Up @@ -109,8 +110,8 @@ export const PageDetailsHeader = observer(() => {
</div>
</Header.LeftItem>
<Header.RightItem>
<PageEditInformationPopover page={page} />
<PageDetailsHeaderExtraActions page={page} />
<PageHeaderActions page={page} storeType={storeType} />
</Header.RightItem>
</Header>
);
Expand Down
12 changes: 6 additions & 6 deletions web/ce/components/pages/editor/ai/menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import React, { RefObject, useEffect, useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { ChevronRight, CornerDownRight, LucideIcon, RefreshCcw, Sparkles, TriangleAlert } from "lucide-react";
// plane editor
import { EditorRefApi } from "@plane/editor";
Expand All @@ -18,7 +18,7 @@ import { AskPiMenu } from "./ask-pi-menu";
const aiService = new AIService();

type Props = {
editorRef: RefObject<EditorRefApi>;
editorRef: EditorRefApi | null;
isOpen: boolean;
onClose: () => void;
workspaceId: string;
Expand Down Expand Up @@ -73,7 +73,7 @@ export const EditorAIMenu: React.FC<Props> = (props) => {
};
// handle task click
const handleClick = async (key: AI_EDITOR_TASKS) => {
const selection = editorRef.current?.getSelectedText();
const selection = editorRef?.getSelectedText();
if (!selection || activeTask === key) return;
setActiveTask(key);
if (key === AI_EDITOR_TASKS.ASK_ANYTHING) return;
Expand All @@ -86,7 +86,7 @@ export const EditorAIMenu: React.FC<Props> = (props) => {
};
// handle re-generate response
const handleRegenerate = async () => {
const selection = editorRef.current?.getSelectedText();
const selection = editorRef?.getSelectedText();
if (!selection || !activeTask) return;
setIsRegenerating(true);
await handleGenerateResponse({
Expand All @@ -104,7 +104,7 @@ export const EditorAIMenu: React.FC<Props> = (props) => {
// handle re-generate response
const handleToneChange = async (key: string) => {
const selectedTone = TONES_LIST.find((t) => t.key === key);
const selection = editorRef.current?.getSelectedText();
const selection = editorRef?.getSelectedText();
if (!selectedTone || !selection || !activeTask) return;
setResponse(undefined);
setIsRegenerating(false);
Expand All @@ -123,7 +123,7 @@ export const EditorAIMenu: React.FC<Props> = (props) => {
// handle replace selected text with the response
const handleInsertText = (insertOnNextLine: boolean) => {
if (!response) return;
editorRef.current?.insertText(response, insertOnNextLine);
editorRef?.insertText(response, insertOnNextLine);
onClose();
};

Expand Down
10 changes: 10 additions & 0 deletions web/ce/components/pages/header/collaborators-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use client";

// store
import { TPageInstance } from "@/store/pages/base-page";

export type TPageCollaboratorsListProps = {
page: TPageInstance;
};

export const PageCollaboratorsList = ({}: TPageCollaboratorsListProps) => null;
116 changes: 116 additions & 0 deletions web/ce/components/pages/header/lock-control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"use client";

import { useState, useEffect, useRef } from "react";
import { observer } from "mobx-react";
import { LockKeyhole, LockKeyholeOpen } from "lucide-react";
// plane imports
import { Tooltip } from "@plane/ui";
// hooks
import { usePageOperations } from "@/hooks/use-page-operations";
// store
import { TPageInstance } from "@/store/pages/base-page";

// Define our lock display states, renaming "icon-only" to "neutral"
type LockDisplayState = "neutral" | "locked" | "unlocked";

type Props = {
page: TPageInstance;
};

export const PageLockControl = observer(({ page }: Props) => {
// Initial state: if locked, then "locked", otherwise default to "neutral"
const [displayState, setDisplayState] = useState<LockDisplayState>(page.is_locked ? "locked" : "neutral");
// derived values
const { canCurrentUserLockPage, is_locked } = page;
// Ref for the transition timer
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Ref to store the previous value of isLocked for detecting transitions
const prevLockedRef = useRef(is_locked);
// page operations
const {
pageOperations: { toggleLock },
} = usePageOperations({
page,
});

// Cleanup any running timer on unmount
useEffect(
() => () => {
if (timerRef.current) clearTimeout(timerRef.current);
},
[]
);

// Update display state when isLocked changes
useEffect(() => {
// Clear any previous timer to avoid overlapping transitions
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}

// Transition logic:
// If locked, ensure the display state is "locked"
// If unlocked after being locked, show "unlocked" briefly then revert to "neutral"
if (is_locked) {
setDisplayState("locked");
} else if (prevLockedRef.current === true) {
setDisplayState("unlocked");
timerRef.current = setTimeout(() => {
setDisplayState("neutral");
timerRef.current = null;
}, 600);
} else {
setDisplayState("neutral");
}

// Update the previous locked state
prevLockedRef.current = is_locked;
}, [is_locked]);

if (!canCurrentUserLockPage) return null;

// Render different UI based on the current display state
return (
<>
{displayState === "neutral" && (
<Tooltip tooltipContent="Lock" position="bottom">
<button
type="button"
onClick={toggleLock}
className="flex-shrink-0 size-6 grid place-items-center rounded text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 transition-colors"
aria-label="Lock"
>
<LockKeyhole className="size-3.5" />
</button>
</Tooltip>
)}

{displayState === "locked" && (
<button
type="button"
onClick={toggleLock}
className="h-6 flex items-center gap-1 px-2 rounded text-custom-primary-100 bg-custom-primary-100/20 hover:bg-custom-primary-100/30 transition-colors"
aria-label="Locked"
>
<LockKeyhole className="flex-shrink-0 size-3.5 animate-lock-icon" />
<span className="text-xs font-medium whitespace-nowrap overflow-hidden transition-all duration-500 ease-out animate-text-slide-in">
Locked
</span>
</button>
)}

{displayState === "unlocked" && (
<div
className="h-6 flex items-center gap-1 px-2 rounded text-custom-text-200 animate-fade-out"
aria-label="Unlocked"
>
<LockKeyholeOpen className="flex-shrink-0 size-3.5 animate-unlock-icon" />
<span className="text-xs font-medium whitespace-nowrap overflow-hidden transition-all duration-500 ease-out animate-text-slide-in animate-text-fade-out">
Unlocked
</span>
</div>
)}
</>
);
});
10 changes: 10 additions & 0 deletions web/ce/components/pages/header/move-control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use client";

// store
import { TPageInstance } from "@/store/pages/base-page";

export type TPageMoveControlProps = {
page: TPageInstance;
};

export const PageMoveControl = ({}: TPageMoveControlProps) => null;
19 changes: 0 additions & 19 deletions web/core/components/pages/dropdowns/edit-information-popover.tsx

This file was deleted.

1 change: 0 additions & 1 deletion web/core/components/pages/dropdowns/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from "./actions";
export * from "./edit-information-popover";
14 changes: 7 additions & 7 deletions web/core/components/pages/editor/editor-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ export type TEditorBodyHandlers = {

type Props = {
config: TEditorBodyConfig;
editorRef: React.RefObject<EditorRefApi>;
editorReady: boolean;
editorForwardRef: React.RefObject<EditorRefApi>;
handleConnectionStatus: Dispatch<SetStateAction<boolean>>;
handleEditorReady: Dispatch<SetStateAction<boolean>>;
handleEditorReady: (status: boolean) => void;
handlers: TEditorBodyHandlers;
page: TPageInstance;
webhookConnectionParams: TWebhookConnectionQueryParams;
Expand All @@ -56,7 +56,7 @@ type Props = {
export const PageEditorBody: React.FC<Props> = observer((props) => {
const {
config,
editorRef,
editorForwardRef,
handleConnectionStatus,
handleEditorReady,
handlers,
Expand All @@ -70,7 +70,7 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
const { getUserDetails } = useMember();

// derived values
const { id: pageId, name: pageTitle, isContentEditable, updateTitle } = page;
const { id: pageId, name: pageTitle, isContentEditable, updateTitle, editorRef } = page;
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id ?? "";
// issue-embed
const { issueEmbedProps } = useIssueEmbed({
Expand Down Expand Up @@ -172,10 +172,10 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
<div className="sticky top-[72px]">
<div className="group/page-toc relative px-page-x">
<div className="cursor-pointer max-h-[50vh] overflow-hidden">
<PageContentBrowser editorRef={editorRef?.current} showOutline />
<PageContentBrowser editorRef={editorRef} showOutline />
</div>
<div className="absolute top-0 right-0 opacity-0 translate-x-1/2 pointer-events-none group-hover/page-toc:opacity-100 group-hover/page-toc:-translate-x-1/4 group-hover/page-toc:pointer-events-auto transition-all duration-300 w-52 max-h-[70vh] overflow-y-scroll vertical-scrollbar scrollbar-sm whitespace-nowrap bg-custom-background-90 p-4 rounded">
<PageContentBrowser editorRef={editorRef?.current} />
<PageContentBrowser editorRef={editorRef} />
</div>
</div>
</div>
Expand All @@ -196,7 +196,7 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
id={pageId}
fileHandler={config.fileHandler}
handleEditorReady={handleEditorReady}
ref={editorRef}
ref={editorForwardRef}
containerClassName="h-full p-0 pb-64"
displayConfig={displayConfig}
mentionHandler={{
Expand Down
2 changes: 1 addition & 1 deletion web/core/components/pages/editor/header/logo-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const PageEditorHeaderLogoPicker: React.FC<Props> = observer((props) => {

return (
<div
className={cn(className, "max-h-0 pointer-events-none transition-all ease-linear duration-200", {
className={cn(className, "max-h-0 pointer-events-none transition-all ease-linear duration-300", {
"max-h-[56px] pointer-events-auto": isLogoSelected,
})}
>
Expand Down
Loading