plane/web/components/pages/editor/header/extra-options.tsx
Aaryan Khandelwal 8454e4f1e0
[WEB-986] fix: editor slash command positioning (#4186)
* fix: table selected cell border

* chore: add syncing message when revalidating page data

* fix: slash command positioning

* fix: mentions list dropdown positioning
2024-04-15 19:45:03 +05:30

107 lines
3.8 KiB
TypeScript

import { useState } from "react";
import { observer } from "mobx-react";
import { Lock, RefreshCw, Sparkle } from "lucide-react";
// editor
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/document-editor";
// ui
import { ArchiveIcon } from "@plane/ui";
// components
import { GptAssistantPopover } from "@/components/core";
import { PageInfoPopover, PageOptionsDropdown } from "@/components/pages";
// helpers
import { cn } from "@/helpers/common.helper";
import { renderFormattedDate } from "@/helpers/date-time.helper";
// hooks
import { useApplication } from "@/hooks/store";
// store
import { IPageStore } from "@/store/pages/page.store";
type Props = {
editorRef: React.RefObject<EditorRefApi>;
handleDuplicatePage: () => void;
isSyncing: boolean;
pageStore: IPageStore;
projectId: string;
readOnlyEditorRef: React.RefObject<EditorReadOnlyRefApi>;
};
export const PageExtraOptions: React.FC<Props> = observer((props) => {
const { editorRef, handleDuplicatePage, isSyncing, pageStore, projectId, readOnlyEditorRef } = props;
// states
const [gptModalOpen, setGptModal] = useState(false);
// store hooks
const {
config: { envConfig },
} = useApplication();
// derived values
const { archived_at, isContentEditable, isSubmitting, is_locked } = pageStore;
const handleAiAssistance = async (response: string) => {
if (!editorRef) return;
editorRef.current?.setEditorValueAtCursorPosition(response);
};
return (
<div className="flex flex-grow items-center justify-end gap-3">
{isContentEditable && (
<div
className={cn("fade-in flex items-center gap-x-2 transition-all duration-300", {
"fade-out": isSubmitting === "saved",
})}
>
{isSubmitting === "submitting" && <RefreshCw className="h-4 w-4 stroke-custom-text-300" />}
<span className="text-sm text-custom-text-300">{isSubmitting === "submitting" ? "Saving..." : "Saved"}</span>
</div>
)}
{isSyncing && (
<div className="flex items-center gap-x-2">
<RefreshCw className="h-4 w-4 stroke-custom-text-300" />
<span className="text-sm text-custom-text-300">Syncing...</span>
</div>
)}
{is_locked && (
<div className="flex h-7 items-center gap-2 rounded-full bg-custom-background-80 px-3 py-0.5 text-xs font-medium text-custom-text-300">
<Lock className="h-3 w-3" />
<span>Locked</span>
</div>
)}
{archived_at && (
<div className="flex h-7 items-center gap-2 rounded-full bg-blue-500/20 px-3 py-0.5 text-xs font-medium text-blue-500">
<ArchiveIcon className="h-3 w-3" />
<span>Archived at {renderFormattedDate(archived_at)}</span>
</div>
)}
{isContentEditable && envConfig?.has_openai_configured && (
<GptAssistantPopover
isOpen={gptModalOpen}
projectId={projectId}
handleClose={() => {
setGptModal((prevData) => !prevData);
// this is done so that the title do not reset after gpt popover closed
// reset(getValues());
}}
onResponse={handleAiAssistance}
placement="top-end"
button={
<button
type="button"
className="flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90"
onClick={() => setGptModal((prevData) => !prevData)}
>
<Sparkle className="h-4 w-4" />
AI
</button>
}
className="!min-w-[38rem]"
/>
)}
<PageInfoPopover pageStore={pageStore} />
<PageOptionsDropdown
editorRef={isContentEditable ? editorRef.current : readOnlyEditorRef.current}
handleDuplicatePage={handleDuplicatePage}
pageStore={pageStore}
/>
</div>
);
});