Ability to format user and team bio (#6572)
* use texteditor for bio * remove block types from about editor * add props to make editor height adjustable * set isDirty to true when about input is edited * add editor to getting-started * fix editor height * remove required error for onboarding * add helper function to check if parsed bio has text * add back commented code * fix onboarding tests for optional about field * rename function * parse team bio for read only members * code clean up * fix failing e2e because of missing test id * fix onboarding e2e test * add missing parse of user bio * Update apps/web/components/getting-started/steps-views/UserProfile.tsx * Update apps/web/pages/settings/my-account/profile.tsx Co-authored-by: Omar López <zomars@me.com> * use css inline style for height instead of tailwind class * fix height of editor-input * save bio as markdown in db * fix empty line when bio is empty * fix hydration failed error * Update packages/ui/components/editor/Editor.tsx Co-authored-by: Omar López <zomars@me.com> * remove unused import Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: Omar López <zomars@me.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
13 files changed
+125
-77
No files matched your search
@@ -1,15 +1,19 @@
|
||||
import { ArrowRightIcon } from "@heroicons/react/solid";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import { useRouter } from "next/router";
|
||||
import { FormEvent, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import turndownService from "@calcom/lib/turndownService";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button, ImageUploader, showToast, TextArea } from "@calcom/ui";
|
||||
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
|
||||
import { Avatar } from "@calcom/ui";
|
||||
|
||||
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
|
||||
|
||||
const md = new MarkdownIt("default", { html: true, breaks: true });
|
||||
|
||||
type FormData = {
|
||||
bio: string;
|
||||
};
|
||||
@@ -21,13 +25,10 @@ const UserProfile = (props: IUserProfileProps) => {
|
||||
const { user } = props;
|
||||
const { t } = useLocale();
|
||||
const avatarRef = useRef<HTMLInputElement>(null!);
|
||||
const bioRef = useRef<HTMLTextAreaElement>(null);
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({ defaultValues: { bio: user?.bio || "" } });
|
||||
const { setValue, handleSubmit, getValues } = useForm<FormData>({
|
||||
defaultValues: { bio: user?.bio || "" },
|
||||
});
|
||||
|
||||
const { data: eventTypes } = trpc.viewer.eventTypes.list.useQuery();
|
||||
const [imageSrc, setImageSrc] = useState<string>(user?.avatar || "");
|
||||
const utils = trpc.useContext();
|
||||
@@ -138,25 +139,12 @@ const UserProfile = (props: IUserProfileProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<fieldset className="mt-8">
|
||||
<label htmlFor="bio" className="mb-2 block text-sm font-medium text-gray-700">
|
||||
{t("about")}
|
||||
</label>
|
||||
<TextArea
|
||||
{...register("bio", { required: true })}
|
||||
ref={bioRef}
|
||||
name="bio"
|
||||
id="bio"
|
||||
className="mt-1 block h-[60px] w-full rounded-sm border border-gray-300 px-3 py-2 focus:border-gray-500 focus:outline-none focus:ring-gray-500 sm:text-sm"
|
||||
defaultValue={user?.bio || undefined}
|
||||
onChange={(event) => {
|
||||
setValue("bio", event.target.value);
|
||||
}}
|
||||
<Label className="mb-2 block text-sm font-medium text-gray-700">{t("about")}</Label>
|
||||
<Editor
|
||||
getText={() => md.render(getValues("bio") || user?.bio || "")}
|
||||
setText={(value: string) => setValue("bio", turndownService.turndown(value))}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
/>
|
||||
{errors.bio && (
|
||||
<p data-testid="required" className="py-2 text-xs text-red-500">
|
||||
{t("required")}
|
||||
</p>
|
||||
)}
|
||||
<p className="mt-2 font-sans text-sm font-normal text-gray-600 dark:text-white">
|
||||
{t("few_sentences_about_yourself")}
|
||||
</p>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import MarkdownIt from "markdown-it";
|
||||
import Link from "next/link";
|
||||
import { TeamPageProps } from "pages/team/[slug]";
|
||||
|
||||
@@ -6,6 +7,8 @@ import { Avatar } from "@calcom/ui";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
||||
|
||||
type TeamType = TeamPageProps["team"];
|
||||
type MembersType = TeamType["members"];
|
||||
type MemberType = MembersType[number];
|
||||
@@ -13,6 +16,8 @@ type MemberType = MembersType[number];
|
||||
const Member = ({ member, teamName }: { member: MemberType; teamName: string | null }) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
const isBioEmpty = !member.bio || !member.bio.replace("<p><br></p>", "").length;
|
||||
|
||||
return (
|
||||
<Link key={member.id} href={`/${member.username}`}>
|
||||
<div className="sm:min-w-80 sm:max-w-80 dark:bg-darkgray-200 dark:hover:bg-darkgray-300 group flex min-h-full w-[90%] flex-col space-y-2 rounded-md bg-white p-4 hover:cursor-pointer hover:bg-gray-50 ">
|
||||
@@ -23,9 +28,18 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
||||
/>
|
||||
<section className="line-clamp-4 mt-2 w-full space-y-1">
|
||||
<p className="font-medium text-gray-900 dark:text-white">{member.name}</p>
|
||||
<p className="line-clamp-3 overflow-ellipsis text-sm font-normal text-gray-500 dark:text-white">
|
||||
{member.bio || t("user_from_team", { user: member.name, team: teamName })}
|
||||
</p>
|
||||
<div className="line-clamp-3 overflow-ellipsis text-sm font-normal text-gray-500 dark:text-white">
|
||||
{!isBioEmpty ? (
|
||||
<>
|
||||
<div
|
||||
className="dark:text-darkgray-600 text-s text-gray-500"
|
||||
dangerouslySetInnerHTML={{ __html: md.render(member.bio || "") }}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
t("user_from_team", { user: member.name, team: teamName })
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"@stripe/react-stripe-js": "^1.10.0",
|
||||
"@stripe/stripe-js": "^1.35.0",
|
||||
"@tanstack/react-query": "^4.3.9",
|
||||
"@types/turndown": "^5.0.1",
|
||||
"@vercel/edge-functions-ui": "^0.2.1",
|
||||
"@vercel/og": "^0.0.21",
|
||||
"accept-language-parser": "^1.5.0",
|
||||
@@ -118,6 +119,7 @@
|
||||
"stripe": "^9.16.0",
|
||||
"superjson": "1.9.1",
|
||||
"tailwindcss-radix": "^2.6.0",
|
||||
"turndown": "^7.1.1",
|
||||
"uuid": "^8.3.2",
|
||||
"web3": "^1.7.5",
|
||||
"zod": "^3.20.2"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import classNames from "classnames";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -34,6 +35,8 @@ import { EmbedProps } from "@lib/withEmbedSsr";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
||||
|
||||
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
|
||||
const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props;
|
||||
const [user] = users; //To be used when we only have a single user, not dynamic group
|
||||
@@ -41,6 +44,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
|
||||
const isBioEmpty = !user.bio || !user.bio.replace("<p><br></p>", "").length;
|
||||
|
||||
const groupEventTypes = props.users.some((user) => !user.allowDynamicBooking) ? (
|
||||
<div className="space-y-6" data-testid="event-types">
|
||||
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
|
||||
@@ -55,7 +60,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
{eventTypes.map((type, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
|
||||
className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
|
||||
<FiArrowRight className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
|
||||
<Link
|
||||
href={getUsernameSlugLink({ users: props.users, slug: type.slug })}
|
||||
@@ -138,7 +143,14 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
<BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" />
|
||||
)}
|
||||
</h1>
|
||||
<p className="dark:text-darkgray-600 text-s text-gray-500">{user.bio}</p>
|
||||
{!isBioEmpty && (
|
||||
<>
|
||||
<div
|
||||
className="dark:text-darkgray-600 text-s text-gray-500"
|
||||
dangerouslySetInnerHTML={{ __html: md.render(user.bio || "") }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
@@ -163,7 +175,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
<div
|
||||
key={type.id}
|
||||
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
|
||||
className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
|
||||
className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
|
||||
<FiArrowRight className="absolute right-4 top-4 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
|
||||
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
|
||||
<Link
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IdentityProvider } from "@prisma/client";
|
||||
import crypto from "crypto";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import { signOut } from "next-auth/react";
|
||||
import { BaseSyntheticEvent, useRef, useState } from "react";
|
||||
@@ -9,6 +10,7 @@ import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { ErrorCode } from "@calcom/lib/auth";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import turndownService from "@calcom/lib/turndownService";
|
||||
import { TRPCClientErrorLike } from "@calcom/trpc/client";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
@@ -32,6 +34,7 @@ import {
|
||||
SkeletonContainer,
|
||||
SkeletonText,
|
||||
TextField,
|
||||
Editor,
|
||||
} from "@calcom/ui";
|
||||
import { FiAlertTriangle, FiTrash2 } from "@calcom/ui/components/icon";
|
||||
|
||||
@@ -40,14 +43,16 @@ import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
const md = new MarkdownIt("default", { html: true, breaks: true });
|
||||
|
||||
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} />
|
||||
<div className="mt-6 mb-8 space-y-6 divide-y">
|
||||
<div className="flex items-center">
|
||||
<SkeletonAvatar className=" h-12 w-12 px-4" />
|
||||
<SkeletonButton className=" h-6 w-32 rounded-md p-5" />
|
||||
<SkeletonAvatar className="h-12 w-12 px-4" />
|
||||
<SkeletonButton className="h-6 w-32 rounded-md p-5" />
|
||||
</div>
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
@@ -361,9 +366,15 @@ const ProfileForm = ({
|
||||
<TextField label={t("email")} hint={t("change_email_hint")} {...formMethods.register("email")} />
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
<TextField label={t("about")} hint={t("bio_hint")} {...formMethods.register("bio")} />
|
||||
<Label>{t("about")}</Label>
|
||||
<Editor
|
||||
getText={() => md.render(formMethods.getValues("bio") || "")}
|
||||
setText={(value: string) => {
|
||||
formMethods.setValue("bio", turndownService.turndown(value), { shouldDirty: true });
|
||||
}}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button disabled={isDisabled} color="primary" className="mt-8" type="submit">
|
||||
{t("update")}
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import classNames from "classnames";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -20,6 +21,8 @@ import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import Team from "@components/team/screens/Team";
|
||||
|
||||
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
||||
|
||||
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
function TeamPage({ team }: TeamPageProps) {
|
||||
useTheme();
|
||||
@@ -75,6 +78,8 @@ function TeamPage({ team }: TeamPageProps) {
|
||||
|
||||
const teamName = team.name || "Nameless Team";
|
||||
|
||||
const isBioEmpty = !team.bio || !team.bio.replace("<p><br></p>", "").length;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HeadSeo
|
||||
@@ -91,7 +96,14 @@ function TeamPage({ team }: TeamPageProps) {
|
||||
<p className="font-cal dark:text-darkgray-900 mb-2 text-2xl tracking-wider text-gray-900">
|
||||
{teamName}
|
||||
</p>
|
||||
<p className="dark:text-darkgray-500 mt-2 text-sm font-normal text-gray-500">{team.bio}</p>
|
||||
{!isBioEmpty && (
|
||||
<>
|
||||
<div
|
||||
className="dark:text-darkgray-600 text-s text-gray-500"
|
||||
dangerouslySetInnerHTML={{ __html: md.render(team.bio || "") }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{(showMembers.isOn || !team.eventTypes.length) && <Team team={team} />}
|
||||
{!showMembers.isOn && team.eventTypes.length > 0 && (
|
||||
|
||||
@@ -54,25 +54,16 @@ test.describe("Onboarding", () => {
|
||||
});
|
||||
|
||||
await test.step("step 4", async () => {
|
||||
const finishButton = await page.locator("button[type=submit]");
|
||||
// bio field is required, try to submit (and test whether that fails)
|
||||
await finishButton.click();
|
||||
|
||||
const requiredBio = await page.locator("data-testid=required");
|
||||
await expect(requiredBio).toBeVisible();
|
||||
|
||||
await page.locator("textarea[name=bio]").fill("Something about me");
|
||||
|
||||
const isDisabled = await finishButton.isDisabled();
|
||||
await expect(isDisabled).toBe(false);
|
||||
|
||||
await finishButton.click();
|
||||
await page.locator("button[type=submit]").click();
|
||||
|
||||
// should redirect to /event-types after onboarding
|
||||
await page.waitForURL("/event-types");
|
||||
|
||||
const userComplete = await user.self();
|
||||
expect(userComplete.bio).toBe("Something about me");
|
||||
|
||||
const userCompleteBio = userComplete.bio ? userComplete.bio : "";
|
||||
|
||||
expect(userCompleteBio.replace("<p><br></p>", "").length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { MembershipRole, Prisma } from "@prisma/client";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import { useSession } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -11,6 +12,7 @@ import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import objectKeys from "@calcom/lib/objectKeys";
|
||||
import turndownService from "@calcom/lib/turndownService";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import {
|
||||
Avatar,
|
||||
@@ -24,13 +26,15 @@ import {
|
||||
LinkIconButton,
|
||||
Meta,
|
||||
showToast,
|
||||
TextArea,
|
||||
TextField,
|
||||
Editor,
|
||||
} from "@calcom/ui";
|
||||
import { FiExternalLink, FiLink, FiTrash2, FiLogOut } from "@calcom/ui/components/icon";
|
||||
|
||||
import { getLayout } from "../../../settings/layouts/SettingsLayout";
|
||||
|
||||
const md = new MarkdownIt("default", { html: true, breaks: true });
|
||||
|
||||
const regex = new RegExp("^[a-zA-Z0-9-]*$");
|
||||
|
||||
const teamProfileFormSchema = z.object({
|
||||
@@ -90,6 +94,8 @@ const ProfileView = () => {
|
||||
|
||||
const permalink = `${CAL_URL?.replace(/^(https?:|)\/\//, "")}/team/${team?.slug}`;
|
||||
|
||||
const isBioEmpty = !team || !team.bio || !team.bio.replace("<p><br></p>", "").length;
|
||||
|
||||
const deleteTeamMutation = trpc.viewer.teams.delete.useMutation({
|
||||
async onSuccess() {
|
||||
await utils.viewer.teams.list.invalidate();
|
||||
@@ -213,23 +219,14 @@ const ProfileView = () => {
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="bio"
|
||||
render={({ field: { value } }) => (
|
||||
<div className="mt-8">
|
||||
<Label>{t("about")}</Label>
|
||||
<TextArea
|
||||
name="bio"
|
||||
value={value}
|
||||
className="h-14"
|
||||
onChange={(e) => {
|
||||
form.setValue("bio", e?.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8">
|
||||
<Label>{t("about")}</Label>
|
||||
<Editor
|
||||
getText={() => md.render(form.getValues("bio") || "")}
|
||||
setText={(value: string) => form.setValue("bio", turndownService.turndown(value))}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-gray-600">{t("team_description")}</p>
|
||||
<Button color="primary" className="mt-8" type="submit" loading={mutation.isLoading}>
|
||||
{t("update")}
|
||||
@@ -239,7 +236,7 @@ const ProfileView = () => {
|
||||
(team.metadata as Prisma.JsonObject)?.requestedSlug && (
|
||||
<Button
|
||||
color="secondary"
|
||||
className="ml-2 mt-8"
|
||||
className="mt-8 ml-2"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
publishMutation.mutate({ teamId: team.id });
|
||||
@@ -255,10 +252,13 @@ const ProfileView = () => {
|
||||
<Label className="text-black">{t("team_name")}</Label>
|
||||
<p className="text-sm text-gray-800">{team?.name}</p>
|
||||
</div>
|
||||
{team?.bio && (
|
||||
{team && !isBioEmpty && (
|
||||
<>
|
||||
<Label className="mt-5 text-black">{t("about")}</Label>
|
||||
<p className="text-sm text-gray-800">{team.bio}</p>
|
||||
<div
|
||||
className="dark:text-darkgray-600 text-s text-gray-500"
|
||||
dangerouslySetInnerHTML={{ __html: md.render(team.bio || "") }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -597,6 +597,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
|
||||
props.form.clearErrors();
|
||||
}}
|
||||
variables={DYNAMIC_TEXT_VARIABLES}
|
||||
height="200px"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { components, GroupBase, Props, ValueContainerProps } from "react-select";
|
||||
|
||||
import { Select } from "@calcom/ui";
|
||||
import { FiPlus } from "@calcom/ui/components/icon";
|
||||
|
||||
const LimitedChipsContainer = <Option, IsMulti extends boolean, Group extends GroupBase<Option>>({
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import TurndownService from "turndown";
|
||||
|
||||
const turndownService = new TurndownService();
|
||||
|
||||
turndownService.addRule("newLine", {
|
||||
filter: ["br"],
|
||||
replacement: () => {
|
||||
return "<p><br></p>";
|
||||
},
|
||||
});
|
||||
|
||||
export default turndownService;
|
||||
@@ -29,6 +29,7 @@ export type TextEditorProps = {
|
||||
setText: (text: string) => void;
|
||||
excludedToolbarItems?: string[];
|
||||
variables?: string[];
|
||||
height?: string;
|
||||
};
|
||||
|
||||
const editorConfig = {
|
||||
@@ -63,8 +64,11 @@ export const Editor = (props: TextEditorProps) => {
|
||||
excludedToolbarItems={props.excludedToolbarItems}
|
||||
variables={props.variables}
|
||||
/>
|
||||
<div className="editor-inner">
|
||||
<RichTextPlugin contentEditable={<ContentEditable className="editor-input" />} placeholder="" />
|
||||
<div className="editor-inner" style={{ height: props.height }}>
|
||||
<RichTextPlugin
|
||||
contentEditable={<ContentEditable style={{ height: props.height }} className="editor-input" />}
|
||||
placeholder=""
|
||||
/>
|
||||
<AutoFocusPlugin />
|
||||
<ListPlugin />
|
||||
<LinkPlugin />
|
||||
|
||||
@@ -34,18 +34,18 @@
|
||||
position: relative;
|
||||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
height: 200px;
|
||||
overflow: scroll;
|
||||
resize: vertical;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.editor-input {
|
||||
min-height: 150px;
|
||||
height: auto;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
tab-size: 1;
|
||||
outline: 0;
|
||||
padding: 15px 10px;
|
||||
padding: 10px 10px;
|
||||
outline: none
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user