fix: whatsapp message body sometimes editable (Issue #16067) (#16185)

* 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:
Shivam Jadhav
2024-08-20 16:19:24 +09:00
committed by GitHub
co-authored by unknown Anik Dhabal Babu Amit Sharma
parent 86f8c6c5fa
commit 4ff2ce90f9
5 changed files with 81 additions and 47 deletions
+14 -38
View File
@@ -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();
+3 -2
View File
@@ -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;
}