diff --git a/apps/web/components/eventtype/EventSetupTab.tsx b/apps/web/components/eventtype/EventSetupTab.tsx index 7a5a765ecd..ebc7b18653 100644 --- a/apps/web/components/eventtype/EventSetupTab.tsx +++ b/apps/web/components/eventtype/EventSetupTab.tsx @@ -1,5 +1,5 @@ import type { EventTypeSetupProps } from "pages/event-types/[type]"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import type { UseFormGetValues, UseFormSetValue, Control, FormState } from "react-hook-form"; import type { MultiValue } from "react-select"; @@ -12,45 +12,10 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { md } from "@calcom/lib/markdownIt"; import { slugify } from "@calcom/lib/slugify"; import turndown from "@calcom/lib/turndownService"; -import { - Label, - Select, - SettingsToggle, - Skeleton, - TextField, - Editor, - SkeletonContainer, - SkeletonText, -} from "@calcom/ui"; +import { Label, Select, SettingsToggle, Skeleton, TextField, Editor } from "@calcom/ui"; import Locations from "@components/eventtype/Locations"; -const DescriptionEditor = ({ isEditable }: { isEditable: boolean }) => { - const formMethods = useFormContext(); - const [mounted, setIsMounted] = useState(false); - const { t } = useLocale(); - const [firstRender, setFirstRender] = useState(true); - useEffect(() => { - setIsMounted(true); - }, []); - - return mounted ? ( - md.render(formMethods.getValues("description") || "")} - setText={(value: string) => formMethods.setValue("description", turndown(value), { shouldDirty: true })} - excludedToolbarItems={["blockType"]} - placeholder={t("quick_video_meeting")} - editable={isEditable} - firstRender={firstRender} - setFirstRender={setFirstRender} - /> - ) : ( - - - - ); -}; - export const EventSetupTab = ( props: Pick< EventTypeSetupProps, @@ -63,6 +28,7 @@ export const EventSetupTab = ( const [multipleDuration, setMultipleDuration] = useState( formMethods.getValues("metadata")?.multipleDuration ); + const [firstRender, setFirstRender] = useState(true); const orgBranding = useOrgBranding(); const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled"); @@ -111,7 +77,17 @@ export const EventSetupTab = ( {t("description")} {(isManagedEventType || isChildrenManagedEventType) && shouldLockIndicator("description")} - + md.render(formMethods.getValues("description") || "")} + setText={(value: string) => + formMethods.setValue("description", turndown(value), { shouldDirty: true }) + } + excludedToolbarItems={["blockType"]} + placeholder={t("quick_video_meeting")} + editable={!descriptionLockedProps.disabled} + firstRender={firstRender} + setFirstRender={setFirstRender} + /> { expect(screen.getByText("add_variable")).toBeInTheDocument(); }); - it("respects editable prop", () => { - render(); - const editor = screen.getByTestId("editor-input"); - // eslint-disable-next-line playwright/missing-playwright-await - expect(editor).toHaveAttribute("contenteditable", "false"); - }); - it("excludes toolbar items", () => { render(); expect(screen.queryByTitle("Bold")).not.toBeInTheDocument(); diff --git a/packages/ui/components/editor/Editor.tsx b/packages/ui/components/editor/Editor.tsx index c4a62ca09a..b0715c9bb7 100644 --- a/packages/ui/components/editor/Editor.tsx +++ b/packages/ui/components/editor/Editor.tsx @@ -20,6 +20,7 @@ import ExampleTheme from "./ExampleTheme"; import { VariableNode } from "./nodes/VariableNode"; import AddVariablesPlugin from "./plugins/AddVariablesPlugin"; import AutoLinkPlugin from "./plugins/AutoLinkPlugin"; +import EditablePlugin from "./plugins/EditablePlugin"; import ToolbarPlugin from "./plugins/ToolbarPlugin"; import "./stylesEditor.css"; @@ -70,7 +71,7 @@ export const Editor = (props: TextEditorProps) => { const editable = props.editable ?? true; return (
- +
{ { />
+ ); diff --git a/packages/ui/components/editor/plugins/EditablePlugin.test.tsx b/packages/ui/components/editor/plugins/EditablePlugin.test.tsx new file mode 100644 index 0000000000..06ba2effb5 --- /dev/null +++ b/packages/ui/components/editor/plugins/EditablePlugin.test.tsx @@ -0,0 +1,57 @@ +import { LexicalComposer } from "@lexical/react/LexicalComposer"; +import { ContentEditable } from "@lexical/react/LexicalContentEditable"; +import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary"; +import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"; +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; + +import EditablePlugin from "./EditablePlugin"; + +const mockSetEditable = vi.fn(); + +const mockEditorContext = { + setEditable: mockSetEditable, +}; + +vi.mock("@lexical/react/LexicalComposerContext", () => ({ + useLexicalComposerContext: () => [mockEditorContext], +})); + +function setup(editable: boolean) { + const initialConfig = { + namespace: "test-editor", + onError: (error: Error) => console.error(error), + }; + + return render( + + } + placeholder={
Enter some text...
} + ErrorBoundary={LexicalErrorBoundary} + /> + +
+ ); +} + +describe("EditablePlugin", () => { + beforeEach(() => { + mockSetEditable.mockClear(); + }); + + it("sets the editor to editable mode", () => { + setup(true); + expect(mockSetEditable).toHaveBeenCalledWith(true); + }); + + it("sets the editor to non-editable mode", () => { + setup(false); + expect(mockSetEditable).toHaveBeenCalledWith(false); + }); + + it("renders without crashing", () => { + setup(true); + expect(screen.getByText("Enter some text...")).toBeTruthy(); + }); +}); diff --git a/packages/ui/components/editor/plugins/EditablePlugin.tsx b/packages/ui/components/editor/plugins/EditablePlugin.tsx new file mode 100644 index 0000000000..9449aeb3ca --- /dev/null +++ b/packages/ui/components/editor/plugins/EditablePlugin.tsx @@ -0,0 +1,7 @@ +import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; + +export default function EditablePlugin({ editable }: { editable: boolean }) { + const [editor] = useLexicalComposerContext(); + editor.setEditable(editable); + return null; +}