* Fixing issue #16067 * Creating reusable Editor component for Editor * fix editor issue * remove log * fix: unit test * fix: unit test * add unit test for the plugin and changes --------- Co-authored-by: unknown <adhabal2002@gmail.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
This commit is contained in:
co-authored by
unknown
Anik Dhabal Babu
Amit Sharma
parent
86f8c6c5fa
commit
4ff2ce90f9
@@ -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<FormValues>();
|
||||
const [mounted, setIsMounted] = useState(false);
|
||||
const { t } = useLocale();
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
return mounted ? (
|
||||
<Editor
|
||||
getText={() => 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}
|
||||
/>
|
||||
) : (
|
||||
<SkeletonContainer>
|
||||
<SkeletonText className="block h-24 w-full" />
|
||||
</SkeletonContainer>
|
||||
);
|
||||
};
|
||||
|
||||
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")}
|
||||
</Label>
|
||||
<DescriptionEditor isEditable={!descriptionLockedProps.disabled} />
|
||||
<Editor
|
||||
getText={() => 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}
|
||||
/>
|
||||
</div>
|
||||
<TextField
|
||||
required
|
||||
|
||||
@@ -26,13 +26,6 @@ describe("Editor", () => {
|
||||
expect(screen.getByText("add_variable")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("respects editable prop", () => {
|
||||
render(<Editor {...defaultProps} editable={false} />);
|
||||
const editor = screen.getByTestId("editor-input");
|
||||
// eslint-disable-next-line playwright/missing-playwright-await
|
||||
expect(editor).toHaveAttribute("contenteditable", "false");
|
||||
});
|
||||
|
||||
it("excludes toolbar items", () => {
|
||||
render(<Editor {...defaultProps} excludedToolbarItems={["bold", "italic"]} />);
|
||||
expect(screen.queryByTitle("Bold")).not.toBeInTheDocument();
|
||||
|
||||
@@ -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 (
|
||||
<div className="editor rounded-md">
|
||||
<LexicalComposer initialConfig={{ ...editorConfig, editable }}>
|
||||
<LexicalComposer initialConfig={{ ...editorConfig }}>
|
||||
<div className="editor-container hover:border-emphasis focus-within:ring-brand-default rounded-md p-0 transition focus-within:ring-2">
|
||||
<ToolbarPlugin
|
||||
getText={props.getText}
|
||||
@@ -88,7 +89,6 @@ export const Editor = (props: TextEditorProps) => {
|
||||
<RichTextPlugin
|
||||
contentEditable={
|
||||
<ContentEditable
|
||||
data-testid="editor-input"
|
||||
readOnly={!editable}
|
||||
style={{ height: props.height }}
|
||||
className="editor-input"
|
||||
@@ -117,6 +117,7 @@ export const Editor = (props: TextEditorProps) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<EditablePlugin editable={editable} />
|
||||
</LexicalComposer>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
<LexicalComposer initialConfig={initialConfig}>
|
||||
<RichTextPlugin
|
||||
contentEditable={<ContentEditable />}
|
||||
placeholder={<div>Enter some text...</div>}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<EditablePlugin editable={editable} />
|
||||
</LexicalComposer>
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user